KAFKA-13041: Enable connecting VS Code remote debugger (#10915)

The changes in this PR enable connecting VS Code's remote debugger to a system test running locally with ducker-ak.
Changes include:
- added zip_safe=False to setup.py - this enables installing kafkatest module together with source code when running `python setup.py  develop/install`.
- install [debugpy](https://github.com/microsoft/debugpy) on ducker nodes
- expose 5678 (default debugpy port) on ducker01 node - ducker01 is the one that actually executes tests, so that's where you'd connect to.
- added `-d|--debug` option to `ducker-ak test` command - if used, tests will run via `python3.7 -m debugpy` command, which would listen on 5678 and pause until debugger is connected.
- changed the logic of the `ducker-ak test` command so that ducktape args are collected separately after `--` - otherwise any argument we add to the `test` command in the future might potentially
shadow a similar ducktape argument. 
	- we don't really check that `ducktape_args` are args while `test_name_args` are actual test names, so the difference between the two is minimal actually - most importantly we do check that `test_name_args` is not empty, but we are ok if `ducktape_args` is.

Reviewers: Ewen Cheslack-Postava <ewen@confluent.io>, Manikumar Reddy <manikumar.reddy@gmail.com>
This commit is contained in:
Stanislav Vodetskyi 2021-07-08 08:05:14 -07:00 committed by GitHub
parent 2b8aff58b5
commit 058589b03d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 12 deletions

View File

@ -51,6 +51,40 @@ bash tests/docker/ducker-ak up -j 'openjdk:11'; tests/docker/run_tests.sh
```
REBUILD="t" bash tests/docker/run_tests.sh
```
* Debug tests in VS Code:
- Run test with `--debug` flag (can be before or after file name):
```
tests/docker/ducker-ak up; tests/docker/ducker-ak test tests/kafkatest/tests/core/security_test.py --debug
```
- Test will run in debug mode and wait for a debugger to attach.
- Launch VS Code debugger with `"attach"` request - here's an example:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Attach to Ducker",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"justMyCode": false,
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}
]
}
```
- To pass `--debug` flag to ducktape itself, use `--`:
```
tests/docker/ducker-ak test tests/kafkatest/tests/core/security_test.py --debug -- --debug
```
* Notes
- The scripts to run tests creates and destroys docker network named *knw*.

View File

@ -35,7 +35,7 @@ LABEL ducker.creator=$ducker_creator
# we have to install git since it is included in openjdk:8 but not openjdk:11
RUN apt update && apt install -y sudo git netcat iptables rsync unzip wget curl jq coreutils openssh-server net-tools vim python3-pip python3-dev libffi-dev libssl-dev cmake pkg-config libfuse-dev iperf traceroute && apt-get -y clean
RUN python3 -m pip install -U pip==21.1.1;
RUN pip3 install --upgrade cffi virtualenv pyasn1 boto3 pycrypto pywinrm ipaddress enum34 && pip3 install --upgrade ducktape==0.8.1
RUN pip3 install --upgrade cffi virtualenv pyasn1 boto3 pycrypto pywinrm ipaddress enum34 debugpy && pip3 install --upgrade ducktape==0.8.1
# Set up ssh
COPY ./ssh-config /root/.ssh/config

View File

@ -47,6 +47,9 @@ default_jdk="openjdk:8"
# The default ducker-ak image name.
default_image_name="ducker-ak"
# Port to listen on when debugging
debugpy_port=5678
# Display a usage message on the terminal and exit.
#
# $1: The exit status to use
@ -74,11 +77,20 @@ up [-n|--num-nodes NUM_NODES] [-f|--force] [docker-image]
on the host. The argument can be a single port (like 5005), a port range like (5005-5009)
or a combination of port/port-range separated by comma (like 2181,9092 or 2181,5005-5008).
By default no port is exposed. See README.md for more detail on this option.
Note that port 5678 will be automatically exposed for ducker01 node and will be mapped to 5678
on your local machine to enable debugging in VS Code.
test [test-name(s)]
test [-d|--debug] [test-name(s)] [-- [ducktape args]]
Run a test or set of tests inside the currently active Ducker nodes.
For example, to run the system test produce_bench_test, you would run:
./tests/docker/ducker-ak test ./tests/kafkatest/tests/core/produce_bench_test.py
If --debug is passed, the tests will wait for remote VS Code debugger to connect on port 5678:
./tests/docker/ducker-ak test --debug ./tests/kafkatest/tests/core/produce_bench_test.py
To pass arguments to underlying ducktape invocation, pass them after `--`, e.g.:
./tests/docker/ducker-ak test ./tests/kafkatest/tests/core/produce_bench_test.py -- --test-runner-timeout 1800000
ssh [node-name|user-name@node-name] [command]
Log in to a running ducker container. If node-name is not given, it prints
@ -246,6 +258,7 @@ docker_run() {
local node=${1}
local image_name=${2}
local ports_option=${3}
local port_mapping=${4}
local expose_ports=""
if [[ -n ${ports_option} ]]; then
@ -254,6 +267,9 @@ docker_run() {
expose_ports="${expose_ports} --expose ${expose_port}"
done
fi
if [[ -n ${port_mapping} ]]; then
expose_ports="${expose_ports} -p ${port_mapping}:${port_mapping}"
fi
# Invoke docker-run. We need privileged mode to be able to run iptables
# and mount FUSE filesystems inside the container. We also need it to
@ -340,7 +356,8 @@ attempting to start new ones."
if [[ -n "${custom_ducktape}" ]]; then
setup_custom_ducktape "${custom_ducktape}" "${image_name}"
fi
for n in $(seq -f %02g 1 ${num_nodes}); do
docker_run ducker01 "${image_name}" "${expose_ports}" "${debugpy_port}"
for n in $(seq -f %02g 2 ${num_nodes}); do
local node="ducker${n}"
docker_run "${node}" "${image_name}" "${expose_ports}"
done
@ -425,23 +442,41 @@ ducker_test() {
require_commands docker
docker inspect ducker01 &>/dev/null || \
die "ducker_test: the ducker01 instance appears to be down. Did you run 'ducker up'?"
[[ $# -lt 1 ]] && \
declare -a test_name_args=()
local debug=0
while [[ $# -ge 1 ]]; do
case "${1}" in
-d|--debug) debug=1; shift;;
--) shift; break;;
*) test_name_args+=("${1}"); shift;;
esac
done
local ducktape_args=${*}
[[ ${#test_name_args} -lt 1 ]] && \
die "ducker_test: you must supply at least one system test to run. Type --help for help."
local args=""
local kafka_test=0
for arg in "${@}"; do
local test_names=""
for test_name in ${test_name_args[*]}; do
local regex=".*\/kafkatest\/(.*)"
if [[ $arg =~ $regex ]]; then
if [[ $test_name =~ $regex ]]; then
local kpath=${BASH_REMATCH[1]}
args="${args} ./tests/kafkatest/${kpath}"
test_names="${test_names} ./tests/kafkatest/${kpath}"
else
args="${args} ${arg}"
test_names="${test_names} ${test_name}"
fi
done
must_pushd "${kafka_dir}"
(test -f ./gradlew || gradle) && ./gradlew systemTestLibs
must_popd
cmd="cd /opt/kafka-dev && ducktape --cluster-file /opt/kafka-dev/tests/docker/build/cluster.json $args"
if [[ "${debug}" -eq 1 ]]; then
local ducktape_cmd="python3.7 -m debugpy --listen 0.0.0.0:${debugpy_port} --wait-for-client /usr/local/bin/ducktape"
else
local ducktape_cmd="ducktape"
fi
cmd="cd /opt/kafka-dev && ${ducktape_cmd} --cluster-file /opt/kafka-dev/tests/docker/build/cluster.json $test_names $ducktape_args"
echo "docker exec ducker01 bash -c \"${cmd}\""
exec docker exec --user=ducker ducker01 bash -c "${cmd}"
}

View File

@ -32,4 +32,7 @@ fi
if ${SCRIPT_DIR}/ducker-ak ssh | grep -q '(none)'; then
${SCRIPT_DIR}/ducker-ak up -n "${KAFKA_NUM_CONTAINERS}" || die "ducker-ak up failed"
fi
[[ -n ${_DUCKTAPE_OPTIONS} ]] && _DUCKTAPE_OPTIONS="-- ${_DUCKTAPE_OPTIONS}"
${SCRIPT_DIR}/ducker-ak test ${TC_PATHS} ${_DUCKTAPE_OPTIONS} || die "ducker-ak test failed"

View File

@ -51,7 +51,8 @@ setup(name="kafkatest",
license="apache2.0",
packages=find_packages(),
include_package_data=True,
install_requires=["ducktape==0.8.1", "requests==2.24.0"],
install_requires=["ducktape==0.8.8", "requests==2.24.0"],
tests_require=["pytest", "mock"],
cmdclass={'test': PyTest},
zip_safe=False
)