docs: add VPP Container Testbench example and lab
[vpp.git] / docs / usecases / vpp_testbench / src / vpp_testbench_helpers.sh
diff --git a/docs/usecases/vpp_testbench/src/vpp_testbench_helpers.sh b/docs/usecases/vpp_testbench/src/vpp_testbench_helpers.sh
new file mode 100755 (executable)
index 0000000..7dfb646
--- /dev/null
@@ -0,0 +1,273 @@
+#!/bin/bash
+################################################################################
+# @brief:       Helper functions for the VPP testbench project.
+#               NOTE: functions prefixed with "host_only" are functions
+#               intended to be executed on the host OS, **outside** of the
+#               Docker containers. These are typically functions for bring-up
+#               (i.e. creating the Docker networks, launching/terminating the
+#               Docker containers, etc.). If a function is not prefixed with
+#               "host_only", assume that the function/value/etc. is intended
+#               for use within the Docker containers. We could maybe re-factor
+#               this in the future so "host_only" functions live in a separate
+#               file.
+# @author:      Matthew Giassa <mgiassa@cisco.com>
+# @copyright:   (C) Cisco 2021.
+################################################################################
+
+# Meant to be sourced, not executed directly.
+if [ "${BASH_SOURCE[0]}" -ef "$0" ]; then
+    echo "This script is intended to be sourced, not run. Aborting."
+    false
+    exit
+fi
+
+#------------------------------------------------------------------------------#
+# For tests using the Linux kernel network stack.
+#------------------------------------------------------------------------------#
+# Health check probe port for all containers.
+export DOCKER_HEALTH_PROBE_PORT="8123"
+# Docker bridge network settings.
+export CLIENT_BRIDGE_IP_DOCKER="169.254.0.1"
+export SERVER_BRIDGE_IP_DOCKER="169.254.0.2"
+export BRIDGE_NET_DOCKER="169.254.0.0/24"
+export BRIDGE_GW_DOCKER="169.254.0.254"
+# Overlay IP addresses.
+export CLIENT_VXLAN_IP_LINUX="169.254.10.1"
+export SERVER_VXLAN_IP_LINUX="169.254.10.2"
+export MASK_VXLAN_LINUX="24"
+export VXLAN_ID_LINUX="42"
+# IANA (rather than Linux legacy port value).
+export VXLAN_PORT="4789"
+
+# Docker network we use to bridge containers.
+export DOCKER_NET="vpp-testbench-net"
+# Docker container names for client and server (runtime aliases).
+export DOCKER_CLIENT_HOST="vpp-testbench-client"
+export DOCKER_SERVER_HOST="vpp-testbench-server"
+# Some related variables have to be computed at the last second, so they
+# are not all defined up-front.
+export CLIENT_VPP_NETNS_DST="/var/run/netns/${DOCKER_CLIENT_HOST}"
+export SERVER_VPP_NETNS_DST="/var/run/netns/${DOCKER_SERVER_HOST}"
+
+# VPP options.
+# These can be arbitrarily named.
+export CLIENT_VPP_HOST_IF="vpp1"
+export SERVER_VPP_HOST_IF="vpp2"
+# Putting VPP interfaces on separate subnet from Linux-stack i/f.
+export CLIENT_VPP_MEMIF_IP="169.254.11.1"
+export SERVER_VPP_MEMIF_IP="169.254.11.2"
+export VPP_MEMIF_NM="24"
+export CLIENT_VPP_TAP_IP_MEMIF="169.254.12.1"
+export SERVER_VPP_TAP_IP_MEMIF="169.254.12.2"
+export VPP_TAP_NM="24"
+# Bridge domain ID (for VPP tap + VXLAN interfaces). Arbitrary.
+export VPP_BRIDGE_DOMAIN_TAP="1000"
+
+# VPP socket path. Make it one level "deeper" than the "/run/vpp" that is used
+# by default, so our containers don't accidentally connect to an instance of
+# VPP running on the host OS (i.e. "/run/vpp/vpp.sock"), and hang the system.
+export VPP_SOCK_PATH="/run/vpp/containers"
+
+#------------------------------------------------------------------------------#
+# @brief:       Converts an integer value representation of a VXLAN ID to a
+#               VXLAN IPv4 multicast address (string represenation). This
+#               effectively sets the first octet to "239" and the remaining 3x
+#               octets to the IP-address equivalent of a 24-bit value.
+#               Assumes that it's never supplied an input greater than what a
+#               24-bit unsigned integer can hold.
+function vxlan_id_to_mc_ip()
+{
+    if [ $# -ne 1 ]; then
+        echo "Sanity failure."
+        false
+        exit
+    fi
+
+    local id="${1}"
+    local a b c d ret
+    a="239"
+    b="$(((id>>16) & 0xff))"
+    c="$(((id>>8)  & 0xff))"
+    d="$(((id)     & 0xff))"
+    ret="${a}.${b}.${c}.${d}"
+
+    echo "${ret}"
+    true
+}
+# Multicast address for VXLAN. Treat the lower three octets as the 24-bit
+# representation of the VXLAN ID for ease-of-use (use-case specific, not
+# necessarily an established rule/protocol).
+MC_VXLAN_ADDR_LINUX="$(vxlan_id_to_mc_ip ${VXLAN_ID_LINUX})"
+export MC_VXLAN_ADDR_LINUX
+
+#------------------------------------------------------------------------------#
+# @brief:       Get'er function (so makefile can re-use common values from this
+#               script, and propagate them down to the Docker build operations
+#               and logic within the Dockerfile; "DRY").
+function host_only_get_docker_health_probe_port()
+{
+    echo "${DOCKER_HEALTH_PROBE_PORT}"
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Creates the Docker bridge network used to connect the
+#               client and server testbench containers.
+function host_only_create_docker_networks()
+{
+    # Create network (bridge for VXLAN). Don't touch 172.16/12 subnet, as
+    # Docker uses it by default for its own overlay functionality.
+    docker network create \
+        --driver bridge \
+        --subnet=${BRIDGE_NET_DOCKER} \
+        --gateway=${BRIDGE_GW_DOCKER} \
+        "${DOCKER_NET}"
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Destroys the Docker bridge network for connecting the
+#               containers.
+function host_only_destroy_docker_networks()
+{
+    docker network rm "${DOCKER_NET}" || true
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Bringup/dependency helper for VPP.
+function host_only_create_vpp_deps()
+{
+    # Create area for VPP sockets and mount points, if it doesn't already
+    # exist. Our containers need access to this path so they can see each
+    # others' respective sockets so we can bind them together via memif.
+    sudo mkdir -p "${VPP_SOCK_PATH}"
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Launches the testbench client container.
+function host_only_run_testbench_client_container()
+{
+    # Sanity check.
+    if [ $# -ne 1 ]; then
+        echo "Sanity failure."
+        false
+        exit
+    fi
+
+    # Launch container. Mount the local PWD into the container too (so we can
+    # backup results).
+    local image_name="${1}"
+    docker run -d --rm \
+        --cap-add=NET_ADMIN \
+        --cap-add=SYS_NICE \
+        --cap-add=SYS_PTRACE \
+        --device=/dev/net/tun:/dev/net/tun \
+        --device=/dev/vfio/vfio:/dev/vfio/vfio \
+        --device=/dev/vhost-net:/dev/vhost-net \
+        --name "${DOCKER_CLIENT_HOST}" \
+        --volume="$(pwd):/work:rw" \
+        --volume="${VPP_SOCK_PATH}:/run/vpp:rw" \
+        --network name="${DOCKER_NET},ip=${CLIENT_BRIDGE_IP_DOCKER}" \
+        --workdir=/work \
+        "${image_name}"
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Launches the testbench server container.
+function host_only_run_testbench_server_container()
+{
+    # Sanity check.
+    if [ $# -ne 1 ]; then
+        echo "Sanity failure."
+        false
+        exit
+    fi
+
+    # Launch container. Mount the local PWD into the container too (so we can
+    # backup results).
+    local image_name="${1}"
+    docker run -d --rm \
+        --cap-add=NET_ADMIN \
+        --cap-add=SYS_NICE \
+        --cap-add=SYS_PTRACE \
+        --device=/dev/net/tun:/dev/net/tun \
+        --device=/dev/vfio/vfio:/dev/vfio/vfio \
+        --device=/dev/vhost-net:/dev/vhost-net \
+        --name "${DOCKER_SERVER_HOST}" \
+        --volume="${VPP_SOCK_PATH}:/run/vpp:rw" \
+        --network name="${DOCKER_NET},ip=${SERVER_BRIDGE_IP_DOCKER}" \
+        "${image_name}"
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Terminates the testbench client container.
+function host_only_kill_testbench_client_container()
+{
+    docker kill "${DOCKER_CLIENT_HOST}" || true
+    docker rm   "${DOCKER_CLIENT_HOST}" || true
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Terminates the testbench server container.
+function host_only_kill_testbench_server_container()
+{
+    docker kill "${DOCKER_SERVER_HOST}" || true
+    docker rm   "${DOCKER_SERVER_HOST}" || true
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Launches an interactive shell in the client container.
+function host_only_shell_client_container()
+{
+    docker exec -it "${DOCKER_CLIENT_HOST}" bash --init-file /entrypoint.sh
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Launches an interactive shell in the server container.
+function host_only_shell_server_container()
+{
+    docker exec -it "${DOCKER_SERVER_HOST}" bash --init-file /entrypoint.sh
+}
+
+#------------------------------------------------------------------------------#
+# @brief:       Determines the network namespace or "netns" associated with a
+#               running Docker container, and then creates a network interface
+#               in the default/host netns, and moves it into the netns
+#               associated with the container.
+function host_only_move_host_interfaces_into_container()
+{
+    # NOTE: this is only necessary if we want to create Linux network
+    # interfaces while working in the default namespace, and then move them
+    # into container network namespaces.
+    # In earlier versions of this code, we did such an operation, but now we
+    # just create the interfaces inside the containers themselves (requires
+    # CAP_NET_ADMIN, or privileged containers, which we avoid). This is left
+    # here as it's occasionally useful for debug purposes (or might become a
+    # mini-lab itself).
+
+    # Make sure netns path exists.
+    sudo mkdir -p /var/run/netns
+
+    # Mount container network namespaces so that they are accessible via "ip
+    # netns". Ignore "START_OF_SCRIPT": just used to make
+    # linter-compliant text indentation look nicer.
+    DOCKER_CLIENT_PID=$(docker inspect -f '{{.State.Pid}}' ${DOCKER_CLIENT_HOST})
+    DOCKER_SERVER_PID=$(docker inspect -f '{{.State.Pid}}' ${DOCKER_SERVER_HOST})
+    CLIENT_VPP_NETNS_SRC=/proc/${DOCKER_CLIENT_PID}/ns/net
+    SERVER_VPP_NETNS_SRC=/proc/${DOCKER_SERVER_PID}/ns/net
+    sudo ln -sfT "${CLIENT_VPP_NETNS_SRC}" "${CLIENT_VPP_NETNS_DST}"
+    sudo ln -sfT "${SERVER_VPP_NETNS_SRC}" "${SERVER_VPP_NETNS_DST}"
+
+    # Move these interfaces into the namespaces of the containers and assign an
+    # IPv4 address to them.
+    sudo ip link set dev "${CLIENT_VPP_HOST_IF}" netns "${DOCKER_CLIENT_NETNS}"
+    sudo ip link set dev "${SERVER_VPP_HOST_IF}" netns "${DOCKER_SERVER_NETNS}"
+    docker exec ${DOCKER_CLIENT_HOST} ip a
+    docker exec ${DOCKER_SERVER_HOST} ip a
+
+    # Bring up the links and assign IP addresses. This must be done
+    # **after** moving the interfaces to a new netns, as we might have a
+    # hypothetical use case where we assign the same IP to multiple
+    # interfaces, which would be a problem. This collision issue isn't a
+    # problem though if the interfaces are in separate network namespaces
+    # though.
+}
+