From 4a0dd383cf363ba7df105b87838435ef1cfa4fd7 Mon Sep 17 00:00:00 2001 From: Matthew Giassa Date: Fri, 19 Nov 2021 17:06:11 +0000 Subject: docs: add VPP Container Testbench example and lab Adding a "VPP container testbench" (pair of Docker containers plus helper scripts to test Linux and VPP interfaces). Will be part of a larger set of labs/exercises/tutorials. Putting this baseline setup up for review first to see if the community sees use/value in it. If so, additional exercises using the testbench will be added gradually. Type: improvement Signed-off-by: Matthew Giassa Change-Id: I582310f7355419e907d575f640482ca49cbb282f --- .../vpp_testbench/src/vpp_testbench_helpers.sh | 273 +++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100755 docs/usecases/vpp_testbench/src/vpp_testbench_helpers.sh (limited to '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 index 00000000000..7dfb646a1db --- /dev/null +++ b/docs/usecases/vpp_testbench/src/vpp_testbench_helpers.sh @@ -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 +# @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. +} + -- cgit 1.2.3-korg