@@ -58,6 +58,36 @@ default_kafka_mode="jvm"
58
58
# Port to listen on when debugging
59
59
debugpy_port=5678
60
60
61
+ # Host port to bind for debugpy on ducker01
62
+ debugpy_host_port=" ${DUCKER_DEBUGPY_PORT:- } "
63
+
64
+ # Support for running multiple ducker‑ak clusters on one machine.
65
+ prefix=" ${DUCKER_PREFIX:- } "
66
+
67
+ # the docker network name for this cluster.
68
+ net_name () {
69
+ if [[ -n " ${prefix} " ]]; then
70
+ echo " ${prefix} -ducknet"
71
+ else
72
+ echo " ducknet"
73
+ fi
74
+ }
75
+
76
+ # the container name prefix (e.g. ducker or <prefix>-ducker)
77
+ container_prefix () {
78
+ if [[ -n " ${prefix} " ]]; then
79
+ echo " ${prefix} -ducker"
80
+ else
81
+ echo " ducker"
82
+ fi
83
+ }
84
+
85
+ # Given an integer index, produce the full container name.
86
+ node_name () {
87
+ local idx=" ${1} "
88
+ printf " %s%02d" " $( container_prefix) " " ${idx} "
89
+ }
90
+
61
91
# Display a usage message on the terminal and exit.
62
92
#
63
93
# $1: The exit status to use
@@ -72,7 +102,7 @@ help|-h|--help
72
102
Display this help message
73
103
74
104
up [-n|--num-nodes NUM_NODES] [-f|--force] [docker-image]
75
- [-C|--custom-ducktape DIR] [-e|--expose-ports ports] [-j|--jdk JDK_VERSION] [--ipv6]
105
+ [-C|--custom-ducktape DIR] [-e|--expose-ports ports] [-j|--jdk JDK_VERSION] [--ipv6] [--prefix NAME] [--debug-port {PORT|auto}]
76
106
Bring up a cluster with the specified amount of nodes (defaults to ${default_num_nodes} ).
77
107
The docker image name defaults to ${default_image_name} . If --force is specified, we will
78
108
attempt to bring up an image even some parameters are not valid.
@@ -91,15 +121,17 @@ up [-n|--num-nodes NUM_NODES] [-f|--force] [docker-image]
91
121
92
122
If --ipv6 is specified, we will create a Docker network with IPv6 enabled.
93
123
94
- Note that port 5678 will be automatically exposed for ducker01 node and will be mapped to 5678
95
- on your local machine to enable debugging in VS Code.
124
+ Debug port mapping:
125
+ - Without --prefix: ${debugpy_port} (container) -> ${debugpy_port} (host)
126
+ - With --prefix: default 'auto' (host random -> ${debugpy_port} ); override via --debug-port or DUCKER_DEBUGPY_PORT
127
+ - The chosen host port is recorded at build/debug-port.<prefix>.txt
96
128
97
- test [-d|--debug] [test-name(s)] [-- [ducktape args]]
129
+ test [-d|--debug] [--prefix NAME] [ test-name(s)] [-- [ducktape args]]
98
130
Run a test or set of tests inside the currently active Ducker nodes.
99
131
For example, to run the system test produce_bench_test, you would run:
100
132
./tests/docker/ducker-ak test ./tests/kafkatest/tests/core/produce_bench_test.py
101
133
102
- If --debug is passed, the tests will wait for remote VS Code debugger to connect on port 5678 :
134
+ If --debug is passed, the tests will wait for remote VS Code debugger to connect on host debug port :
103
135
./tests/docker/ducker-ak test --debug ./tests/kafkatest/tests/core/produce_bench_test.py
104
136
105
137
To pass arguments to underlying ducktape invocation, pass them after ` --` , e.g.:
@@ -113,14 +145,20 @@ ssh [node-name|user-name@node-name] [command]
113
145
is specified, we will run that command. Otherwise, we will provide a login
114
146
shell.
115
147
116
- down [-q|--quiet] [-f|--force]
148
+ down [-q|--quiet] [-f|--force] [--prefix NAME]
117
149
Tear down all the currently active ducker-ak nodes. If --quiet is specified,
118
150
only error messages are printed. If --force or -f is specified, "docker rm -f"
119
151
will be used to remove the nodes, which kills currently running ducker-ak test.
152
+ If --prefix is provided, only that cluster is removed.
120
153
121
154
purge [--f|--force]
122
155
Purge Docker images created by ducker-ak. This will free disk space.
123
156
If --force is set, we run 'docker rmi -f'.
157
+
158
+ Environment variables:
159
+ - DUCKER_PREFIX : equivalent to --prefix
160
+ - DUCKER_DEBUGPY_PORT : equivalent to --debug-port
161
+
124
162
EOF
125
163
exit " ${exit_status} "
126
164
}
@@ -292,14 +330,18 @@ docker_run() {
292
330
done
293
331
fi
294
332
if [[ -n ${port_mapping} ]]; then
295
- expose_ports=" ${expose_ports} -p ${port_mapping} :${port_mapping} "
333
+ if [[ " ${port_mapping} " == * :* ]]; then
334
+ expose_ports=" ${expose_ports} -p ${port_mapping} "
335
+ else
336
+ expose_ports=" ${expose_ports} -p ${port_mapping} :${port_mapping} "
337
+ fi
296
338
fi
297
339
298
340
# Invoke docker-run. We need privileged mode to be able to run iptables
299
341
# and mount FUSE filesystems inside the container. We also need it to
300
342
# run iptables inside the container.
301
343
must_do -v docker run --privileged \
302
- -d -t -h " ${node} " --network ducknet " ${expose_ports} " \
344
+ -d -t -h " ${node} " --network " $( net_name ) " " ${expose_ports} " \
303
345
--memory=${docker_run_memory_limit} --memory-swappiness=1 \
304
346
-v " ${kafka_dir} :/opt/kafka-dev" --name " ${node} " -- " ${image_name} "
305
347
}
@@ -310,14 +352,14 @@ setup_custom_ducktape() {
310
352
311
353
[[ -f " ${custom_ducktape} /ducktape/__init__.py" ]] || \
312
354
die " You must supply a valid ducktape directory to --custom-ducktape"
313
- docker_run ducker01 " ${image_name} "
314
- local running_container=" $( docker ps -f=network=ducknet -q) "
355
+ docker_run " $( node_name 1 ) " " ${image_name} "
356
+ local running_container=" $( docker ps -f=network=$( net_name ) -q) "
315
357
must_do -v -o docker cp " ${custom_ducktape} " " ${running_container} :/opt/ducktape"
316
- docker exec --user=root ducker01 bash -c ' set -x && cd /opt/kafka-dev/tests && sudo python3 ./setup.py develop install && cd /opt/ducktape && sudo python3 ./setup.py develop install'
358
+ docker exec --user=root " $( node_name 1 ) " bash -c ' set -x && cd /opt/kafka-dev/tests && sudo python3 ./setup.py develop install && cd /opt/ducktape && sudo python3 ./setup.py develop install'
317
359
[[ $? -ne 0 ]] && die " failed to install the new ducktape."
318
- must_do -v -o docker commit ducker01 " ${image_name} "
360
+ must_do -v -o docker commit " $( node_name 1 ) " " ${image_name} "
319
361
must_do -v docker kill " ${running_container} "
320
- must_do -v docker rm ducker01
362
+ must_do -v docker rm " $( node_name 1 ) "
321
363
}
322
364
323
365
cleanup_native_dir () {
@@ -348,6 +390,7 @@ prepare_native_dir() {
348
390
349
391
ducker_up () {
350
392
require_commands docker
393
+ [[ -z " ${prefix} " ]] && prefix=" ${DUCKER_PREFIX:- } "
351
394
while [[ $# -ge 1 ]]; do
352
395
case " ${1} " in
353
396
-C|--custom-ducktape) set_once custom_ducktape " ${2} " " the custom ducktape directory" ; shift 2;;
@@ -357,6 +400,8 @@ ducker_up() {
357
400
-e|--expose-ports) set_once expose_ports " ${2} " " the ports to expose" ; shift 2;;
358
401
-m|--kafka_mode) set_once kafka_mode " ${2} " " the mode in which kafka will run" ; shift 2;;
359
402
--ipv6) set_once ipv6 " true" " enable IPv6" ; shift ;;
403
+ --prefix) set_once prefix " ${2} " " prefix" ; shift 2;;
404
+ --debug-port) set_once debugpy_host_port " ${2} " " debug host port (number or 'auto')" ; shift 2;;
360
405
* ) set_once image_name " ${1} " " docker image name" ; shift ;;
361
406
esac
362
407
done
@@ -396,36 +441,66 @@ it up anyway."
396
441
exit 1
397
442
fi
398
443
fi
399
- local running_containers=" $( docker ps -f=network=ducknet -q) "
444
+ local running_containers=" $( docker ps -f=network=$( net_name ) -q) "
400
445
local num_running_containers=$( count ${running_containers} )
401
446
if [[ ${num_running_containers} -gt 0 ]]; then
402
447
die " ducker_up: there are ${num_running_containers} ducker containers \
403
- running already. Use ducker down to bring down these containers before \
448
+ running already. Use ducker down --prefix ${prefix :- } to bring down these containers before \
404
449
attempting to start new ones."
405
450
fi
406
451
407
452
echo " ducker_up: Bringing up ${image_name} with ${num_nodes} nodes..."
408
- if docker network inspect ducknet & > /dev/null; then
409
- must_do -v docker network rm ducknet
453
+ if docker network inspect " $( net_name ) " & > /dev/null; then
454
+ must_do -v docker network rm " $( net_name ) "
410
455
fi
411
456
network_create_args=" "
412
457
if [[ " ${ipv6} " == " true" ]]; then
413
458
subnet_cidr_prefix=" ${DUCKER_SUBNET_CIDR:- " fc00:cf17" } "
414
459
network_create_args=" --ipv6 --subnet ${subnet_cidr_prefix} ::/64"
415
460
fi
416
- must_do -v docker network create ${network_create_args} ducknet
461
+ must_do -v docker network create ${network_create_args} " $( net_name) "
462
+
417
463
if [[ -n " ${custom_ducktape} " ]]; then
418
464
setup_custom_ducktape " ${custom_ducktape} " " ${image_name} "
419
465
fi
420
- docker_run ducker01 " ${image_name} " " ${expose_ports} " " ${debugpy_port} "
466
+ # Determine debug host port mapping for the first node:
467
+ # - If no prefix and not overridden -> fixed ${debugpy_port}
468
+ # - If prefix present and not overridden -> auto (random free host port)
469
+ if [[ -z " ${debugpy_host_port} " ]]; then
470
+ if [[ -n " ${prefix} " ]]; then
471
+ debugpy_host_port=" auto"
472
+ else
473
+ debugpy_host_port=" ${debugpy_port} "
474
+ fi
475
+ fi
476
+ if [[ " ${debugpy_host_port} " == " auto" ]]; then
477
+ debug_map=" 0:${debugpy_port} "
478
+ else
479
+ debug_map=" ${debugpy_host_port} :${debugpy_port} "
480
+ fi
481
+ docker_run " $( node_name 1) " " ${image_name} " " ${expose_ports} " " ${debug_map} "
482
+
483
+ # Inform the user which host port is actually used for debug on node1.
484
+ if [[ " ${debugpy_host_port} " == " auto" ]]; then
485
+ assigned=" $( docker port " $( node_name 1) " ${debugpy_port} /tcp | awk -F: ' END{print $NF}' ) "
486
+ echo " ducker_up: assigned debug host port ${assigned} for $( node_name 1) (container ${debugpy_port} /tcp)"
487
+ debugpy_host_port=" ${assigned} "
488
+ else
489
+ echo " ducker_up: debug host port ${debugpy_host_port} mapped to container ${debugpy_port} on $( node_name 1) "
490
+ fi
491
+ if [[ -n " ${prefix} " ]]; then
492
+ mkdir -p " ${ducker_dir} /build"
493
+ echo " ${debugpy_host_port} " > " ${ducker_dir} /build/debug-port${prefix: +.${prefix} } .txt"
494
+ fi
495
+
421
496
for n in $( seq -f %02g 2 ${num_nodes} ) ; do
422
- local node=" ducker ${n} "
497
+ local node=" $( container_prefix ) ${n} "
423
498
docker_run " ${node} " " ${image_name} " " ${expose_ports} "
424
499
done
425
500
mkdir -p " ${ducker_dir} /build"
426
- exec 3<> " ${ducker_dir} /build/node_hosts"
501
+ exec 3<> " ${ducker_dir} /build/node_hosts${prefix : +. ${prefix} } "
427
502
for n in $( seq -f %02g 1 ${num_nodes} ) ; do
428
- local node=" ducker ${n} "
503
+ local node=" $( container_prefix ) ${n} "
429
504
if [[ " ${ipv6} " == " true" ]]; then
430
505
docker exec --user=root " ${node} " grep " ${node} " /etc/hosts | grep " ${subnet_cidr_prefix} " >&3
431
506
else
@@ -435,25 +510,25 @@ attempting to start new ones."
435
510
done
436
511
exec 3>& -
437
512
for n in $( seq -f %02g 1 ${num_nodes} ) ; do
438
- local node=" ducker ${n} "
513
+ local node=" $( container_prefix ) ${n} "
439
514
docker exec --user=root " ${node} " \
440
- bash -c " grep -v ${node} /opt/kafka-dev/tests/docker/build/node_hosts >> /etc/hosts"
515
+ bash -c " grep -v ${node} /opt/kafka-dev/tests/docker/build/node_hosts${prefix : +. ${prefix} } >> /etc/hosts"
441
516
[[ $? -ne 0 ]] && die " failed to append to the /etc/hosts file on ${node} "
442
517
# Filter out ipv4 addresses if ipv6
443
518
if [[ " ${ipv6} " == " true" ]]; then
444
519
docker exec --user=root " ${node} " \
445
- bash -c " grep -v -E '([0-9]{1,3}\.){3}[0-9]{1,3}' /opt/kafka-dev/tests/docker/build/node_hosts >> /etc/hosts"
520
+ bash -c " grep -v -E '([0-9]{1,3}\.){3}[0-9]{1,3}' /opt/kafka-dev/tests/docker/build/node_hosts${prefix : +. ${prefix} } >> /etc/hosts"
446
521
[[ $? -ne 0 ]] && die " failed to append to the /etc/hosts file on ${node} "
447
522
fi
448
523
done
449
524
450
525
if [ " $kafka_mode " == " native" ]; then
451
- docker exec --user=root ducker01 bash -c ' cp /opt/kafka-binary/kafka.Kafka /opt/kafka-dev/kafka.Kafka'
526
+ docker exec --user=root " $( node_name 1 ) " bash -c ' cp /opt/kafka-binary/kafka.Kafka /opt/kafka-dev/kafka.Kafka'
452
527
fi
453
528
454
529
echo " ducker_up: added the latest entries to /etc/hosts on each node."
455
- generate_cluster_json_file " ${num_nodes} " " ${ducker_dir} /build/cluster.json"
456
- echo " ducker_up: successfully wrote ${ducker_dir} /build/cluster.json"
530
+ generate_cluster_json_file " ${num_nodes} " " ${ducker_dir} /build/cluster${prefix : +. ${prefix} } .json"
531
+ echo " ducker_up: successfully wrote ${ducker_dir} /build/cluster${prefix : +. ${prefix} } .json"
457
532
echo " ** ducker_up: successfully brought up ${num_nodes} nodes."
458
533
}
459
534
491
566
else
492
567
suffix=" ,"
493
568
fi
494
- local node=$( printf ducker%02d ${n} )
569
+ local node_prefix=" ${prefix: +${prefix} -} ducker"
570
+ local node=$( printf " %s%02d" " ${node_prefix} " ${n} )
495
571
cat<< EOF >&3
496
572
{
497
573
"externally_routable_ip": "${node} ",
@@ -527,13 +603,14 @@ correct_latest_link() {
527
603
528
604
ducker_test () {
529
605
require_commands docker
530
- docker inspect ducker01 & > /dev/null || \
531
- die " ducker_test: the ducker01 instance appears to be down. Did you run 'ducker up'? "
606
+ [[ -z " ${prefix} " ]] && prefix= " ${DUCKER_PREFIX :- } "
607
+
532
608
declare -a test_name_args=()
533
609
local debug=0
534
610
while [[ $# -ge 1 ]]; do
535
611
case " ${1} " in
536
612
-d|--debug) debug=1; shift ;;
613
+ --prefix) set_once prefix " ${2} " " prefix" ; shift 2;;
537
614
--) shift ; break ;;
538
615
* ) test_name_args+=(" ${1} " ); shift ;;
539
616
esac
@@ -563,9 +640,10 @@ ducker_test() {
563
640
local ducktape_cmd=" ducktape"
564
641
fi
565
642
566
- cmd=" cd /opt/kafka-dev && ${ducktape_cmd} --cluster-file /opt/kafka-dev/tests/docker/build/cluster.json $test_names $ducktape_args "
567
- echo " docker exec ducker01 bash -c \" ${cmd} \" "
568
- docker exec --user=ducker ducker01 bash -c " ${cmd} "
643
+ local cluster_json_in_container=" /opt/kafka-dev/tests/docker/build/cluster${prefix: +.${prefix} } .json"
644
+ cmd=" cd /opt/kafka-dev && ${ducktape_cmd} --cluster-file ${cluster_json_in_container} $test_names $ducktape_args "
645
+ echo " docker exec $( node_name 1) bash -c \" ${cmd} \" "
646
+ docker exec --user=ducker " $( node_name 1) " bash -c " ${cmd} "
569
647
docker_status=$?
570
648
correct_latest_link
571
649
exit " ${docker_status} "
@@ -611,7 +689,7 @@ $(echo_running_container_names)"
611
689
612
690
# Echo all the running Ducker container names, or (none) if there are no running Ducker containers.
613
691
echo_running_container_names () {
614
- node_names=" $( docker ps -f=network=ducknet -q --format ' {{.Names}}' | sort) "
692
+ node_names=" $( docker ps -f=network=$( net_name ) -q --format ' {{.Names}}' | sort) "
615
693
if [[ -z " ${node_names} " ]]; then
616
694
echo " (none)"
617
695
else
@@ -621,20 +699,22 @@ echo_running_container_names() {
621
699
622
700
ducker_down () {
623
701
require_commands docker
702
+ [[ -z " ${prefix} " ]] && prefix=" ${DUCKER_PREFIX:- } "
624
703
local verbose=1
625
704
local force_str=" "
626
705
while [[ $# -ge 1 ]]; do
627
706
case " ${1} " in
628
707
-q|--quiet) verbose=0; shift ;;
629
708
-f|--force) force_str=" -f" ; shift ;;
709
+ --prefix) set_once prefix " ${2} " " prefix" ; shift 2;;
630
710
* ) die " ducker_down: unexpected command-line argument ${1} " ;;
631
711
esac
632
712
done
633
713
local running_containers
634
- running_containers=" $( docker ps -f=network=ducknet -q) "
635
- [[ $? -eq 0 ]] || die " ducker_down: docker command failed. Is the docker daemon running?"
714
+ running_containers=" $( docker ps -f=network=$( net_name ) -q) "
715
+ [[ $? -eq 0 ]] || die " ducker_down: docker command failed. Is the docker daemon running?"
636
716
running_containers=${running_containers// $' \n ' / }
637
- local all_containers=" $( docker ps -a -f=network=ducknet -q) "
717
+ local all_containers=" $( docker ps -a -f=network=$( net_name ) -q) "
638
718
all_containers=${all_containers// $' \n ' / }
639
719
if [[ -z " ${all_containers} " ]]; then
640
720
maybe_echo " ${verbose} " " No ducker containers found."
@@ -648,9 +728,21 @@ ducker_down() {
648
728
must_do ${verbose_flag} docker kill " ${running_containers} "
649
729
fi
650
730
must_do ${verbose_flag} docker rm ${force_str} " ${all_containers} "
651
- must_do ${verbose_flag} -o rm -f -- " ${ducker_dir} /build/node_hosts" " ${ducker_dir} /build/cluster.json"
652
- if docker network inspect ducknet & > /dev/null; then
653
- must_do -v docker network rm ducknet
731
+ if [[ -n " ${prefix} " ]]; then
732
+ # Prefixed cluster: remove only prefixed files
733
+ must_do ${verbose_flag} -o rm -f -- \
734
+ " ${ducker_dir} /build/node_hosts.${prefix} " \
735
+ " ${ducker_dir} /build/debug-port.${prefix} .txt" \
736
+ " ${ducker_dir} /build/cluster.${prefix} .json"
737
+ else
738
+ # Unprefixed cluster: remove only unprefixed files
739
+ must_do ${verbose_flag} -o rm -f -- \
740
+ " ${ducker_dir} /build/node_hosts" \
741
+ " ${ducker_dir} /build/cluster.json"
742
+ fi
743
+
744
+ if docker network inspect " $( net_name) " & > /dev/null; then
745
+ must_do -v docker network rm " $( net_name) "
654
746
fi
655
747
maybe_echo " ${verbose} " " ducker_down: removed $( count ${all_containers} ) containers."
656
748
}
0 commit comments