summaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
2018-02-07vhost: Added ARMV8 NEON version of function map_guest_mem()Nitin Saxena1-0/+69
(VPP-1085) The NEON implementation searches particular address in VHOST_MEMORY_MAX_NREGIONS regions. Searching two regions at a time. Change-Id: Icc3c6746bc98e3a1fa71424e51b64f62efbfdc74 Signed-off-by: Nitin Saxena <nitin.saxena@cavium.com>
2018-02-07af_packet: Fix lock positionPierre Pfister1-2/+1
In multi-worker cases, af-packet tx was subject to a pretty serious race condition as the device lock was obtained after some queue values were read from queue. Result could go from packet loss to queue inconsistency, leading to tx being stuck for 'some time'. The fix is really simple. Finding the problem was not... Change-Id: Ib18967b7459a8609428a56de934c577cea87b165 Signed-off-by: Pierre Pfister <ppfister@cisco.com>
2018-02-07classifier-based ACL: refactor + add output ACLAndrew Yourtchenko21-502/+719
For implementation of MACIP ACLs enhancement (VPP-1088), an outbound classifier-based ACL would be needed. There was an existing incomplete code for outbound ACLs, it looked almost exact copy of input ACLs, minus the various enhancements, trying to sync that code seemed error-prone and cumbersome to maintain in the longer run. This change refactors the input+output ACLs processing into a unified routine (thus any changes will have effect on both), and also adds the API to set the output interface ACL, with the same format and semantics as the existing input one (except working on output ACL of course). WARNING: IP outbound ACL in L3 mode clobbers the ip.* fields in the vnet_buffer_opaque_t, since the code is using l2_classify.* The net_buffer (p0)->ip.save_rewrite_length is rescued into l2_classify.pad.l2_len, and used to rewind the header in case of drop, so that ipX_drop prints something sensible. Change-Id: I62f814f1e3650e504474a3a5359edb8a0a8836ed Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
2018-02-07Refactor vlib_buffer flagsDamjan Marion17-169/+152
Change-Id: I853386aebfe488ebb10328435b81b6e3403c5dd0 Signed-off-by: Damjan Marion <damarion@cisco.com>
2018-02-07SCTP: address coverity-scan warningsMarco Varlese2-19/+10
Change-Id: Iba7c398a398e24b96eb536bbcefa841bd153a205 Signed-off-by: Marco Varlese <marco.varlese@suse.com>
2018-02-07libmemif: cleanup queue info while memif connectingChun Li2-1/+5
Change-Id: I4265fd0606f87f80f43f7f59ced1c3a73de82776 Signed-off-by: Chun Li <chunl2@cisco.com>
2018-02-06Fix clang -Wvarargs compile errorsDamjan Marion3-5/+5
error: passing an object that undergoes default argument promotion to 'va_start' has undefined behavior [-Werror,-Wvarargs] Change-Id: Id342beea916ec73e29e399087532caecfa19055f Signed-off-by: Damjan Marion <damarion@cisco.com>
2018-02-06Fix clang implicit conversion errorsDamjan Marion2-14/+16
Change-Id: I1771a1cca2a4bc394677b2a18f14c47f0633fa77 Signed-off-by: Damjan Marion <damarion@cisco.com>
2018-02-06vlib: epoll on worker threadsDamjan Marion12-42/+204
This patch teaches worer threads to sleep and to be waken up by kernel if there is activity on file desctiptors assigned to that thread. It also adds counters to epoll file descriptors and new debug cli 'show unix file'. Change-Id: Iaf67869f4aa88ff5b0a08982e1c08474013107c4 Signed-off-by: Damjan Marion <damarion@cisco.com>
2018-02-06SCTP: handling of heartbeating and max-retransmitsMarco Varlese4-57/+164
This patch address the need to send/receive heartbeats between peers. At the same time, the number of unacked heartbeats is tracked and when the peer requests to send DATA to the remote-peer the value of unacked heartbeats needs to be checked against the maximum value allowed for retransmissions. If the unacked heartbeats value is higher then the remote-peer is considered unreachable and the connetion needs to be shutdown. Change-Id: I2b1a21c26775e734dbe82486f40982ed5702dc63 Signed-off-by: Marco Varlese <marco.varlese@suse.com>
2018-02-06make test: don't test/set parameters in dockerKlement Sekera1-0/+11
Do not set UDS related system parameters if DOCKER_TEST is set to "True" as docker environment doesn't contain the necessary /proc/... entries. Change-Id: Id85e4512c7bba6b3feb6e6fd1fbe1e05aa10a341 Signed-off-by: Klement Sekera <ksekera@cisco.com>
2018-02-06BIER: fix support for longer bit-string lengthsNeale Ranns17-259/+391
Change-Id: I2421197b76be58099e5f8ed5554410adff202109 Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
2018-02-06VCL: Fix type in trace output.Dave Wallace1-2/+2
Change-Id: I7834e676c23a697a12a6e06111c68450ba787fc9 Signed-off-by: Dave Wallace <dwallacelf@gmail.com>
2018-02-06SCTP: missing spinlock init when multiple threadsMarco Varlese1-2/+5
When the number of threads results being more than 1, the spinlock structure requires to be initialized otherwise subsequent calls to the "lock" API (clib_spinlock_lock_if_init) would result in a void operation. Change-Id: Ia268c4687252e41962bb3f1217f0a849d8c40385 Signed-off-by: Marco Varlese <marco.varlese@suse.com>
2018-02-06Reflexive ACL support on ICMPSteve Shin1-25/+113
Normally session keys are generated by mirroring the packets sent. ICMP message type should be used and inverted for the stateful ACL. Supported ICMP messages with this patch: - ICMPv4: Echo/Timestamp/Information/Address Mask requests - ICMPv6: Echo request/Node Information Queury The invmap & valid_new tables can be modified to make any other ICMP messages to be reflexive ACL. Change-Id: Ia47b08b79fe0a5b1f7a995af78de3763d275dbd9 Signed-off-by: Steve Shin <jonshin@cisco.com>
2018-02-06Provide page-aligned length to ftruncate.Igor Mikhailov (imichail)1-1/+1
For some files such as hugepages files, ftruncate() fails with the error "Invalid argument" if the 'length' parameter is not on a page boundary. Change-Id: I42a9cde98707da15e3c5d1653046e2277fc7a424 Signed-off-by: Igor Mikhailov (imichail) <imichail@cisco.com>
2018-02-05session: segment manager refactorFlorin Coras21-620/+878
- use valloc as a 'central' segment baseva manager - use per segment manager segment pools and use rwlocks to guard them - add session test that exercises segment creation - embed segment manager properties into application since they're shared - fix rw locks Change-Id: I761164c147275d9e8a926f1eda395e090d231f9a Signed-off-by: Florin Coras <fcoras@cisco.com>
2018-02-05NAT64: Run nat64-expire-worker-walk only when NAT64 is configured (VPP-1162)Matus Fabian2-4/+48
Change-Id: Ic5e8d74bf5ac84cce5661de44778c89541c67636 Signed-off-by: Matus Fabian <matfabia@cisco.com>
2018-02-05Fix ip4/6_reass_main.ip4/6_reass_expire_node_idx used before setDave Barach3-8/+17
Add an ASSERT to vlib_process_signal_event_helper to catch future instances of passing node_index = 0 to vlib_process_signal_event(). Change-Id: Iec896fc7c3917feb2fd3198cea42851ba88e64e5 Signed-off-by: Dave Barach <dave@barachs.net>
2018-02-05SCTP: calculate RTO / RTT and RTTVAR as per RFCMarco Varlese4-47/+122
This patch addresses the need to calculate the RTO / RTT and RTTVAR according to the rules depicted by the RFC4960 at section 6.3.1 Change-Id: I1d346f3c67610070b3f602f32c7738d58b99ffed Signed-off-by: Marco Varlese <marco.varlese@suse.com>
2018-02-05vhost_user: code cleanupHaiyang Tan1-4/+4
1. Replace the magic number '-1' with MAP_FAILED 2. On x86 platform, QEMU uses vhostuser required the memory back-end is file based, the file could be tmpfs(4K page size) or hugetlbfs(2M or 1G page size) Change-Id: If1818cb6833728d641f68e4d4a3bc645e70f2ee6 Signed-off-by: Haiyang Tan <haiyang.tan.dev@gmail.com>
2018-02-03IP reassembly: workaround coverity warningsKlement Sekera2-0/+2
Change-Id: Ide577f036d9d8dcedd99cdb4666a0eaf8a19b92e Signed-off-by: Klement Sekera <ksekera@cisco.com>
2018-02-02Clean up for vcl.am, making vppcom.h C++ awareKeith Burns (alagalah)2-10/+22
Change-Id: I2548ebd37e16bed50b5c8046b728415a341413e3 Signed-off-by: Keith Burns (alagalah) <alagalah@gmail.com>
2018-02-02lisp-cp: fix handling of ndp without source link addr VPP-1159Florin Coras1-4/+21
Change-Id: Idddb60bbc7fcc701d39212f6422a6b2f6dc75221 Signed-off-by: Florin Coras <fcoras@cisco.com> (cherry picked from commit cba3675fabe618194bf80a9de0e9c53b89a541ca)
2018-02-02make test: use random seedKlement Sekera1-1/+1
This fixes a constant setting of random seed forgotten from testing. Change-Id: Ie3c4db8bb2b4b73ba33de1ffc02cb563391fd31c Signed-off-by: Klement Sekera <ksekera@cisco.com>
2018-02-02VOM: route-domain find() fixNeale Ranns3-24/+5
Change-Id: I5b7117f3568e3ba979baa15521b2cfc180abb682 Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
2018-02-02vlmemory/svm: fix client detach from svm regionFlorin Coras6-13/+59
Clients cannot know at svm region detach time if the shm backing files have been recreated (e.g., if vpp restarts) and therefore should not try to unlink them. Otherwise, terminating clients attached to previous instantiations of a re-allocated region end up making the new instance un-mappable by removing its backing file. Change-Id: Idcd0cab776e63fd75b821bc9f0fac58217b9ccbe Signed-off-by: Florin Coras <fcoras@cisco.com>
2018-02-02Add link to 18.01 test framework documentation.Dave Wallace1-0/+1
Change-Id: I030602391ea3b612ac9a6780399cc30b427cc3a5 Signed-off-by: Dave Wallace <dwallacelf@gmail.com> (cherry picked from commit 92b15bcea4c6c5e62415a8207463eb9a897630c6)
2018-02-02Update 18.01 Release NotesDave Wallace1-0/+1
Change-Id: Id2f13c59c6f4e7bc79f6e77d6dab752bf6dfb06a Signed-off-by: Dave Wallace <dwallacelf@gmail.com> (cherry picked from commit a1a382bb2bc2fbf6bf947a24a263fefbe32497e7)
2018-02-02vlib_buffer_clone: allow client to request the maximum number of clones; 256Neale Ranns1-12/+50
Change-Id: Id96dc5d86719546268b50a9999a06387d2d9075c Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
2018-02-02Add L3DSR feature in LB pluginHongjun Ni9-96/+282
L3DSR is used to overcome Layer 2 limitations of Direct Server Return Load Balancing. It maps VIP to DSCP bits, and reuse TOS bits to transfer it to server, and then server will get VIP from DSCP-to-VIP mapping. Please refer to https://www.nanog.org/meetings/nanog51/presentations/Monday/NANOG51.Talk45.nanog51-Schaumann.pdf Change-Id: I403ffeadfb04ed0265086eb2dc41f2e17f8f34cb Signed-off-by: Hongjun Ni <hongjun.ni@intel.com>
2018-02-01Out-of-order data chunks handling and moreMarco Varlese4-69/+235
This patch addresses the need to handle out-of-order data chunks received by a peer. To do that effectively, we had to add the handling of data chunks flags (E/B/U bit) to understand whether the stream is fragmenting user-message data and in that case if a fragment is the FIRST/MIDDLE/LAST one of a transmission. The same patch also addresses the security requirement to have a HMAC calculated and incorporated in the INIT_ACK and COOKIE_ECHO chunks. The algorithm used is the HMAC-SHA1. Change-Id: Ib6a9a80492e2aafe5c8480d6e02da895efe9f90b Signed-off-by: Marco Varlese <marco.varlese@suse.com>
2018-02-01IPv4/6 reassemblyKlement Sekera35-66/+4322
Change-Id: Ic5dcadd13c88b8a5e7896dab82404509c081614a Signed-off-by: Klement Sekera <ksekera@cisco.com>
2018-02-01vxlan: Lookup FIB in either IPv4 or IPv6 families.Jon Loeliger1-4/+5
Prior to this commit, the VXLAN "create" API assumed the vrf_id belonged to only the IPv4 FIB tables space. With this commit, the FIB table is found in either the IPv4 or IPv6 table as determined by the is_ipv6 flag. This follows the same pattern that was already being done in the CLI code for the VXLAN "create" command. Change-Id: I35d5e37db24efa858e4696dc2c004fa64bb4a4a8 Signed-off-by: Jon Loeliger <jdl@netgate.com>
2018-02-01FIB: Consolidate several copies of fib_ip_proto() into one.Jon Loeliger5-24/+11
Rather than having multiple copies of the same function scattered around, promote the function into the FIB PROTOCOL definitions in fib_types.h. Change-Id: I11c4d85931167d3a5f3dc1278afecc8845b23cd7 Signed-off-by: Jon Loeliger <jdl@netgate.com>
2018-02-01tcp: tcp_output.c failed to compile when VLIB_BUFFER_TRACE_TRAJECTORY is enabledSteven1-1/+1
Fixed a typo in tcp_push_header(). The typo only kicks in when the macro VLIB_BUFFER_TRACE_TRAJECTORY is enabled. Change-Id: I62832a4932ec5b14e3063d5eac113780851aae59 Signed-off-by: Steven <sluong@cisco.com>
2018-02-01dpdk:fix typo in prefetch sequenceEyal Bari1-2/+2
Change-Id: I7110436626352d45ffe0ca71fb88dea2c77ab639 Signed-off-by: Eyal Bari <ebari@cisco.com>
2018-02-01Add flowhash hash table to vppinfraPierre Pfister5-0/+1009
This hash table intends to provide an alternative to the widely used bihash table in places where either: - Hash entry timeout is required - The hash table data does not fit in CPU cache Although the bihash table is very fast, each lookup requires accessing two cache lines in a serialized fashion. It works fine when the hash table is in cache, but hits a wall when it does not. The 'flowhash' table uses a simplified design (at the cost of a less good bucket auto-scaling) where each access only requires a single memory lookup (in the absence of collision). The hash table also uses a reduced number of registers. In practice, a VPP node implementing a stateful feature would typically: - prefetch buffer metadata (in-cache) - prefetch packet header (in-cache) - compute hash & prefetch hash bucket (possibly in RAM) - read/write key and value from bucket Using this hash table, it is possible to pipeline accesses in a way that does not exhaust CPU's line field buffers, even when the requested value is located in RAM (i.e. not in cache). Measurements showed it was possible to scale to tens of millions of flows (with a full 5-tuple matching and 32B value, i.e. 1 cache line per flow) with no performance degradation when the hash table grows to the point it doesn't fit in cache anymore. I have used this table in a couple of non-open-sourced projects, but think it might be useful to lb, nat, and possibly other VPP subsystems. More information in the .h file. Change-Id: I2b13dde0eabd868b75da1cedbfca0bf74d705102 Signed-off-by: Pierre Pfister <ppfister@cisco.com>
2018-02-01srv6-ad: fixing coverity issuesFrancois Clad1-8/+32
Change-Id: Ica6d8dd773bb3b478f1c7e40d59dfbdd4b588b85 Signed-off-by: Francois Clad <fclad@cisco.com>
2018-02-01srv6-as: fixing coverity issuesFrancois Clad1-8/+32
Change-Id: I911e09aadd3df1123634fd97098920f107f9a2fc Signed-off-by: Francois Clad <fclad@cisco.com>
2018-01-31Fix VNET_BUFFER_F_SPAN_CLONE flagDamjan Marion1-1/+1
Change-Id: I670e41bcfc61a45555431603c937f8dad4d165e9 Signed-off-by: Damjan Marion <damarion@cisco.com>
2018-01-31RPMS: Also install C.py, JSON.py for vppapipgen.Jon Loeliger1-0/+3
When building plugins outside of the VPP tree, the vppapigen tool requires the use of the C.py / JSON.py code. To that end, install it in /usr/share/vpp as referenced. Change-Id: I457d58e7bde7140c7811fa0a93b4f44d1310784a Signed-off-by: Jon Loeliger <jdl@netgate.com>
2018-01-31vlib: allocating buffers on thread-x and freeing them on thread-y causesSteven4-25/+17
a crash on debug image (VPP-1151) In debug image, there is extra code to validate the buffer when it is freed. It uses the hash table to lookup the buffer index with spinlock to prevent contention. However, there is one spinlock for each worker thread. So allocating the buffer on thread-x and freeing the same buffer on thread-y causes the validation to fail on thread-y. The fix is to have only one spinlock, stored in vlib_global_main. Change-Id: Ic383846cefe84a3e262255afcf82276742f0f62e Signed-off-by: Steven <sluong@cisco.com> (cherry picked from commit a7effa1b072463f12305a474f082aeaffb7ada4b)
2018-01-31Improved tracing for the IP[46] not-enabled case.Neale Ranns6-24/+77
now we get 00:00:03:665501: pg-input ... 00:00:03:665681: ethernet-input ... 00:00:03:665691: ip6-input UDP: 2001::1 -> ffef::1 tos 0x00, flow label 0x0, hop limit 64, payload length 108 UDP: 1234 -> 1234 length 108, checksum 0x7b25 00:00:03:665695: ip6-not-enabled UDP: 2001::1 -> ffef::1 tos 0x00, flow label 0x0, hop limit 64, payload length 108 UDP: 1234 -> 1234 length 108, checksum 0x7b25 00:00:03:665706: error-drop ethernet-input: no error Same goes for IPv4 Change-Id: Ia360df39b43281d3a0aa1b686f04b73cfa37c546 Signed-off-by: Neale Ranns <nranns@cisco.com>
2018-01-31NAT66 1:1 mapping (VPP-1108)Matus Fabian11-2/+1557
Support the 1:1 translation of source address for IPv6 Change-Id: I934d18e5ec508bf7422d796ee5f172b79c048011 Signed-off-by: Matus Fabian <matfabia@cisco.com>
2018-01-31NAT44: in2out output feature skip translation for already translated packets ↵Matus Fabian3-15/+173
(VPP-1156) Change-Id: I5395245c9e49f741a949ada1f725c34f9379c249 Signed-off-by: Matus Fabian <matfabia@cisco.com>
2018-01-31NAT44: Delete dynamic sessions matching new 1:1NAT (VPP-1158)Matus Fabian2-8/+83
Change-Id: Ib99b597502b8335e57ecfa122b12e2e5aa45ee1a Signed-off-by: Matus Fabian <matfabia@cisco.com>
2018-01-31NAT44: nat44_static_mapping_details protocol=0 if addr_only=0 (VPP-1158)Matus Fabian3-13/+22
Change-Id: I1e3cfc751e7657464fc850dc56ddf763df45f62e Signed-off-by: Matus Fabian <matfabia@cisco.com>
2018-01-31Prep-work patch for congestion-controlMarco Varlese6-450/+670
This patch addresses the missing field in various data-structures to track valuable information to implement the congestion-control algorithms and manage sub-connections states. It adds the possibility to queue up to 2 SACKs chunks when the connection is not gapping. At the same time, it pushes some variable/field renaming for better readibility. Change-Id: Idcc53512983456779600a75e78e21af078e46602 Signed-off-by: Marco Varlese <marco.varlese@suse.de>
2018-01-30Compile valloc.c, install header file, etc.Dave Barach2-1/+8
Change-Id: Ibc252d9ed595be955790ec1c97d8730e43ad89b2 Signed-off-by: Dave Barach <dave@barachs.net>
ound-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
# Copyright (c) 2019 Cisco and/or its affiliates.
# Copyright (c) 2019 PANTHEON.tech and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -exuo pipefail

