Skip to content

Commit acd05f3

Browse files
klarosezuercher
authored andcommitted
build: add scripts for privileged docker test execution (envoyproxy#5268)
We want to run some integration tests which require various privileges. I have added some scripts which wrap a bazel test target in another script which sets up such a remote environment. The general approach is similar to bazel-test-gdb, except no intermediate script is generated. The script works fairly well. See the documentation portion of the PR for some examples. There are a few gotchas with it, however, that we should probably resolve over time: It won't work with tests that depend on anything outside the test itself -- e.g. input files. If compiling locally, there may be some challenges invoking other binaries in the target container, since we end up overwriting some runtime libraries that the container's gdb may need. There is no strategy just yet to run a privileged test in gdb... I suspect that sudo bazel-test-gdb will have to be sufficient. Example: tools/bazel-test-docker.sh //test/integration:integration_test --jobs=4 -c dbg Risk Level: Low. This is not used by anything, and it will only ever be used explicitly, so it should be tested substantially then. Testing: Tested locally with various integration tests, as well as in circle with my integration test which requires privileges: https://circleci.com/gh/klarose/envoy-circle/11. This commit was based off my hacked up fork of envoy, which only ran the one test: https://github.com/klarose/envoy-circle Docs Changes: I added a description of the script to bazel/README.md. Release Notes: Only affects tooling, so I don't think one is required. Fixes Issue envoyproxy#5246 Signed-off-by: Kyle Larose <[email protected]>
1 parent db3a93a commit acd05f3

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

bazel/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,50 @@ tools/bazel-test-gdb //test/common/http:async_client_impl_test -c dbg
200200
Without the `-c dbg` Bazel option at the end of the command line the test
201201
binaries will not include debugging symbols and GDB will not be very useful.
202202

203+
# Running Bazel tests requiring privileges
204+
205+
Some tests may require privileges (e.g. CAP_NET_ADMIN) in order to execute. One option is to run
206+
them with elevated privileges, e.g. `sudo test`. However, that may not always be possible,
207+
particularly if the test needs to run in a CI pipeline. `tools/bazel-test-docker.sh` may be used in
208+
such situations to run the tests in a privileged docker container.
209+
210+
The script works by wrapping the test execution in the current repository's circle ci build
211+
container, then executing it either locally or on a remote docker container. In both cases, the
212+
container runs with the `--privileged` flag, allowing it to execute operations which would otherwise
213+
be restricted.
214+
215+
The command line format is:
216+
`tools/bazel-test-docker.sh <bazel-test-target> [optional-flags-to-bazel]`
217+
218+
The script uses two optional environment variables to control its behaviour:
219+
220+
* `RUN_REMOTE=<yes|no>`: chooses whether to run on a remote docker server.
221+
* `LOCAL_MOUNT=<yes|no>`: copy/mount local libraries onto the docker container.
222+
223+
Use `RUN_REMOTE=yes` when you don't want to run against your local docker instance. Note that you
224+
will need to override a few environment variables to set up the remote docker. The list of variables
225+
can be found in the [Documentation](https://docs.docker.com/engine/reference/commandline/cli/).
226+
227+
Use `LOCAL_MOUNT=yes` when you are not building with the envoy build container. This will ensure
228+
that the libraries against which the tests dynmically link will be available and of the correct
229+
version.
230+
231+
## Examples
232+
233+
Running the http integration test in a privileged container:
234+
235+
```bash
236+
tools/bazel-test-docker.sh //test/integration:integration_test --jobs=4 -c dbg
237+
```
238+
239+
Running the http integration test compiled locally against a privileged remote container:
240+
241+
```bash
242+
setup_remote_docker_variables
243+
RUN_REMOTE=yes MOUNT_LOCAL=yes tools/bazel-test-docker.sh //test/integration:integration_test \
244+
--jobs=4 -c dbg
245+
```
246+
203247
# Additional Envoy build and test options
204248

205249
In general, there are 3 [compilation

tools/bazel-test-docker.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/bash
2+
3+
# Run a single Bazel test target under a privileged docker. Usage:
4+
#
5+
# tools/bazel-test-docker //test/foo:bar --some_other --bazel_args
6+
# By default, this will run in a local docker container, mounting the local shared library paths
7+
# into the counter. To run remotely, use RUN_REMOTE=yes. If the test was compiled with a different
8+
# toolchain than the envoy-build container, passing in LOCAL_MOUNT=yes will force it to copy the
9+
# local libraries into the container.
10+
11+
if [[ -z "$1" ]]; then
12+
echo "First argument to $0 must be a [@repo]//test/foo:bar label identifying a set of test to run"
13+
echo "\"$1\" does not match this pattern"
14+
exit 1
15+
fi
16+
17+
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
18+
[[ -z "${BAZEL}" ]] && BAZEL=bazel
19+
[[ -z "${DOCKER}" ]] && DOCKER=docker
20+
21+
if [[ -z "${RUN_REMOTE}" ]]; then
22+
LOCAL_MOUNT="${LOCAL_MOUNT:-yes}"
23+
RUN_REMOTE=no
24+
else
25+
LOCAL_MOUNT="${LOCAL_MOUNT:-no}"
26+
RUN_REMOTE=yes
27+
fi
28+
29+
# Pass through the docker environment
30+
DOCKER_ENV=$(mktemp -t docker_env.XXXXXX)
31+
function cleanup() {
32+
rm -f "${DOCKER_ENV}"
33+
}
34+
35+
trap cleanup EXIT
36+
cat > "${DOCKER_ENV}" <<EOF
37+
#!/bin/bash
38+
export DOCKER_CERT_PATH="${DOCKER_CERT_PATH}"
39+
export DOCKER_HOST="${DOCKER_HOST}"
40+
export DOCKER_MACHINE_NAME="${DOCKER_MACHINE_NAME}"
41+
export DOCKER_TLS_VERIFY="${DOCKER_TLS_VERIFY}"
42+
export NO_PROXY="${NO_PROXY}"
43+
EOF
44+
45+
. ./ci/envoy_build_sha.sh
46+
IMAGE=envoyproxy/envoy-build:${ENVOY_BUILD_SHA}
47+
48+
# Note docker_wrapper.sh is tightly coupled to the order of arguments here due to where the test
49+
# name is passed in.
50+
"${BAZEL}" test "$@" --strategy=TestRunner=standalone --cache_test_results=no \
51+
--test_output=summary --run_under="${SCRIPT_DIR}/docker_wrapper.sh ${IMAGE} ${RUN_REMOTE} \
52+
${LOCAL_MOUNT} ${DOCKER_ENV}"

tools/docker_wrapper.sh

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/bin/bash
2+
#
3+
# Wraps a test invocation in docker.
4+
5+
set -e
6+
IMAGE=$1
7+
RUN_REMOTE=$2
8+
LOCAL_MOUNT=$3
9+
DOCKER_ENV=$4
10+
TEST_PATH=$(realpath "$5")
11+
shift 5
12+
13+
if [ "${RUN_REMOTE}" == "yes" ]; then
14+
echo "Using docker environment from ${DOCKER_ENV}:"
15+
cat "${DOCKER_ENV}"
16+
fi
17+
. "${DOCKER_ENV}"
18+
19+
CONTAINER_NAME="envoy-test-runner"
20+
ENVFILE=$(mktemp -t "bazel-test-env.XXXXXX")
21+
function cleanup() {
22+
rm -f "${ENVFILE}"
23+
if [ "${RUN_REMOTE}" == "yes" ]; then
24+
docker rm -f "${CONTAINER_NAME}" || true # We don't really care if it fails.
25+
fi
26+
}
27+
28+
trap cleanup EXIT
29+
30+
cat > ${ENVFILE} <<EOF
31+
TEST_WORKSPACE=/tmp/workspace
32+
TEST_SRCDIR=/tmp/src
33+
ENVOY_IP_TEST_VERSIONS=v4only
34+
EOF
35+
36+
CMDLINE="set -a && . /env && env && /test $@"
37+
LIB_PATHS="/lib/x86_64-linux-gnu/ /usr/lib/x86_64-linux-gnu/ /lib64/"
38+
39+
40+
if [ "${RUN_REMOTE}" != "yes" ]; then
41+
# We're running locally. If told to, mount the library directories locally.
42+
LIB_MOUNTS=""
43+
if [ "${LOCAL_MOUNT}" == "yes" ]
44+
then
45+
for path in $LIB_PATHS; do
46+
LIB_MOUNTS="${LIB_MOUNTS} -v ${path}:${path}:ro"
47+
done
48+
fi
49+
50+
# Note: we don't quote LIB_MOUNTS on purpose; we want it to expand.
51+
docker run --rm --privileged -v "${TEST_PATH}:/test" ${LIB_MOUNTS} -i -v "${ENVFILE}:/env" \
52+
"${IMAGE}" bash -c "${CMDLINE}"
53+
else
54+
# In this case, we need to create the container, then make new layers on top of it, since we
55+
# can't mount everything into it.
56+
docker create -t --privileged --name "${CONTAINER_NAME}" "${IMAGE}" \
57+
bash -c "${CMDLINE}"
58+
docker cp "$TEST_PATH" "${CONTAINER_NAME}:/test"
59+
docker cp "$ENVFILE" "${CONTAINER_NAME}:/env"
60+
61+
# If some local libraries are necessary, copy them over.
62+
if [ "${LOCAL_MOUNT}" == "yes" ]; then
63+
for path in ${LIB_PATHS}; do
64+
# $path. gives us a path ending it /. This means that we will copy the contents into the
65+
# destination directory, not overwrite the entire directory.
66+
docker cp -L "${path}." "${CONTAINER_NAME}:${path}"
67+
done
68+
fi
69+
70+
docker start -a "${CONTAINER_NAME}"
71+
fi

0 commit comments

Comments
 (0)