# This library defines functions used by multiple entry scripts.
# Keep functions ordered alphabetically, please.

# TODO: Add a link to bash style guide.
# TODO: Consider putting every die into a {} block,
#   the code might become more readable (but longer).


function activate_docker_topology () {

    # Create virtual vpp-device topology. Output of the function is topology
    # file describing created environment saved to a file.
    #
    # Variables read:
    # - BASH_FUNCTION_DIR - Path to existing directory this file is located in.
    # - TOPOLOGIES - Available topologies.
    # - NODENESS - Node multiplicity of desired testbed.
    # - FLAVOR - Node flavor string, usually describing the processor.
    # - IMAGE_VER_FILE - Name of file that contains the image version.
    # - CSIT_DIR - Directory where ${IMAGE_VER_FILE} is located.
    # Variables set:
    # - WORKING_TOPOLOGY - Path to topology file.

    set -exuo pipefail

    source "${BASH_FUNCTION_DIR}/device.sh" || {
        die "Source failed!"
    }

    device_image="$(< ${CSIT_DIR}/${IMAGE_VER_FILE})"
    case_text="${NODENESS}_${FLAVOR}"
    case "${case_text}" in
        "1n_skx" | "1n_tx2")
            # We execute reservation over csit-shim-dcr (ssh) which runs sourced
            # script's functions. Env variables are read from ssh output
            # back to localhost for further processing.
            hostname=$(grep search /etc/resolv.conf | cut -d' ' -f3) || die
            ssh="ssh root@${hostname} -p 6022"
            run="activate_wrapper ${NODENESS} ${FLAVOR} ${device_image}"
            # The "declare -f" output is long and boring.
            set +x
            # backtics to avoid https://midnight-commander.org/ticket/2142
            env_vars=`${ssh} "$(declare -f); ${run}"` || {
                die "Topology reservation via shim-dcr failed!"
            }
            set -x
            set -a
            source <(echo "$env_vars" | grep -v /usr/bin/docker) || {
                die "Source failed!"
            }
            set +a
            ;;
        "1n_vbox")
            # We execute reservation on localhost. Sourced script automatially
            # sets environment variables for further processing.
            activate_wrapper "${NODENESS}" "${FLAVOR}" "${device_image}" || die
            ;;
        *)
            die "Unknown specification: ${case_text}!"
    esac

    trap 'deactivate_docker_topology' EXIT || {
         die "Trap attempt failed, please cleanup manually. Aborting!"
    }

    # Replace all variables in template with those in environment.
    source <(echo 'cat <<EOF >topo.yml'; cat ${TOPOLOGIES[0]}; echo EOF;) || {
        die "Topology file create failed!"
    }

    WORKING_TOPOLOGY="/tmp/topology.yaml"
    mv topo.yml "${WORKING_TOPOLOGY}" || {
        die "Topology move failed!"
    }
    cat ${WORKING_TOPOLOGY} | grep -v password || {
        die "Topology read failed!"
    }
}


function activate_virtualenv () {

    # Update virtualenv pip package, delete and create virtualenv directory,
    # activate the virtualenv, install requirements, set PYTHONPATH.

    # Arguments:
    # - ${1} - Path to existing directory for creating virtualenv in.
    #          If missing or empty, ${CSIT_DIR} is used.
    # - ${2} - Path to requirements file, ${CSIT_DIR}/requirements.txt if empty.
    # Variables read:
    # - CSIT_DIR - Path to existing root of local CSIT git repository.
    # Variables exported:
    # - PYTHONPATH - CSIT_DIR, as CSIT Python scripts usually need this.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    root_path="${1-$CSIT_DIR}"
    env_dir="${root_path}/env"
    req_path=${2-$CSIT_DIR/requirements.txt}
    rm -rf "${env_dir}" || die "Failed to clean previous virtualenv."
    pip3 install --upgrade virtualenv || {
        die "Virtualenv package install failed."
    }
    virtualenv -p $(which python3) "${env_dir}" || {
        die "Virtualenv creation for $(which python3) failed."
    }
    set +u
    source "${env_dir}/bin/activate" || die "Virtualenv activation failed."
    set -u
    pip3 install --upgrade -r "${req_path}" || {
        die "Requirements installation failed."
    }
    # Most CSIT Python scripts assume PYTHONPATH is set and exported.
    export PYTHONPATH="${CSIT_DIR}" || die "Export failed."
}


function archive_tests () {

    # Create .tar.xz of generated/tests for archiving.
    # To be run after generate_tests, kept separate to offer more flexibility.

    # Directory read:
    # - ${GENERATED_DIR}/tests - Tree of executed suites to archive.
    # File rewriten:
    # - ${ARCHIVE_DIR}/tests.tar.xz - Archive of generated tests.

    set -exuo pipefail

    tar c "${GENERATED_DIR}/tests" | xz -9e > "${ARCHIVE_DIR}/tests.tar.xz" || {
        die "Error creating archive of generated tests."
    }
}


function check_download_dir () {

    # Fail if there are no files visible in ${DOWNLOAD_DIR}.
    #
    # Variables read:
    # - DOWNLOAD_DIR - Path to directory pybot takes the build to test from.
    # Directories read:
    # - ${DOWNLOAD_DIR} - Has to be non-empty to proceed.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    if [[ ! "$(ls -A "${DOWNLOAD_DIR}")" ]]; then
        die "No artifacts downloaded!"
    fi
}


function check_prerequisites () {

    # Fail if prerequisites are not met.
    #
    # Functions called:
    # - installed - Check if application is installed/present in system.
    # - die - Print to stderr and exit.

    set -exuo pipefail

    if ! installed sshpass; then
        die "Please install sshpass before continue!"
    fi
}


function common_dirs () {

    # Set global variables, create some directories (without touching content).

    # Variables set:
    # - BASH_FUNCTION_DIR - Path to existing directory this file is located in.
    # - CSIT_DIR - Path to existing root of local CSIT git repository.
    # - TOPOLOGIES_DIR - Path to existing directory with available tpologies.
    # - RESOURCES_DIR - Path to existing CSIT subdirectory "resources".
    # - TOOLS_DIR - Path to existing resources subdirectory "tools".
    # - PYTHON_SCRIPTS_DIR - Path to existing tools subdirectory "scripts".
    # - ARCHIVE_DIR - Path to created CSIT subdirectory "archive".
    # - DOWNLOAD_DIR - Path to created CSIT subdirectory "download_dir".
    # - GENERATED_DIR - Path to created CSIT subdirectory "generated".
    # Directories created if not present:
    # ARCHIVE_DIR, DOWNLOAD_DIR, GENERATED_DIR.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    this_file=$(readlink -e "${BASH_SOURCE[0]}") || {
        die "Some error during locating of this source file."
    }
    BASH_FUNCTION_DIR=$(dirname "${this_file}") || {
        die "Some error during dirname call."
    }
    # Current working directory could be in a different repo, e.g. VPP.
    pushd "${BASH_FUNCTION_DIR}" || die "Pushd failed"
    relative_csit_dir=$(git rev-parse --show-toplevel) || {
        die "Git rev-parse failed."
    }
    CSIT_DIR=$(readlink -e "${relative_csit_dir}") || die "Readlink failed."
    popd || die "Popd failed."
    TOPOLOGIES_DIR=$(readlink -e "${CSIT_DIR}/topologies/available") || {
        die "Readlink failed."
    }
    RESOURCES_DIR=$(readlink -e "${CSIT_DIR}/resources") || {
        die "Readlink failed."
    }
    TOOLS_DIR=$(readlink -e "${RESOURCES_DIR}/tools") || {
        die "Readlink failed."
    }
    DOC_GEN_DIR=$(readlink -e "${TOOLS_DIR}/doc_gen") || {
        die "Readlink failed."
    }
    PYTHON_SCRIPTS_DIR=$(readlink -e "${TOOLS_DIR}/scripts") || {
        die "Readlink failed."
    }

    ARCHIVE_DIR=$(readlink -f "${CSIT_DIR}/archive") || {
        die "Readlink failed."
    }
    mkdir -p "${ARCHIVE_DIR}" || die "Mkdir failed."
    DOWNLOAD_DIR=$(readlink -f "${CSIT_DIR}/download_dir") || {
        die "Readlink failed."
    }
    mkdir -p "${DOWNLOAD_DIR}" || die "Mkdir failed."
    GENERATED_DIR=$(readlink -f "${CSIT_DIR}/generated") || {
        die "Readlink failed."
    }
    mkdir -p "${GENERATED_DIR}" || die "Mkdir failed."
}


function compose_pybot_arguments () {

    # Variables read:
    # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
    # - DUT - CSIT test/ subdirectory, set while processing tags.
    # - TAGS - Array variable holding selected tag boolean expressions.
    # - TOPOLOGIES_TAGS - Tag boolean expression filtering tests for topology.
    # - TEST_CODE - The test selection string from environment or argument.
    # Variables set:
    # - PYBOT_ARGS - String holding part of all arguments for pybot.
    # - EXPANDED_TAGS - Array of strings pybot arguments compiled from tags.

    set -exuo pipefail

    # No explicit check needed with "set -u".
    PYBOT_ARGS=("--loglevel" "TRACE")
    PYBOT_ARGS+=("--variable" "TOPOLOGY_PATH:${WORKING_TOPOLOGY}")

    case "${TEST_CODE}" in
        *"device"*)
            PYBOT_ARGS+=("--suite" "tests.${DUT}.device")
            ;;
        *"func"*)
            PYBOT_ARGS+=("--suite" "tests.${DUT}.func")
            ;;
        *"perf"*)
            PYBOT_ARGS+=("--suite" "tests.${DUT}.perf")
            ;;
        *)
            die "Unknown specification: ${TEST_CODE}"
    esac

    EXPANDED_TAGS=()
    for tag in "${TAGS[@]}"; do
        if [[ ${tag} == "!"* ]]; then
            EXPANDED_TAGS+=("--exclude" "${tag#$"!"}")
        else
            EXPANDED_TAGS+=("--include" "${TOPOLOGIES_TAGS}AND${tag}")
        fi
    done
}


function copy_archives () {

    # Create additional archive if workspace variable is set.
    # This way if script is running in jenkins all will be
    # automatically archived to logs.fd.io.
    #
    # Variables read:
    # - WORKSPACE - Jenkins workspace, copy only if the value is not empty.
    #   Can be unset, then it speeds up manual testing.
    # - ARCHIVE_DIR - Path to directory with content to be copied.
    # Directories updated:
    # - ${WORKSPACE}/archives/ - Created if does not exist.
    #   Content of ${ARCHIVE_DIR}/ is copied here.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    if [[ -n "${WORKSPACE-}" ]]; then
        mkdir -p "${WORKSPACE}/archives/" || die "Archives dir create failed."
        cp -rf "${ARCHIVE_DIR}"/* "${WORKSPACE}/archives" || die "Copy failed."
    fi
}


function deactivate_docker_topology () {

    # Deactivate virtual vpp-device topology by removing containers.
    #
    # Variables read:
    # - NODENESS - Node multiplicity of desired testbed.
    # - FLAVOR - Node flavor string, usually describing the processor.

    set -exuo pipefail

    case_text="${NODENESS}_${FLAVOR}"
    case "${case_text}" in
        "1n_skx" | "1n_tx2")
            hostname=$(grep search /etc/resolv.conf | cut -d' ' -f3) || die
            ssh="ssh root@${hostname} -p 6022"
            env_vars=$(env | grep CSIT_ | tr '\n' ' ' ) || die
            # The "declare -f" output is long and boring.
            set +x
            ${ssh} "$(declare -f); deactivate_wrapper ${env_vars}" || {
                die "Topology cleanup via shim-dcr failed!"
            }
            set -x
            ;;
        "1n_vbox")
            enter_mutex || die
            clean_environment || {
                die "Topology cleanup locally failed!"
            }
            exit_mutex || die
            ;;
        *)
            die "Unknown specification: ${case_text}!"
    esac
}


function die () {

    # Print the message to standard error end exit with error code specified
    # by the second argument.
    #
    # Hardcoded values:
    # - The default error message.
    # Arguments:
    # - ${1} - The whole error message, be sure to quote. Optional
    # - ${2} - the code to exit with, default: 1.

    set -x
    set +eu
    warn "${1:-Unspecified run-time error occurred!}"
    exit "${2:-1}"
}


function die_on_pybot_error () {

    # Source this fragment if you want to abort on any failed test case.
    #
    # Variables read:
    # - PYBOT_EXIT_STATUS - Set by a pybot running fragment.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    if [[ "${PYBOT_EXIT_STATUS}" != "0" ]]; then
        die "Test failures are present!" "${PYBOT_EXIT_STATUS}"
    fi
}


function generate_tests () {

    # Populate ${GENERATED_DIR}/tests based on ${CSIT_DIR}/tests/.
    # Any previously existing content of ${GENERATED_DIR}/tests is wiped before.
    # The generation is done by executing any *.py executable
    # within any subdirectory after copying.

    # This is a separate function, because this code is called
    # both by autogen checker and entries calling run_pybot.

    # Directories read:
    # - ${CSIT_DIR}/tests - Used as templates for the generated tests.
    # Directories replaced:
    # - ${GENERATED_DIR}/tests - Overwritten by the generated tests.

    set -exuo pipefail

    rm -rf "${GENERATED_DIR}/tests" || die
    cp -r "${CSIT_DIR}/tests" "${GENERATED_DIR}/tests" || die
    cmd_line=("find" "${GENERATED_DIR}/tests" "-type" "f")
    cmd_line+=("-executable" "-name" "*.py")
    file_list=$("${cmd_line[@]}") || die

    for gen in ${file_list}; do
        directory="$(dirname "${gen}")" || die
        filename="$(basename "${gen}")" || die
        pushd "${directory}" || die
        ./"${filename}" || die
        popd || die
    done
}


function get_test_code () {

    # Arguments:
    # - ${1} - Optional, argument of entry script (or empty as unset).
    #   Test code value to override job name from environment.
    # Variables read:
    # - JOB_NAME - String affecting test selection, default if not argument.
    # Variables set:
    # - TEST_CODE - The test selection string from environment or argument.
    # - NODENESS - Node multiplicity of desired testbed.
    # - FLAVOR - Node flavor string, usually describing the processor.

    set -exuo pipefail

    TEST_CODE="${1-}" || die "Reading optional argument failed, somehow."
    if [[ -z "${TEST_CODE}" ]]; then
        TEST_CODE="${JOB_NAME-}" || die "Reading job name failed, somehow."
    fi

    case "${TEST_CODE}" in
        *"1n-vbox"*)
            NODENESS="1n"
            FLAVOR="vbox"
            ;;
        *"1n-skx"*)
            NODENESS="1n"
            FLAVOR="skx"
            ;;
       *"1n-tx2"*)
            NODENESS="1n"
            FLAVOR="tx2"
            ;;
        *"2n-skx"*)
            NODENESS="2n"
            FLAVOR="skx"
            ;;
        *"3n-skx"*)
            NODENESS="3n"
            FLAVOR="skx"
            ;;
        *"2n-clx"*)
            NODENESS="2n"
            FLAVOR="clx"
            ;;
        *"2n-dnv"*)
            NODENESS="2n"
            FLAVOR="dnv"
            ;;
        *"3n-dnv"*)
            NODENESS="3n"
            FLAVOR="dnv"
            ;;
        *"3n-tsh"*)
            NODENESS="3n"
            FLAVOR="tsh"
            ;;
        *)
            # Fallback to 3-node Haswell by default (backward compatibility)
            NODENESS="3n"
            FLAVOR="hsw"
            ;;
    esac
}


function get_test_tag_string () {

    # Variables read:
    # - GERRIT_EVENT_TYPE - Event type set by gerrit, can be unset.
    # - GERRIT_EVENT_COMMENT_TEXT - Comment text, read for "comment-added" type.
    # - TEST_CODE - The test selection string from environment or argument.
    # Variables set:
    # - TEST_TAG_STRING - The string following trigger word in gerrit comment.
    #   May be empty, or even not set on event types not adding comment.

    # TODO: ci-management scripts no longer need to perform this.

    set -exuo pipefail

    if [[ "${GERRIT_EVENT_TYPE-}" == "comment-added" ]]; then
        case "${TEST_CODE}" in
            *"device"*)
                trigger="devicetest"
                ;;
            *"perf"*)
                trigger="perftest"
                ;;
            *)
                die "Unknown specification: ${TEST_CODE}"
        esac
        # Ignore lines not containing the trigger word.
        comment=$(fgrep "${trigger}" <<< "${GERRIT_EVENT_COMMENT_TEXT}") || true
        # The vpp-csit triggers trail stuff we are not interested in.
        # Removing them and trigger word: https://unix.stackexchange.com/a/13472
        # (except relying on \s whitespace, \S non-whitespace and . both).
        # The last string is concatenated, only the middle part is expanded.
        cmd=("grep" "-oP" '\S*'"${trigger}"'\S*\s\K.+$') || die "Unset trigger?"
        # On parsing error, TEST_TAG_STRING probably stays empty.
        TEST_TAG_STRING=$("${cmd[@]}" <<< "${comment}") || true
    fi
}


function installed () {

    # Check if the given utility is installed. Fail if not installed.
    #
    # Duplicate of common.sh function, as this file is also used standalone.
    #
    # Arguments:
    # - ${1} - Utility to check.
    # Returns:
    # - 0 - If command is installed.
    # - 1 - If command is not installed.

    set -exuo pipefail

    command -v "${1}"
}


function reserve_and_cleanup_testbed () {

    # Reserve physical testbed, perform cleanup, register trap to unreserve.
    # When cleanup fails, remove from topologies and keep retrying
    # until all topologies are removed.
    #
    # Variables read:
    # - TOPOLOGIES - Array of paths to topology yaml to attempt reservation on.
    # - PYTHON_SCRIPTS_DIR - Path to directory holding the reservation script.
    # - BUILD_TAG - Any string suitable as filename, identifying
    #   test run executing this function. May be unset.
    # Variables set:
    # - TOPOLOGIES - Array of paths to topologies, with failed cleanups removed.
    # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
    # Functions called:
    # - die - Print to stderr and exit.
    # - ansible_hosts - Perform an action using ansible, see ansible.sh
    # Traps registered:
    # - EXIT - Calls cancel_all for ${WORKING_TOPOLOGY}.

    set -exuo pipefail

    while true; do
        for topo in "${TOPOLOGIES[@]}"; do
            set +e
            scrpt="${PYTHON_SCRIPTS_DIR}/topo_reservation.py"
            opts=("-t" "${topo}" "-r" "${BUILD_TAG:-Unknown}")
            python3 "${scrpt}" "${opts[@]}"
            result="$?"
            set -e
            if [[ "${result}" == "0" ]]; then
                # Trap unreservation before cleanup check,
                # so multiple jobs showing failed cleanup improve chances
                # of humans to notice and fix.
                WORKING_TOPOLOGY="${topo}"
                echo "Reserved: ${WORKING_TOPOLOGY}"
                trap "untrap_and_unreserve_testbed" EXIT || {
                    message="TRAP ATTEMPT AND UNRESERVE FAILED, FIX MANUALLY."
                    untrap_and_unreserve_testbed "${message}" || {
                        die "Teardown should have died, not failed."
                    }
                    die "Trap attempt failed, unreserve succeeded. Aborting."
                }
                # Cleanup check.
                set +e
                ansible_hosts "cleanup"
                result="$?"
                set -e
                if [[ "${result}" == "0" ]]; then
                    break
                fi
                warn "Testbed cleanup failed: ${topo}"
                untrap_and_unreserve_testbed "Fail of unreserve after cleanup."
            fi
            # Else testbed is accessible but currently reserved, moving on.
        done

        if [[ -n "${WORKING_TOPOLOGY-}" ]]; then
            # Exit the infinite while loop if we made a reservation.
            warn "Reservation and cleanup successful."
            break
        fi

        if [[ "${#TOPOLOGIES[@]}" == "0" ]]; then
            die "Run out of operational testbeds!"
        fi

        # Wait ~3minutes before next try.
        sleep_time="$[ ( ${RANDOM} % 20 ) + 180 ]s" || {
            die "Sleep time calculation failed."
        }
        echo "Sleeping ${sleep_time}"
        sleep "${sleep_time}" || die "Sleep failed."
    done
}


function run_pybot () {

    # Run pybot with options based on input variables. Create output_info.xml
    #
    # Variables read:
    # - CSIT_DIR - Path to existing root of local CSIT git repository.
    # - ARCHIVE_DIR - Path to store robot result files in.
    # - PYBOT_ARGS, EXPANDED_TAGS - See compose_pybot_arguments.sh
    # - GENERATED_DIR - Tests are assumed to be generated under there.
    # Variables set:
    # - PYBOT_EXIT_STATUS - Exit status of most recent pybot invocation.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    all_options=("--outputdir" "${ARCHIVE_DIR}" "${PYBOT_ARGS[@]}")
    all_options+=("--noncritical" "EXPECTED_FAILING")
    all_options+=("${EXPANDED_TAGS[@]}")

    pushd "${CSIT_DIR}" || die "Change directory operation failed."
    set +e
    robot "${all_options[@]}" "${GENERATED_DIR}/tests/"
    PYBOT_EXIT_STATUS="$?"
    set -e

    # Generate INFO level output_info.xml for post-processing.
    all_options=("--loglevel" "INFO")
    all_options+=("--log" "none")
    all_options+=("--report" "none")
    all_options+=("--output" "${ARCHIVE_DIR}/output_info.xml")
    all_options+=("${ARCHIVE_DIR}/output.xml")
    rebot "${all_options[@]}" || true
    popd || die "Change directory operation failed."
}


function select_arch_os () {

    # Set variables affected by local CPU architecture and operating system.
    #
    # Variables set:
    # - VPP_VER_FILE - Name of file in CSIT dir containing vpp stable version.
    # - IMAGE_VER_FILE - Name of file in CSIT dir containing the image name.
    # - PKG_SUFFIX - Suffix of OS package file name, "rpm" or "deb."

    set -exuo pipefail

    os_id=$(grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') || {
        die "Get OS release failed."
    }

    case "${os_id}" in
        "ubuntu"*)
            IMAGE_VER_FILE="VPP_DEVICE_IMAGE_UBUNTU"
            VPP_VER_FILE="VPP_STABLE_VER_UBUNTU_BIONIC"
            PKG_SUFFIX="deb"
            ;;
        "centos"*)
            IMAGE_VER_FILE="VPP_DEVICE_IMAGE_CENTOS"
            VPP_VER_FILE="VPP_STABLE_VER_CENTOS"
            PKG_SUFFIX="rpm"
            ;;
        *)
            die "Unable to identify distro or os from ${os_id}"
            ;;
    esac

    arch=$(uname -m) || {
        die "Get CPU architecture failed."
    }

    case "${arch}" in
        "aarch64")
            IMAGE_VER_FILE="${IMAGE_VER_FILE}_ARM"
            ;;
        *)
            ;;
    esac
}


function select_tags () {

    # Variables read:
    # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
    # - TEST_CODE - String affecting test selection, usually jenkins job name.
    # - TEST_TAG_STRING - String selecting tags, from gerrit comment.
    #   Can be unset.
    # - TOPOLOGIES_DIR - Path to existing directory with available tpologies.
    # - BASH_FUNCTION_DIR - Directory with input files to process.
    # Variables set:
    # - TAGS - Array of processed tag boolean expressions.

    set -exuo pipefail

    # NIC SELECTION
    start_pattern='^  TG:'
    end_pattern='^ \? \?[A-Za-z0-9]\+:'
    # Remove the TG section from topology file
    sed_command="/${start_pattern}/,/${end_pattern}/d"
    # All topologies DUT NICs
    available=$(sed "${sed_command}" "${TOPOLOGIES_DIR}"/* \
                | grep -hoP "model: \K.*" | sort -u)
    # Selected topology DUT NICs
    reserved=$(sed "${sed_command}" "${WORKING_TOPOLOGY}" \
               | grep -hoP "model: \K.*" | sort -u)
    # All topologies DUT NICs - Selected topology DUT NICs
    exclude_nics=($(comm -13 <(echo "${reserved}") <(echo "${available}"))) || {
        die "Computation of excluded NICs failed."
    }

    # Select default NIC tag.
    case "${TEST_CODE}" in
        *"3n-dnv"* | *"2n-dnv"*)
            default_nic="nic_intel-x553"
            ;;
        *"3n-tsh"*)
            default_nic="nic_intel-x520-da2"
            ;;
        *"3n-skx"* | *"2n-skx"* | *"2n-clx"*)
            default_nic="nic_intel-xxv710"
            ;;
        *"3n-hsw"* | *"mrr-daily-master")
            default_nic="nic_intel-xl710"
            ;;
        *)
            default_nic="nic_intel-x710"
            ;;
    esac

    sed_nic_sub_cmd="sed s/\${default_nic}/${default_nic}/"
    # Tag file directory shorthand.
    tfd="${BASH_FUNCTION_DIR}"
    case "${TEST_CODE}" in
        # Select specific performance tests based on jenkins job type variable.
        *"ndrpdr-weekly"* )
            readarray -t test_tag_array < "${tfd}/mlr-weekly.txt" || die
            ;;
        *"mrr-daily"* )
            readarray -t test_tag_array <<< $(${sed_nic_sub_cmd} \
                ${tfd}/mrr-daily-${FLAVOR}.txt) || die
            ;;
        *"mrr-weekly"* )
            readarray -t test_tag_array <<< $(${sed_nic_sub_cmd} \
                ${tfd}/mrr-weekly.txt) || die
            ;;
        * )
            if [[ -z "${TEST_TAG_STRING-}" ]]; then
                # If nothing is specified, we will run pre-selected tests by
                # following tags.
                test_tag_array=("mrrAND${default_nic}AND1cAND64bANDip4base"
                                "mrrAND${default_nic}AND1cAND78bANDip6base"
                                "mrrAND${default_nic}AND1cAND64bANDl2bdbase"
                                "mrrAND${default_nic}AND1cAND64bANDl2xcbase"
                                "!dot1q" "!drv_avf")
            else
                # If trigger contains tags, split them into array.
                test_tag_array=(${TEST_TAG_STRING//:/ })
            fi
            ;;
    esac

    # Blacklisting certain tags per topology.
    #
    # Reasons for blacklisting:
    # - ipsechw - Blacklisted on testbeds without crypto hardware accelerator.
    # TODO: Add missing reasons here (if general) or where used (if specific).
    case "${TEST_CODE}" in
        *"2n-skx"*)
            test_tag_array+=("!ipsechw")
            ;;
        *"3n-skx"*)
            test_tag_array+=("!ipsechw")
            # Not enough nic_intel-xxv710 to support double link tests.
            test_tag_array+=("!3_node_double_link_topoANDnic_intel-xxv710")
            ;;
        *"2n-clx"*)
            test_tag_array+=("!ipsechw")
            ;;
        *"2n-dnv"*)
            test_tag_array+=("!ipsechw")
            test_tag_array+=("!memif")
            test_tag_array+=("!srv6_proxy")
            test_tag_array+=("!vhost")
            test_tag_array+=("!vts")
            test_tag_array+=("!drv_avf")
            ;;
        *"3n-dnv"*)
            test_tag_array+=("!memif")
            test_tag_array+=("!srv6_proxy")
            test_tag_array+=("!vhost")
            test_tag_array+=("!vts")
            test_tag_array+=("!drv_avf")
            ;;
        *"3n-tsh"*)
            # 3n-tsh only has x520 NICs which don't work with AVF
            test_tag_array+=("!drv_avf")
            test_tag_array+=("!ipsechw")
            ;;
        *"3n-hsw"*)
            # TODO: Introduce NOIOMMU version of AVF tests.
            # TODO: Make (both) AVF tests work on Haswell,
            # or document why (some of) it is not possible.
            # https://github.com/FDio/vpp/blob/master/src/plugins/avf/README.md
            test_tag_array+=("!drv_avf")
            # All cards have access to QAT. But only one card (xl710)
            # resides in same NUMA as QAT. Other cards must go over QPI
            # which we do not want to even run.
            test_tag_array+=("!ipsechwNOTnic_intel-xl710")
            ;;
        *)
            # Default to 3n-hsw due to compatibility.
            test_tag_array+=("!drv_avf")
            test_tag_array+=("!ipsechwNOTnic_intel-xl710")
            ;;
    esac

    # We will add excluded NICs.
    test_tag_array+=("${exclude_nics[@]/#/!NIC_}")

    TAGS=()

    # We will prefix with perftest to prevent running other tests
    # (e.g. Functional).
    prefix="perftestAND"
    set +x
    if [[ "${TEST_CODE}" == "vpp-"* ]]; then
        # Automatic prefixing for VPP jobs to limit the NIC used and
        # traffic evaluation to MRR.
        if [[ "${TEST_TAG_STRING-}" == *"nic_"* ]]; then
            prefix="${prefix}mrrAND"
        else
            prefix="${prefix}mrrAND${default_nic}AND"
        fi
    fi
    for tag in "${test_tag_array[@]}"; do
        if [[ "${tag}" == "!"* ]]; then
            # Exclude tags are not prefixed.
            TAGS+=("${tag}")
        elif [[ "${tag}" == " "* || "${tag}" == *"perftest"* ]]; then
            # Badly formed tag expressions can trigger way too much tests.
            set -x
            warn "The following tag expression hints at bad trigger: ${tag}"
            warn "Possible cause: Multiple triggers in a single comment."
            die "Aborting to avoid triggering too many tests."
        elif [[ "${tag}" != "" && "${tag}" != "#"* ]]; then
            # Empty and comment lines are skipped.
            # Other lines are normal tags, they are to be prefixed.
            TAGS+=("${prefix}${tag}")
        fi
    done
    set -x
}


function select_topology () {

    # Variables read:
    # - NODENESS - Node multiplicity of testbed, either "2n" or "3n".
    # - FLAVOR - Node flavor string, currently either "hsw" or "skx".
    # - CSIT_DIR - Path to existing root of local CSIT git repository.
    # - TOPOLOGIES_DIR - Path to existing directory with available topologies.
    # Variables set:
    # - TOPOLOGIES - Array of paths to suitable topology yaml files.
    # - TOPOLOGIES_TAGS - Tag expression selecting tests for the topology.
    # Functions called:
    # - die - Print to stderr and exit.

    set -exuo pipefail

    case_text="${NODENESS}_${FLAVOR}"
    case "${case_text}" in
        # TODO: Move tags to "# Blacklisting certain tags per topology" section.
        # TODO: Double link availability depends on NIC used.
        "1n_vbox")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*vpp_device*.template )
            TOPOLOGIES_TAGS="2_node_single_link_topo"
            ;;
        "1n_skx" | "1n_tx2")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*vpp_device*.template )
            TOPOLOGIES_TAGS="2_node_single_link_topo"
            ;;
        "2n_skx")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*2n_skx*.yaml )
            TOPOLOGIES_TAGS="2_node_*_link_topo"
            ;;
        "3n_skx")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*3n_skx*.yaml )
            TOPOLOGIES_TAGS="3_node_*_link_topo"
            ;;
        "2n_clx")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*2n_clx*.yaml )
            TOPOLOGIES_TAGS="2_node_*_link_topo"
            ;;
        "2n_dnv")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*2n_dnv*.yaml )
            TOPOLOGIES_TAGS="2_node_single_link_topo"
            ;;
        "3n_dnv")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*3n_dnv*.yaml )
            TOPOLOGIES_TAGS="3_node_single_link_topo"
            ;;
        "3n_hsw")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*3n_hsw*.yaml )
            TOPOLOGIES_TAGS="3_node_single_link_topo"
            ;;
        "3n_tsh")
            TOPOLOGIES=( "${TOPOLOGIES_DIR}"/*3n_tsh*.yaml )
            TOPOLOGIES_TAGS="3_node_single_link_topo"
            ;;
        *)
            # No falling back to 3n_hsw default, that should have been done
            # by the function which has set NODENESS and FLAVOR.
            die "Unknown specification: ${case_text}"
    esac

    if [[ -z "${TOPOLOGIES-}" ]]; then
        die "No applicable topology found!"
    fi
}


function select_vpp_device_tags () {

    # Variables read:
    # - TEST_CODE - String affecting test selection, usually jenkins job name.
    # - TEST_TAG_STRING - String selecting tags, from gerrit comment.
    #   Can be unset.
    # Variables set:
    # - TAGS - Array of processed tag boolean expressions.

    set -exuo pipefail

    case "${TEST_CODE}" in
        # Select specific device tests based on jenkins job type variable.
        * )
            if [[ -z "${TEST_TAG_STRING-}" ]]; then
                # If nothing is specified, we will run pre-selected tests by
                # following tags. Items of array will be concatenated by OR
                # in Robot Framework.
                test_tag_array=()
            else
                # If trigger contains tags, split them into array.
                test_tag_array=(${TEST_TAG_STRING//:/ })
            fi
            ;;
    esac

    # Blacklisting certain tags per topology.
    #
    # Reasons for blacklisting:
    # - avf - AVF is not possible to run on enic driver of VirtualBox.
    # - vhost - VirtualBox does not support nesting virtualization on Intel CPU.
    case "${TEST_CODE}" in
        *"1n-vbox"*)
            test_tag_array+=("!avf")
            test_tag_array+=("!vhost")
            ;;
        *)
            ;;
    esac

    TAGS=()

    # We will prefix with devicetest to prevent running other tests
    # (e.g. Functional).
    prefix="devicetestAND"
    if [[ "${TEST_CODE}" == "vpp-"* ]]; then
        # Automatic prefixing for VPP jobs to limit testing.
        prefix="${prefix}"
    fi
    for tag in "${test_tag_array[@]}"; do
        if [[ ${tag} == "!"* ]]; then
            # Exclude tags are not prefixed.
            TAGS+=("${tag}")
        else
            TAGS+=("${prefix}${tag}")
        fi
    done
}

function untrap_and_unreserve_testbed () {

    # Use this as a trap function to ensure testbed does not remain reserved.
    # Perhaps call directly before script exit, to free testbed for other jobs.
    # This function is smart enough to avoid multiple unreservations (so safe).
    # Topo cleanup is executed (call it best practice), ignoring failures.
    #
    # Hardcoded values:
    # - default message to die with if testbed might remain reserved.
    # Arguments:
    # - ${1} - Message to die with if unreservation fails. Default hardcoded.
    # Variables read (by inner function):
    # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
    # - PYTHON_SCRIPTS_DIR - Path to directory holding Python scripts.
    # Variables written:
    # - WORKING_TOPOLOGY - Set to empty string on successful unreservation.
    # Trap unregistered:
    # - EXIT - Failure to untrap is reported, but ignored otherwise.
    # Functions called:
    # - die - Print to stderr and exit.
    # - ansible_hosts - Perform an action using ansible, see ansible.sh

    set -xo pipefail
    set +eu  # We do not want to exit early in a "teardown" function.
    trap - EXIT || echo "Trap deactivation failed, continuing anyway."
    wt="${WORKING_TOPOLOGY}"  # Just to avoid too long lines.
    if [[ -z "${wt-}" ]]; then
        set -eu
        warn "Testbed looks unreserved already. Trap removal failed before?"
    else
        ansible_hosts "cleanup" || true
        python3 "${PYTHON_SCRIPTS_DIR}/topo_reservation.py" -c -t "${wt}" || {
            die "${1:-FAILED TO UNRESERVE, FIX MANUALLY.}" 2
        }
        WORKING_TOPOLOGY=""
        set -eu
    fi
}


function warn () {

    # Print the message to standard error.
    #
    # Arguments:
    # - ${@} - The text of the message.

    set -exuo pipefail

    echo "$@" >&2
}