$(BUILD_DIR)/tests:
${Q}mkdir -p $@
+.PHONY: test.multi-server.%
+test.multi-server.%:
+ $(MAKE) -f src/tests/multi-server/all.mk $*
+
######################################################################
#
# Generic rules to set up the tests
--- /dev/null
+If you are reading this, you are probably wondering how to run a multi-server test. Here's a quick overview.
+
+## Before You Begin
+Running the multi-server tests requires the availability of a Docker image `freeradius-build:latest` to be available on the host running the tests.
+```bash
+% cd ${FREERADIUS-SERVER-LOCAL-REPO}
+% make docker.ubuntu24.build
+% docker tag <your-freeradius-docker-image-tag> freeradius-build:latest
+```
+
+
+## Run Test With Makefile
+### Run All Tests
+
+2. Run make target based on the test name. All testcase config files start with "test-*".
+```bash
+% cd ${FREERADIUS-SERVER-LOCAL-REPO}
+% make -f src/tests/multi-server/all.mk
+```
+
+### Run Specific Tests (Example)
+```bash
+% cd ${FREERADIUS-SERVER-LOCAL-REPO}
+% make -f src/tests/multi-server/all.mk test-5hs-autoaccept
+```
+or
+```bash
+% cd ${FREERADIUS-SERVER-LOCAL-REPO}
+% make -f src/tests/multi-server/all.mk test-1p-2hs-autoaccept
+```
+
+### Optional Debug Logs and Logging Verbosity Level
+```bash
+% cd ${FREERADIUS-SERVER-LOCAL-REPO}
+% make -f src/tests/multi-server/all.mk test-5hs-autoaccept DEBUG=1 VERBOSE=4
+```
+
+## Run Multi-Server Tests Manually Without Makefile (Optional)
+
+### Clone Multi-Server Test Framework Repo & Activate Python venv
+```bash
+git clone git@github.com:InkbridgeNetworks/freeradius-multi-server.git ${FREERADIUS-MULTI-SERVER-LOCAL-REPO}
+cd ${FREERADIUS-MULTI-SERVER-LOCAL-REPO}
+./configure
+source .venv/bin/activate
+```
+
+### Render Jinja Templates
+
+In this example we render the Jinja templates used by the environment configuration used by the `test-5hs-autoaccept` test.
+The Docker Compose `env-5hs-autoaccept.yml` file represents the `load-generator -> 5 homeserver` test environment used by the test.
+
+Render FreeRADIUS "homeserver" Virtual Server config:
+```bash
+% python3 src/config_builder.py \
+ --vars-file "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/jinja-vars/env-5hs-autoaccept.vars.yml" \
+ --aux-file "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/configs/freeradius/homeserver/radiusd.conf.j2" \
+ --include-path "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/"
+ ```
+ Render FreeRADIUS "load-generator" Virtual Server config:
+ ```bash
+ python3 src/config_builder.py \
+ --vars-file "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/jinja-vars/env-5hs-autoaccept.vars.yml" \
+ --aux-file "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/configs/freeradius/load-generator/radiusd.conf.j2" \
+ --include-path "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/"
+```
+Render Docker Compose environment:
+```bash
+ python3 src/config_builder.py \
+ --vars-file "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/jinja-vars/env-5hs-autoaccept.vars.yml" \
+ --aux-file "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/docker-compose/env-5hs-autoaccept.yml.j2" \
+ --include-path "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/"
+```
+
+### Run the test:
+```bash
+% DATA_PATH="${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/configs" \
+ make test-framework -- -x -v \
+ --compose "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/environments/docker-compose/env-5hs-autoaccept.yml" \
+ --test "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/src/tests/multi-server/test-5hs-autoaccept.yml" \
+ --use-files \
+ --listener-dir "${FREERADIUS-MULTI-SERVER-LOCAL-REPO_ABS_PATH}/build/tests/multi-server/freeradius-multi-server-test-runtime-logs/test-5hs-autoaccept"
+```
--- /dev/null
+#
+# all.mk for multi-server tests
+#
+# Makefile arguments affecting test framework debug logs and verbosity:
+# - DEBUG=1 to enable debug logs from multi-server test framework
+# - VERBOSE=<level> 1 for normal verbosity (default), up to 4
+#
+
+# Set required variables for Makefile
+SHELL := /bin/bash
+
+FREERADIUS_SERVER_SRC_PATH_REL := ./
+FREERADIUS_SERVER_SRC_PATH_ABS := $(abspath $(FREERADIUS_SERVER_SRC_PATH_REL))
+FREERADIUS_SERVER_BUILD_DIR_PATH_ABS := $(FREERADIUS_SERVER_SRC_PATH_ABS)/build
+FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS := $(FREERADIUS_SERVER_SRC_PATH_ABS)/src/tests/multi-server
+FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS := $(FREERADIUS_SERVER_BUILD_DIR_PATH_ABS)/tests/multi-server
+FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_REPO := https://github.com/InkbridgeNetworks/freeradius-multi-server.git
+FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_BRANCH := logging-rework
+
+FREERADIUS_MULTI_SERVER_FRAMEWORK_LOG_DIR_ABS:= $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)/logs
+FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS := $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)/logs
+
+MULTI_SERVER_TEST_COMBINED_LOG := $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/multi-server-tests-stdout-combined.log
+MULTI_SERVER_TEST_LINELOG_COMBINED_LOG := $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/multi-server-tests-linelog-combined.log
+
+# Enable multi-server test framework debug logs
+DEBUG ?= 0
+DEBUG_LEVEL_0 := ""
+DEBUG_LEVEL_1 := -x
+DEBUG_LEVEL_2 := -xx
+DEBUG_ARG := $(DEBUG_LEVEL_$(DEBUG))
+
+# Multi-server test verbosity level
+VERBOSE ?= 1
+VERBOSE_LEVEL_0 := ""
+VERBOSE_LEVEL_1 := -v
+VERBOSE_LEVEL_2 := -vv
+VERBOSE_LEVEL_3 := -vvv
+VERBOSE_LEVEL_4 := -vvvv
+VERBOSE_ARG := $(VERBOSE_LEVEL_$(VERBOSE))
+
+# Default Multi-server tests (1st target of Makefile)
+# We purposely do not run all make targets here to run the short
+# tests by default.
+multi-server: test-5hs-autoaccept test-1p-2hs-autoaccept combine-multi-server-test-linelog
+
+.PHONY: test.multi-server
+test.multi-server: multi-server
+
+# Clean target to remove all .log and .txt.bak files in the runtime logs directory
+.PHONY: clean.test.multi-server
+clean.test.multi-server:
+ @echo "INFO: Removing all .log and .txt.bak files in $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)"
+ rm -f $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/*.log
+ rm -f $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/*.log.bak
+ rm -f $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/*.txt.bak
+
+# Allow standalone use: make -f src/tests/multi-server/all.mk clean
+# Prerequisite-only rule merges safely with the top-level clean target
+.PHONY: clean
+clean: clean.test.multi-server
+
+# Hook into the top-level clean.test when included as a submakefile
+clean.test: clean.test.multi-server
+
+# Additional multi-server tests for longer runs
+multi-server-5min: test-5hs-autoaccept-5min test-1p-2hs-autoaccept-5min combine-multi-server-test-linelog
+
+.PHONY: env-5hs-autoaccept test-5hs-autoaccept test-5hs-autoaccept-5min env-1p-2hs-autoaccept test-1p-2hs-autoaccept test-1p-2hs-autoaccept-5min combine-multi-server-test-linelog
+
+env-5hs-autoaccept:
+ @LOG_FILE="$(MULTI_SERVER_TEST_COMBINED_LOG)"; \
+ set -e; exec &> >(tee -a "$${LOG_FILE}"); \
+ \
+ ENV_NAME=$@; \
+ \
+ echo "INFO: FREERADIUS_SERVER_SRC_PATH_REL=$(FREERADIUS_SERVER_SRC_PATH_REL)"; \
+ echo "INFO: FREERADIUS_SERVER_SRC_PATH_ABS=$(FREERADIUS_SERVER_SRC_PATH_ABS)"; \
+ echo "INFO: FREERADIUS_SERVER_BUILD_DIR_PATH_ABS=$(FREERADIUS_SERVER_BUILD_DIR_PATH_ABS)"; \
+ echo "INFO: FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS=$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)"; \
+ echo "INFO: FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS=$(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)"; \
+ \
+ mkdir -p "$(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)"; \
+ mkdir -p "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)"; \
+ cd $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS); \
+ \
+ if [ ! -d freeradius-multi-server/.git ]; then \
+ ( git clone $(FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_REPO) freeradius-multi-server && cd freeradius-multi-server && git checkout $(FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_BRANCH) && cd freeradius-multi-server && git checkout $(FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_BRANCH) ); \
+ else \
+ #( cd freeradius-multi-server && git pull ); \
+ #( cd freeradius-multi-server ); \
+ ( cd freeradius-multi-server && git checkout $(FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_BRANCH) && git pull ); \
+ fi; \
+ \
+ cd freeradius-multi-server; \
+ $(MAKE) configure; \
+ . .venv/bin/activate; \
+ \
+ echo "INFO: Currently in $$(pwd)"; \
+ \
+ MULTI_SERVER_ENV_VARS_FILE_PATH_ABS="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/jinja-vars/$$ENV_NAME.vars.yml"; \
+ JINJA_RENDERING_SCOPE_PATH_ABS="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)"; \
+ echo "INFO: MULTI_SERVER_ENV_VARS_FILE_PATH_ABS=$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS"; \
+ echo "INFO: FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS=$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)"; \
+ echo "INFO: JINJA_RENDERING_SCOPE_PATH_ABS=$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ set -x; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs/freeradius/homeserver/radiusd.conf.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs/freeradius/load-generator/radiusd.conf.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/docker-compose/$$ENV_NAME.yml.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+
+
+test-5hs-autoaccept: env-5hs-autoaccept
+ @LOG_FILE="$(MULTI_SERVER_TEST_COMBINED_LOG)"; \
+ set -e; exec &> >(tee -a "$${LOG_FILE}"); \
+ \
+ TEST_NAME=$@; \
+ \
+ cd $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)/freeradius-multi-server; \
+ . .venv/bin/activate; \
+ \
+ echo "INFO: Running $${TEST_NAME}"; \
+ \
+ set -x; \
+ \
+ DATA_PATH="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs" \
+ make test-framework \
+ -- $(DEBUG_ARG) $(VERBOSE_ARG) \
+ --compose "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/docker-compose/env-5hs-autoaccept.yml" \
+ --test "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/$$TEST_NAME.yml" \
+ --use-files \
+ --listener-dir "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)" \
+ --log-dir "$(FREERADIUS_MULTI_SERVER_FRAMEWORK_LOG_DIR_ABS)" \
+ --output "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/$$TEST_NAME-result.log"
+
+test-5hs-autoaccept-5min: env-5hs-autoaccept
+ @LOG_FILE="$(MULTI_SERVER_TEST_COMBINED_LOG)"; \
+ set -e; exec &> >(tee -a "$${LOG_FILE}"); \
+ \
+ TEST_NAME=$@; \
+ \
+ cd $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)/freeradius-multi-server; \
+ . .venv/bin/activate; \
+ \
+ echo "INFO: Running $${TEST_NAME}"; \
+ \
+ set -x; \
+ \
+ DATA_PATH="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs" \
+ make test-framework \
+ -- $(DEBUG_ARG) $(VERBOSE_ARG) \
+ --compose "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/docker-compose/env-5hs-autoaccept.yml" \
+ --test "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/test-5hs-autoaccept-5min.yml" \
+ --use-files \
+ --listener-dir "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)" \
+ --log-dir "$(FREERADIUS_MULTI_SERVER_FRAMEWORK_LOG_DIR_ABS)" \
+ --output "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/$$TEST_NAME-result.log"
+
+env-1p-2hs-autoaccept:
+ @LOG_FILE="$(MULTI_SERVER_TEST_COMBINED_LOG)"; \
+ set -e; exec &> >(tee -a "$${LOG_FILE}"); \
+ \
+ ENV_NAME=$@; \
+ \
+ echo "INFO: FREERADIUS_SERVER_SRC_PATH_REL=$(FREERADIUS_SERVER_SRC_PATH_REL)"; \
+ echo "INFO: FREERADIUS_SERVER_SRC_PATH_ABS=$(FREERADIUS_SERVER_SRC_PATH_ABS)"; \
+ echo "INFO: FREERADIUS_SERVER_BUILD_DIR_PATH_ABS=$(FREERADIUS_SERVER_BUILD_DIR_PATH_ABS)"; \
+ echo "INFO: FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS=$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)"; \
+ echo "INFO: FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS=$(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)"; \
+ \
+ mkdir -p "$(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)"; \
+ mkdir -p "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)"; \
+ cd $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS); \
+ \
+ if [ ! -d freeradius-multi-server/.git ]; then \
+ git clone $(FREERADIUS_MULTI_SERVER_FRAMEWORK_GIT_REPO); \
+ else \
+ #( cd freeradius-multi-server && git pull ); \
+ ( cd freeradius-multi-server ); \
+ fi; \
+ \
+ cd freeradius-multi-server; \
+ $(MAKE) configure; \
+ . .venv/bin/activate; \
+ \
+ echo "INFO: Currently in $$(pwd)"; \
+ \
+ MULTI_SERVER_ENV_VARS_FILE_PATH_ABS="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/jinja-vars/$$ENV_NAME.vars.yml"; \
+ JINJA_RENDERING_SCOPE_PATH_ABS="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)"; \
+ echo "INFO: MULTI_SERVER_ENV_VARS_FILE_PATH_ABS=$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS"; \
+ echo "INFO: FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS=$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)"; \
+ echo "INFO: JINJA_RENDERING_SCOPE_PATH_ABS=$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ set -x; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs/freeradius/homeserver/radiusd.conf.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs/freeradius/proxy/radiusd.conf.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs/freeradius/load-generator/radiusd.conf.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+ \
+ python3 src/config_builder.py \
+ --vars-file "$$MULTI_SERVER_ENV_VARS_FILE_PATH_ABS" \
+ --aux-file "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/docker-compose/$$ENV_NAME.yml.j2" \
+ --include-path "$$JINJA_RENDERING_SCOPE_PATH_ABS"; \
+
+
+test-1p-2hs-autoaccept: env-1p-2hs-autoaccept
+ @LOG_FILE="$(MULTI_SERVER_TEST_COMBINED_LOG)"; \
+ set -e; exec &> >(tee -a "$${LOG_FILE}"); \
+ \
+ TEST_NAME=$@; \
+ \
+ cd $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)/freeradius-multi-server; \
+ . .venv/bin/activate; \
+ \
+ echo "INFO: Running $${TEST_NAME}"; \
+ \
+ set -x; \
+ \
+ DATA_PATH="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs" \
+ make test-framework \
+ -- $(DEBUG_ARG) $(VERBOSE_ARG) \
+ --compose "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/docker-compose/env-1p-2hs-autoaccept.yml" \
+ --test "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/test-1p-2hs-autoaccept.yml" \
+ --use-files \
+ --listener-dir "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)" \
+ --log-dir "$(FREERADIUS_MULTI_SERVER_FRAMEWORK_LOG_DIR_ABS)" \
+ --output "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/$$TEST_NAME-result.log"
+
+test-1p-2hs-autoaccept-5min: env-1p-2hs-autoaccept
+ @LOG_FILE="$(MULTI_SERVER_TEST_COMBINED_LOG)"; \
+ set -e; exec &> >(tee -a "$${LOG_FILE}"); \
+ \
+ TEST_NAME=$@; \
+ \
+ cd $(FREERADIUS_MULTI_SERVER_BUILD_DIR_PATH_ABS)/freeradius-multi-server; \
+ . .venv/bin/activate; \
+ \
+ echo "INFO: Running $${TEST_NAME}"; \
+ \
+ set -x; \
+ \
+ DATA_PATH="$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/configs" \
+ make test-framework \
+ -- $(DEBUG_ARG) $(VERBOSE_ARG) \
+ --compose "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/environments/docker-compose/env-1p-2hs-autoaccept.yml" \
+ --test "$(FREERADIUS_MULTI_SERVER_TESTS_BASE_PATH_ABS)/$$TEST_NAME.yml" \
+ --use-files \
+ --listener-dir "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)" \
+ --log-dir "$(FREERADIUS_MULTI_SERVER_FRAMEWORK_LOG_DIR_ABS)" \
+ --output "$(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/$$TEST_NAME-result.log"
+
+combine-multi-server-test-linelog:
+ @echo "INFO: Combining multi-server test linelog message output into $(MULTI_SERVER_TEST_LINELOG_COMBINED_LOG)"
+ @rm -f $(MULTI_SERVER_TEST_LINELOG_COMBINED_LOG)
+ for f in $(FREERADIUS_MULTI_SERVER_TEST_RUNTIME_LOGS_DIR_ABS)/*.txt.bak; do \
+ echo "$$f"; \
+ cat "$$f"; \
+ echo ""; \
+ done > $(MULTI_SERVER_TEST_LINELOG_COMBINED_LOG)
--- /dev/null
+Test environment configuration files which includes, and not limited to, FreeRADIUS configuration, and 3rd party
+environment component configuration, etc.
--- /dev/null
+
+always ok {
+ rcode = ok
+}
+always handled {
+ rcode = handled
+}
--- /dev/null
+
+attr_filter attr_filter.access_reject {
+ key = User-Name
+ filename = ${modconfdir}/${.:name}/access_reject
+}
+
+attr_filter attr_filter.access_challenge {
+ key = User-Name
+ filename = ${modconfdir}/${.:name}/access_challenge
+}
+
+attr_filter attr_filter.accounting_response {
+ key = User-Name
+ filename = ${modconfdir}/${.:name}/accounting_response
+}
--- /dev/null
+
+detail {
+ filename = "${radacctdir}/%{Net.Src.IP}/detail-%Y-%m-%d"
+ escape_filenames = no
+ permissions = 0600
+ header = "%t"
+}
--- /dev/null
+
+files {
+ moddir = ${modconfdir}/${.:instance}
+ filename = ${moddir}/authorize
+}
+
+files files_accounting {
+ filename = ${modconfdir}/files/accounting
+}
--- /dev/null
+json {
+ encode {
+ output_mode = object_simple
+ value {
+ binary_format = base16
+ }
+ }
+}
--- /dev/null
+
+linelog {
+ format = "This is a log message for %{User-Name}"
+ destination = file
+
+ file {
+ filename = ${logdir}/linelog
+ permissions = 0600
+ escape_filenames = no
+
+ # Disable message buffering
+ buffer_count = 0
+ buffer_delay = 0
+ buffer_expiry = 0
+ }
+}
+
+linelog log_stdout {
+ destination = stdout
+}
--- /dev/null
+
+linelog linelog_test_framework {
+ destination = file
+
+ file {
+ filename = /var/run/multi-server/$ENV{TEST_PROJECT_NAME}.txt
+ permissions = 0644
+ escape_filenames = no
+
+ # Disable message buffering
+ buffer_count = 0
+ buffer_delay = 0
+ buffer_expiry = 0
+ }
+}
--- /dev/null
+
+linelog linelog_test_framework {
+ destination = unix
+ format = "bar"
+
+ unix {
+ filename = /var/run/multi-server/$ENV{TEST_PROJECT_NAME}.sock
+ pool {
+ start = 1
+ min = 1
+ max = 1
+ spare = 0
+ uses = 0
+ retry_delay = 30
+ lifetime = 0
+ idle_timeout = 0
+ }
+ }
+}
--- /dev/null
+
+stats {
+
+}
--- /dev/null
+
+accept {
+ reply.Packet-Type := ::Access-Accept
+
+ handled
+}
--- /dev/null
+apt-get update
+apt-get install -y iputils-ping iproute2 ipcalc
+IP_CIDR=$(ip -o -f inet addr show eth0 | awk '{print $4}')
+TEST_SUBNET=$(ipcalc -n "$IP_CIDR" | grep Network | awk '{print $2}')
+export TEST_SUBNET
+echo "TEST_SUBNET=$TEST_SUBNET" >> /etc/environment
+echo "Detected TEST_SUBNET: $TEST_SUBNET"
--- /dev/null
+name = homeserver
+
+raddbdir = /etc/freeradius
+confdir = ${raddbdir}
+modconfdir = ${confdir}/mods-config
+logdir = /var/log/freeradius
+radacctdir = ${logdir}/radacct
+
+security {
+ allow_core_dumps = yes
+}
+
+trigger {
+
+ server {
+ start = "%linelog('server_start homeserver starting')"
+
+ stop = "%linelog('server_stop homeserver stopping')"
+
+ max_requests = "%linelog('server_max_requests homeserver max requests reached')"
+ }
+
+}
+
+# logging configuration required for linelog module
+log {
+ destination = files
+ file = ${logdir}/radius.log
+ syslog_facility = daemon
+ stripped_names = no
+ auth = yes
+ auth_badpass = yes
+ auth_goodpass = yes
+}
+
+modules {
+ # Common linelog module configuration
+ {% include "environments/configs/freeradius/common/mods-available/linelog" %}
+
+ # Test framework linelog module configuration based on OS environment
+ {% if listener_type == "unix" %}
+ {% include "environments/configs/freeradius/common/mods-available/linelog_socket" %}
+ {% elif listener_type == "file" %}
+ {% include "environments/configs/freeradius/common/mods-available/linelog_file" %}
+ {% endif %}
+
+ # Common json module configuration
+ {% include "environments/configs/freeradius/common/mods-available/json" %}
+
+ # Common always module configuration
+ {% include "environments/configs/freeradius/common/mods-available/always" %}
+
+ # Common attr_filter module configuration
+ {% include "environments/configs/freeradius/common/mods-available/attr_filter" %}
+
+ # Common files module configuration
+ {% include "environments/configs/freeradius/common/mods-available/files" %}
+
+ # Common detail module configuration
+ {% include "environments/configs/freeradius/common/mods-available/detail" %}
+}
+
+policy {
+ # Common control policy configuration
+ {% include "environments/configs/freeradius/common/policy.d/control" %}
+}
+
+server hs-auto-accept {
+
+ namespace = radius
+
+ listen authentication {
+ type = Access-Request
+ type = Status-Server
+ transport = udp
+ require_message_authenticator = auto
+ limit_proxy_state = auto
+ limit {
+ max_clients = 256
+ max_connections = 256
+ idle_timeout = 60.0
+ dynamic_timeout = 600.0
+ nak_lifetime = 30.0
+ cleanup_delay = 5.0
+ }
+ udp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ tcp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ }
+
+ listen authentication {
+ type = Access-Request
+ type = Status-Server
+ transport = tcp
+ tcp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ }
+
+ listen accounting {
+ type = Accounting-Request
+ transport = udp
+ udp {
+ ipaddr = *
+ port = 1813
+ }
+ }
+
+ client upstream-proxy {
+ ipaddr = $ENV{TEST_SUBNET}
+ secret = testing123
+ require_message_authenticator = auto
+ limit_proxy_state = auto
+ }
+
+ recv Access-Request {
+ string Log-Timestamp
+ string Log-Label
+ string Log-Message
+ string Log-Homeserver-IP-Port
+ string Log-Request-Source-IP-Port
+
+ # Initialization
+ Log-Timestamp := "%Y-%m-%d-%H:%G:%e"
+ Log-Label := "INFO"
+ Log-Message := "Access-Request received"
+ Log-Homeserver-IP-Port := "%{Net.Dst.IP}:%{Net.Dst.Port}"
+ Log-Request-Source-IP-Port := "%{Net.Src.IP}:%{Net.Src.Port}"
+
+ {% raw %}
+ %linelog("homeserver-access-request {\"Log-Timestamp\": %{Log-Timestamp}, \"Log-Label\": %{Log-Label}, \"Log-Message\": %{Log-Message}, \"Log-Homeserver-IP-Port\": %{Log-Homeserver-IP-Port}, \"Log-Request-Source-IP-Port\": %{Log-Request-Source-IP-Port}}, \"User-Name\": \"%{%{User-Name}}\", \"Calling-Station-Id\": \"%{Calling-Station-Id}\"")
+
+ {% endraw %}
+ accept
+ }
+
+ send Access-Accept {
+ string Log-Timestamp
+ string Log-Label
+ string Log-Message
+ string Log-Homeserver-IP-Port
+ string Log-Request-Source-IP-Port
+
+ # Initialization
+ Log-Timestamp := "%Y-%m-%d-%H:%G:%e"
+ Log-Label := "INFO"
+ Log-Message := "Access-Accept sent"
+ Log-Homeserver-IP-Port := "%{Net.Dst.IP}:%{Net.Dst.Port}"
+ Log-Request-Source-IP-Port := "%{Net.Src.IP}:%{Net.Src.Port}"
+
+ {% raw %}
+ %linelog("homeserver-access-accept {\"Log-Timestamp\": %{Log-Timestamp}, \"Log-Label\": %{Log-Label}, \"Log-Message\": %{Log-Message}, \"Log-Homeserver-IP-Port\": %{Log-Homeserver-IP-Port}, \"Log-Request-Source-IP-Port\": %{Log-Request-Source-IP-Port}}")
+ {% endraw %}
+
+ handled
+ }
+
+ recv Status-Server {
+ ok
+ }
+
+ send Access-Challenge {
+ attr_filter.access_challenge
+ }
+
+ send Access-Reject {
+ string Log-Timestamp
+ string Log-Label
+ string Log-Message
+ string Log-Homeserver-IP-Port
+ string Log-Request-Source-IP-Port
+
+ # Initialization
+ Log-Timestamp := "%Y-%m-%d-%H:%G:%e"
+ Log-Label := "ERROR"
+ Log-Message := "Access-Reject sent"
+ Log-Homeserver-IP-Port := "%{Net.Dst.IP}:%{Net.Dst.Port}"
+ Log-Request-Source-IP-Port := "%{Net.Src.IP}:%{Net.Src.Port}"
+
+ {% raw %}
+ %linelog_test_framework("homeserver-access-reject {\"Log-Timestamp\": %{Log-Timestamp}, \"Log-Label\": %{Log-Label}, \"Log-Message\": %{Log-Message}, \"Log-Homeserver-IP-Port\": %{Log-Homeserver-IP-Port}, \"Log-Request-Source-IP-Port\": %{Log-Request-Source-IP-Port}}")
+ {% endraw %}
+
+ attr_filter.access_reject
+ }
+
+ recv Accounting-Request {
+ files_accounting
+ }
+
+ accounting Start {
+ }
+
+ accounting Stop {
+ }
+
+ accounting Interim-Update {
+ }
+
+ accounting Accounting-On {
+ }
+
+ accounting Accounting-Off {
+ }
+
+ accounting Failed {
+ }
+
+ send Accounting-Response {
+ detail
+ attr_filter.accounting_response
+ }
+}
--- /dev/null
+User-Name = "testuser"
+User-Password = "testpass"
+Calling-Station-ID = "F1-F2-F3-F4-F5-F6"
--- /dev/null
+testuser Password.Cleartext := "testpass"
--- /dev/null
+name = load-generator
+
+raddbdir = /etc/freeradius
+confdir = ${raddbdir}
+modconfdir = ${confdir}/mods-config
+logdir = /var/log/freeradius
+radacctdir = ${logdir}/radacct
+
+templates {
+ {% include "environments/configs/freeradius/load-generator/template.d/load-generator-templates" %}
+}
+
+security {
+ allow_core_dumps = yes
+}
+
+# logging configuration required for linelog module
+log {
+ destination = files
+ file = ${logdir}/radius.log
+ syslog_facility = daemon
+ stripped_names = no
+ auth = yes
+ auth_badpass = yes
+ auth_goodpass = yes
+}
+
+modules {
+ # Common linelog module configuration
+ {% include "environments/configs/freeradius/common/mods-available/linelog" %}
+
+ # Test framework linelog module configuration based on OS environment
+ {% if listener_type == "unix" %}
+ {% include "environments/configs/freeradius/common/mods-available/linelog_socket" %}
+ {% elif listener_type == "file" %}
+ {% include "environments/configs/freeradius/common/mods-available/linelog_file" %}
+ {% endif %}
+
+ # Common json module configuration
+ {% include "environments/configs/freeradius/common/mods-available/json" %}
+
+ # Common stats module configuration
+ {% include "environments/configs/freeradius/common/mods-available/stats" %}
+
+ # radius module instance configuration
+ {% for i in range(1, (load_gen_num_of_dst_servers + 1)) %}
+ radius radius-module-dst-server{{ i }} {
+ $template radius-module-dst-server-tmpl
+ udp {
+ ipaddr = {{ load_gen_dst_server_name }}{{ i }}
+ }
+ }
+ {% endfor %}
+
+}
+
+server load-generator {
+
+ namespace = radius
+
+ listen load {
+ handler = load
+ type = Access-Request
+ transport = step
+
+ step {
+
+ # Default packet config to use by the load-generator
+ filename = ${confdir}/load-generator-packets/packet.conf
+
+ csv = ${confdir}/stats/load-generator-client-stats.csv
+
+ max_attributes = 64
+
+ #
+ # The load profile is configured via environment variables set
+ # in the testcase configuration files.
+ #
+ start_pps = $ENV{TEST_CONF_START_PPS}
+ max_pps = $ENV{TEST_CONF_MAX_PPS}
+ duration = $ENV{TEST_CONF_DURATION}
+ step = $ENV{TEST_CONF_STEP}
+ max_backlog = $ENV{TEST_CONF_MAX_BACKLOG}
+ parallel = $ENV{TEST_CONF_PARALLEL}
+ num_messages = $ENV{TEST_CONF_NUM_MESSAGES}
+ repeat = no
+ }
+ }
+
+ listen authentication {
+ type = Access-Request
+ type = Status-Server
+ transport = udp
+ require_message_authenticator = auto
+ limit_proxy_state = auto
+
+ limit {
+ max_clients = 256
+ max_connections = 256
+ idle_timeout = 60.0
+ dynamic_timeout = 600.0
+ nak_lifetime = 30.0
+ cleanup_delay = 5.0
+ }
+
+ udp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+
+ tcp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ }
+
+ listen authentication {
+ type = Access-Request
+ type = Status-Server
+ transport = tcp
+
+ tcp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ }
+
+ client localhost {
+ shortname = client-localhost
+ ipaddr = *
+ secret = testing123
+ }
+
+ listen statusserver {
+ transport = udp
+ udp {
+ ipaddr = *
+ port = 1820
+ }
+
+ type = Status-Server
+ }
+
+ #
+ # Receive a Status-Server packet
+ #
+ recv Status-Server {
+ uint32 Status-Server-Counter
+ uint32 Access-Request-Counter
+ uint32 Access-Accept-Counter
+ uint32 Access-Reject-Counter
+ string Status-Server-Result
+ string Status-Server-Message
+
+ # Initialization
+ Status-Server-Counter := 0
+ Access-Request-Counter := 0
+ Access-Accept-Counter := 0
+ Access-Reject-Counter := 0
+ Status-Server-Result := "FAIL"
+ Status-Server-Message := ""
+
+ # stats module required to have access to the Status-Server's global statistics
+ stats
+
+ {%raw%}
+ Status-Server-Counter := %{reply.FreeRADIUS.Stats4.Packet-Counters.Status-Server || 0}
+ Access-Request-Counter := %{reply.FreeRADIUS.Stats4.Packet-Counters.Access-Request || 0}
+ Access-Accept-Counter := %{reply.FreeRADIUS.Stats4.Packet-Counters.Access-Accept || 0}
+ Access-Reject-Counter := %{reply.FreeRADIUS.Stats4.Packet-Counters.Access-Reject || 0}
+
+ # The Stats Module documentation explicitly states: "When listed in a recv Status-Server section, it will add global server statistics to the packet,
+ # hence the reason "reply.FreeRADIUS.Stats4" is used below.
+ if ((reply.FreeRADIUS.Stats4.Packet-Counters.Access-Accept - reply.FreeRADIUS.Stats4.Packet-Counters.Status-Server) != reply.FreeRADIUS.Stats4.Packet-Counters.Access-Request) {
+ Status-Server-Result := "FAIL"
+ Status-Server-Message := "expected vs actual stat mismatch"
+ }
+
+ if (reply.FreeRADIUS.Stats4.Packet-Counters.Access-Request == (reply.FreeRADIUS.Stats4.Packet-Counters.Access-Accept - reply.FreeRADIUS.Stats4.Packet-Counters.Status-Server)) {
+ Status-Server-Result := "PASS"
+ Status-Server-Message := "requests processed successfully"
+ }
+
+ %linelog_test_framework("load-generator-status-server {\"Status-Server-Result\": %json.quote(%{Status-Server-Result}), \"Status-Server-Message\": %json.quote(%{Status-Server-Message}), \"Status-Server-Counter\": %{Status-Server-Counter}, \"Access-Request-Counter\": %{Access-Request-Counter}, \"Access-Accept-Counter\": %{Access-Accept-Counter}, \"Access-Reject-Counter\": %{Access-Reject-Counter}}")
+
+ {%endraw%}
+ }
+
+ recv Access-Request {
+ # Use the load-generator-proxy authenticate logic
+ control.Auth-Type := "load-generator-proxy"
+ }
+
+ authenticate load-generator-proxy {
+ # Use redundant-load-balance to distribute requests to multiple home servers
+ redundant-load-balance {
+ {% for i in range(1, (load_gen_num_of_dst_servers + 1)) %}
+ radius-module-dst-server{{ i }}
+ {% endfor %}
+ }
+ }
+
+ send Access-Accept {
+ stats
+ }
+
+ send Access-Reject {
+ stats
+ }
+
+}
--- /dev/null
+"time","last_packet","rtt","rttvar","pps","pps_accepted","sent","received","backlog","max_backlog","<usec","us","10us","100us","ms","10ms","100ms","s","blocked"
--- /dev/null
+
+#
+# radius module template for load-generator
+#
+radius-module-dst-server-tmpl {
+ mode = client
+ transport = udp
+ type = Access-Request
+ require_message_authenticator = auto
+ file {
+ filename = ${logdir}/packets.bin
+ }
+ udp {
+ port = 1812
+ secret = testing123
+ }
+}
--- /dev/null
+name = proxy
+
+raddbdir = /etc/freeradius
+confdir = ${raddbdir}
+modconfdir = ${confdir}/mods-config
+logdir = /var/log/freeradius
+radacctdir = ${logdir}/radacct
+
+templates {
+ {% include "environments/configs/freeradius/proxy/template.d/proxy-templates" %}
+}
+
+security {
+ allow_core_dumps = yes
+}
+
+# logging configuration required for linelog module
+log {
+ destination = files
+ file = ${logdir}/radius.log
+ syslog_facility = daemon
+ stripped_names = no
+ auth = yes
+ auth_badpass = yes
+ auth_goodpass = yes
+}
+
+modules {
+ # Common linelog module configuration
+ {% include "environments/configs/freeradius/common/mods-available/linelog" %}
+
+ # Test framework linelog module configuration based on OS environment
+ {% if listener_type == "unix" %}
+ {% include "environments/configs/freeradius/common/mods-available/linelog_socket" %}
+ {% elif listener_type == "file" %}
+ {% include "environments/configs/freeradius/common/mods-available/linelog_file" %}
+ {% endif %}
+
+ # Common json module configuration
+ {% include "environments/configs/freeradius/common/mods-available/json" %}
+
+ # Common always module configuration
+ {% include "environments/configs/freeradius/common/mods-available/always" %}
+
+ # radius module instance configuration
+ {% for i in range(1, (proxy_num_of_dst_servers + 1)) %}
+ radius radius-module-dst-server{{ i }} {
+ $template radius-module-dst-server-tmpl
+ udp {
+ ipaddr = {{ proxy_dst_server_name }}{{ i }}
+ }
+ }
+ {% endfor %}
+
+}
+
+policy {
+ # Common control policy configuration
+ {% include "environments/configs/freeradius/common/policy.d/control" %}
+}
+
+server proxy {
+
+ namespace = radius
+
+ listen authentication {
+ type = Access-Request
+ type = Status-Server
+ transport = udp
+ require_message_authenticator = auto
+ limit_proxy_state = auto
+
+ limit {
+ max_clients = 256
+ max_connections = 256
+ idle_timeout = 60.0
+ dynamic_timeout = 600.0
+ nak_lifetime = 30.0
+ cleanup_delay = 5.0
+ }
+
+ udp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+
+ tcp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ }
+
+ listen authentication {
+ type = Access-Request
+ type = Status-Server
+ transport = tcp
+
+ tcp {
+ ipaddr = *
+ port = 1812
+ networks {
+ allow = 127/8
+ allow = 192.0.2/24
+ }
+ }
+ }
+
+ client upstream-proxy {
+ ipaddr = $ENV{TEST_SUBNET}
+ secret = testing123
+ require_message_authenticator = auto
+ limit_proxy_state = auto
+ }
+
+ recv Access-Request {
+ # Use the load-generator-proxy authenticate logic
+ control.Auth-Type := "redundant-load-balance-proxy"
+ }
+
+ authenticate redundant-load-balance-proxy {
+ # Use redundant-load-balance to distribute requests to multiple home servers
+ redundant-load-balance {
+ {% for i in range(1, (proxy_num_of_dst_servers + 1)) %}
+ radius-module-dst-server{{ i }}
+ {% endfor %}
+ }
+ }
+
+ recv Status-Server {
+ ok
+ }
+
+}
--- /dev/null
+
+#
+# radius module template for load-generator
+#
+radius-module-dst-server-tmpl {
+ mode = proxy
+ transport = udp
+ type = Access-Request
+ require_message_authenticator = auto
+ file {
+ filename = ${logdir}/packets.bin
+ }
+ udp {
+ port = 1812
+ secret = testing123
+ }
+}
--- /dev/null
+# ---------------------------------------------------------------
+# Docker Compose Test Environment:
+#
+# --> HS1 (auto-accept)
+# load-generator --> Proxy1
+# --> HS2 (auto-accept)
+#
+# ---------------------------------------------------------------
+x-common-config: &id001
+ cap_add:
+ - NET_ADMIN
+ - SYS_PTRACE
+services:
+{% for i in range(1, (compose_num_of_home_servers + 1)) %}
+ homeserver{{ i }}:
+ image: freeradius-build:latest
+ volumes:
+ - ${DATA_PATH}/freeradius/homeserver/radiusd.conf:/etc/raddb/radiusd.conf
+ - ${DATA_PATH}/freeradius/env-setup.sh:/tmp/env-setup.sh
+ - ${LISTENER_DIR}/:/var/run/multi-server/
+ command: radiusd -X
+ restart: unless-stopped
+ entrypoint:
+ - bash
+ - -lc
+ - |
+ set -euo pipefail
+
+ # Next three lines based on test-framework test config example
+ source /tmp/env-setup.sh
+ export TEST_PROJECT_NAME=${COMPOSE_PROJECT_NAME}
+
+ exec /docker-entrypoint.sh "$@"
+ sleep infinity
+ <<: *id001
+{% endfor %}
+{% for i in range(1, (compose_num_of_proxy_servers + 1)) %}
+ proxy{{ i }}:
+ image: freeradius-build:latest
+ depends_on:
+ {% for i in range(1, (compose_num_of_home_servers + 1)) %}
+ - homeserver{{ i }}
+ {% endfor %}
+ volumes:
+ - ${DATA_PATH}/freeradius/proxy/radiusd.conf:/etc/raddb/radiusd.conf
+ - ${DATA_PATH}/freeradius/env-setup.sh:/tmp/env-setup.sh
+ - ${LISTENER_DIR}/:/var/run/multi-server/
+ command: radiusd -X
+ restart: unless-stopped
+ entrypoint:
+ - bash
+ - -lc
+ - |
+ set -euo pipefail
+
+ # Next three lines based on test-framework test config example
+ source /tmp/env-setup.sh
+ export TEST_PROJECT_NAME=${COMPOSE_PROJECT_NAME}
+
+ exec /docker-entrypoint.sh "$@"
+ sleep infinity
+ <<: *id001
+{% endfor %}
+ load-generator:
+ image: freeradius-build:latest
+ ports:
+ # Expose RADIUS ports for load generator to allow us to access the server from outside docker
+ - "1812:1812/udp"
+ - "1813:1813/udp"
+ - "1820:1820/udp"
+ depends_on:
+ {% for i in range(1, (compose_num_of_proxy_servers + 1)) %}
+ - proxy{{ i }}
+ {% endfor %}
+ volumes:
+ # load-generator templates
+ - ${DATA_PATH}/freeradius/load-generator/template.d/load-generator-templates:/etc/raddb/template.d/load-generator-templates
+
+ # Setup a testuser/testpass user for authentication testing with load-generator
+ # This is only really needed when testing the load-generator with radclient during development.
+ - ${DATA_PATH}/freeradius/load-generator/mods-config/files/authorize:/etc/raddb/mods-config/files/authorize
+
+ # load-generator server config
+ - ${DATA_PATH}/freeradius/load-generator/radiusd.conf:/etc/raddb/radiusd.conf
+
+ - ${DATA_PATH}/freeradius/env-setup.sh:/tmp/env-setup.sh
+ - ${DATA_PATH}/freeradius/load-generator/load-generator-packets/:/etc/raddb/load-generator-packets/
+ - ${LISTENER_DIR}/:/var/run/multi-server/
+ environment:
+ TEST_PROJECT_NAME: ${COMPOSE_PROJECT_NAME}
+ TEST_CONF_START_PPS: 5
+ TEST_CONF_MAX_PPS: 10
+ TEST_CONF_DURATION: 5
+ TEST_CONF_STEP: 5
+ TEST_CONF_PARALLEL: 1
+ TEST_CONF_MAX_BACKLOG: 1000
+ TEST_CONF_REPEAT: "no"
+ entrypoint:
+ - bash
+ - -lc
+ - |
+ set -euo pipefail
+
+ source /tmp/env-setup.sh
+ export TEST_PROJECT_NAME=${COMPOSE_PROJECT_NAME}
+
+ sleep infinity
+ <<: *id001
--- /dev/null
+# ---------------------------------------------------------------
+# Docker Compose Test Environment:
+#
+# --> HS1 (auto-accept)
+#
+# --> HS2 (auto-accept)
+#
+# load-generator --> HS3 (auto-accept)
+#
+# --> HS4 (auto-accept)
+#
+# --> HS5 (auto-accept)
+#
+# ---------------------------------------------------------------
+x-common-config: &id001
+ cap_add:
+ - NET_ADMIN
+ - SYS_PTRACE
+services:
+{% for i in range(1, (compose_num_of_home_servers + 1)) %}
+ homeserver{{ i }}:
+ image: freeradius-build:latest
+ volumes:
+ - ${DATA_PATH}/freeradius/homeserver/radiusd.conf:/etc/raddb/radiusd.conf
+ - ${DATA_PATH}/freeradius/env-setup.sh:/tmp/env-setup.sh
+ - ${LISTENER_DIR}/:/var/run/multi-server/
+ command: radiusd -X
+ restart: unless-stopped
+ entrypoint:
+ - bash
+ - -lc
+ - |
+ set -euo pipefail
+
+ # Next three lines based on test-framework test config example
+ source /tmp/env-setup.sh
+ export TEST_PROJECT_NAME=${COMPOSE_PROJECT_NAME}
+
+ exec /docker-entrypoint.sh "$@"
+ sleep infinity
+ <<: *id001
+{% endfor %}
+ load-generator:
+ image: freeradius-build:latest
+ ports:
+ # Expose RADIUS ports for load generator to allow us to access the server from outside docker
+ - "1812:1812/udp"
+ - "1813:1813/udp"
+ - "1820:1820/udp"
+ depends_on:
+{% for i in range(1, (compose_num_of_home_servers + 1)) %}
+ - homeserver{{ i }}
+{% endfor %}
+ volumes:
+ # load-generator templates
+ - ${DATA_PATH}/freeradius/load-generator/template.d/load-generator-templates:/etc/raddb/template.d/load-generator-templates
+
+ # Setup a testuser/testpass user for authentication testing with load-generator
+ # This is only really needed when testing the load-generator with radclient during development.
+ - ${DATA_PATH}/freeradius/load-generator/mods-config/files/authorize:/etc/raddb/mods-config/files/authorize
+
+ # load-generator server config
+ - ${DATA_PATH}/freeradius/load-generator/radiusd.conf:/etc/raddb/radiusd.conf
+
+ - ${DATA_PATH}/freeradius/env-setup.sh:/tmp/env-setup.sh
+ - ${DATA_PATH}/freeradius/load-generator/load-generator-packets/:/etc/raddb/load-generator-packets/
+ - ${LISTENER_DIR}/:/var/run/multi-server/
+ environment:
+ TEST_PROJECT_NAME: ${COMPOSE_PROJECT_NAME}
+ TEST_CONF_START_PPS: 5
+ TEST_CONF_MAX_PPS: 10
+ TEST_CONF_DURATION: 5
+ TEST_CONF_STEP: 5
+ TEST_CONF_PARALLEL: 1
+ TEST_CONF_MAX_BACKLOG: 1000
+ TEST_CONF_REPEAT: "no"
+ entrypoint:
+ - bash
+ - -lc
+ - |
+ set -euo pipefail
+
+ source /tmp/env-setup.sh
+ export TEST_PROJECT_NAME=${COMPOSE_PROJECT_NAME}
+
+ sleep infinity
+ <<: *id001
--- /dev/null
+jinja_templates_to_render:
+ - environments/configs/freeradius/homeserver/radiusd.conf.j2
+ - environments/configs/freeradius/proxy/radiusd.conf.j2
+ - environments/configs/freeradius/load-generator/radiusd.conf.j2
+ - environments/docker-compose/env-1p-2hs-autoaccept.yml.j2
+
+# Docker compose Jinja template vars
+compose_num_of_home_servers: 2
+compose_num_of_proxy_servers: 1
+# Load generator Jinja template vars
+load_gen_num_of_dst_servers: 1
+load_gen_dst_server_name: proxy
+# Proxy server Jinja template vars
+proxy_num_of_dst_servers: 2
+proxy_dst_server_name: homeserver
+# General Jinja template vars
+listener_type: file
--- /dev/null
+jinja_templates_to_render:
+ - environments/configs/freeradius/homeserver/radiusd.conf.j2
+ - environments/configs/freeradius/load-generator/radiusd.conf.j2
+ - environments/docker-compose/env-5hs-autoaccept.yml.j2
+
+# Docker compose Jinja template vars
+compose_num_of_home_servers: 5
+# Load generator Jinja template vars
+load_gen_num_of_dst_servers: 5
+load_gen_dst_server_name: homeserver
+# General Jinja template vars
+listener_type: file
--- /dev/null
+timeout: 370
+state_order: sequence
+states:
+ state_1:
+ description: 5 minute load-generator test building off of baseline load-generator -> 1 proxy -> 2 home servers test. Home servers auto-accept requests.
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ #
+ # proto_load configuration via environment variables
+ #
+
+ export TEST_CONF_START_PPS=300
+ export TEST_CONF_MAX_PPS=350
+ # Duration must be equal or greater than max pps for proto_load to calculate "pps_accepted" effectively
+ export TEST_CONF_DURATION=60
+ export TEST_CONF_STEP=10
+ export TEST_CONF_PARALLEL=1
+ export TEST_CONF_MAX_BACKLOG=1000
+ export TEST_CONF_REPEAT="no"
+
+ TEST_CONF_NUM_MESSAGES=0
+ for ((pps=$TEST_CONF_START_PPS; pps<=$TEST_CONF_MAX_PPS; pps+=$TEST_CONF_STEP)); do
+ TEST_CONF_NUM_MESSAGES=$((TEST_CONF_NUM_MESSAGES + TEST_CONF_DURATION * pps))
+ done
+ export TEST_CONF_NUM_MESSAGES
+
+ #
+ # Delay to allow the rest of the environment to startup before starting load generation
+ #
+ sleep 20
+
+ #
+ # Starting load-generator server which will generate traffic based on env configuration
+ # from above.
+ #
+ /docker-entrypoint.sh &
+ tail -f /dev/null
+
+ detach: true
+ verify:
+ timeout: 360
+ trigger_mode: unordered
+ state_2:
+ description: Check load-generator Status-Server stats
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ # This command will query the load-generator's built-in Status-Server for statistics. Port 1820 configured to be used for Status-Server.
+ printf 'User-Name := "testuser"\nUser-Password := "testpass"' | /usr/bin/radclient -x localhost:1820 status testing123
+ verify:
+ timeout: 5
+ trigger_mode: unordered
+ triggers:
+ - load-generator-status-server:
+ json:
+ Status-Server-Result:
+ pattern:
+ reg_pattern: PASS
+ Access-Request-Counter:
+ pattern:
+ reg_pattern: (\d+)
+ Access-Accept-Counter:
+ pattern:
+ reg_pattern: (\d+)
--- /dev/null
+timeout: 50
+state_order: sequence
+states:
+ state_1:
+ description: Baseline load-generator test for load-generator -> 1 proxy -> 2 home servers. Home servers auto-accept requests.
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ #
+ # proto_load configuration via environment variables
+ #
+
+ export TEST_CONF_START_PPS=5
+ export TEST_CONF_MAX_PPS=10
+ # Duration must be equal or greater than max pps for proto_load to calculate "pps_accepted" effectively
+ export TEST_CONF_DURATION=5
+ export TEST_CONF_STEP=5
+ export TEST_CONF_PARALLEL=1
+ export TEST_CONF_MAX_BACKLOG=1000
+ export TEST_CONF_REPEAT="no"
+
+ TEST_CONF_NUM_MESSAGES=0
+ for ((pps=$TEST_CONF_START_PPS; pps<=$TEST_CONF_MAX_PPS; pps+=$TEST_CONF_STEP)); do
+ TEST_CONF_NUM_MESSAGES=$((TEST_CONF_NUM_MESSAGES + TEST_CONF_DURATION * pps))
+ done
+ export TEST_CONF_NUM_MESSAGES
+
+ #
+ # Delay to allow the rest of the environment to startup before starting load generation.
+ #
+ sleep 20
+
+ #
+ # Starting load-generator server which will generate traffic based on env configuration
+ # from above.
+ #
+ printf "Starting load-generator with the following configuration:\n"
+ printf " Start PPS: %s\n" "$TEST_CONF_START_PPS"
+ printf " Max PPS: %s\n" "$TEST_CONF_MAX_PPS"
+ printf " Duration (secs): %s\n" "$TEST_CONF_DURATION"
+ printf " Step PPS: %s\n" "$TEST_CONF_STEP"
+ printf " Parallel: %s\n" "$TEST_CONF_PARALLEL"
+ printf " Max Backlog: %s\n" "$TEST_CONF_MAX_BACKLOG"
+ printf " Num Messages: %s\n" "$TEST_CONF_NUM_MESSAGES"
+
+ freeradius &
+
+ tail -f /dev/null
+
+ detach: true
+ verify:
+ timeout: 40
+ trigger_mode: unordered
+ state_2:
+ description: Check load-generator Status-Server stats
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ # This command will query the load-generator's built-in Status-Server for statistics. Port 1820 configured to be used for Status-Server.
+ printf 'User-Name := "testuser"\nUser-Password := "testpass"' | /usr/bin/radclient -x localhost:1820 status testing123
+ verify:
+ timeout: 5
+ trigger_mode: unordered
+ triggers:
+ - load-generator-status-server:
+ json:
+ Status-Server-Result:
+ pattern:
+ reg_pattern: PASS
+ Access-Request-Counter:
+ pattern:
+ reg_pattern: (\d+)
+ Access-Accept-Counter:
+ pattern:
+ reg_pattern: (\d+)
--- /dev/null
+timeout: 370
+state_order: sequence
+states:
+ state_1:
+ description: 5 minute load-generator test building off of baseline load-generator -> 5 home servers test. Home servers auto-accept requests.
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ #
+ # proto_load configuration via environment variables
+ #
+
+ export TEST_CONF_START_PPS=300
+ export TEST_CONF_MAX_PPS=350
+ # Duration must be equal or greater than max pps for proto_load to calculate "pps_accepted" effectively
+ export TEST_CONF_DURATION=60
+ export TEST_CONF_STEP=10
+ export TEST_CONF_PARALLEL=1
+ export TEST_CONF_MAX_BACKLOG=1000
+ export TEST_CONF_REPEAT="no"
+
+ TEST_CONF_NUM_MESSAGES=0
+ for ((pps=$TEST_CONF_START_PPS; pps<=$TEST_CONF_MAX_PPS; pps+=$TEST_CONF_STEP)); do
+ TEST_CONF_NUM_MESSAGES=$((TEST_CONF_NUM_MESSAGES + TEST_CONF_DURATION * pps))
+ done
+ export TEST_CONF_NUM_MESSAGES
+
+ #
+ # Delay to allow the rest of the environment to startup before starting load generation.
+ #
+ sleep 20
+
+ #
+ # Starting load-generator server which will generate traffic based on env configuration
+ # from above.
+ #
+ printf "Starting load-generator with the following configuration:\n"
+ printf " Start PPS: %s\n" "$TEST_CONF_START_PPS"
+ printf " Max PPS: %s\n" "$TEST_CONF_MAX_PPS"
+ printf " Duration (secs): %s\n" "$TEST_CONF_DURATION"
+ printf " Step PPS: %s\n" "$TEST_CONF_STEP"
+ printf " Parallel: %s\n" "$TEST_CONF_PARALLEL"
+ printf " Max Backlog: %s\n" "$TEST_CONF_MAX_BACKLOG"
+ printf " Num Messages: %s\n" "$TEST_CONF_NUM_MESSAGES"
+
+ freeradius &
+
+ tail -f /dev/null
+
+ detach: true
+ verify:
+ timeout: 360
+ trigger_mode: unordered
+ state_2:
+ description: Check load-generator Status-Server stats
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ # This command will query the load-generator's built-in Status-Server for statistics. Port 1820 configured to be used for Status-Server.
+ printf 'User-Name := "testuser"\nUser-Password := "testpass"' | /usr/bin/radclient -x localhost:1820 status testing123
+ verify:
+ timeout: 5
+ trigger_mode: unordered
+ triggers:
+ - load-generator-status-server:
+ json:
+ Status-Server-Result:
+ pattern:
+ reg_pattern: PASS
+ Access-Request-Counter:
+ pattern:
+ reg_pattern: (\d+)
+ Access-Accept-Counter:
+ pattern:
+ reg_pattern: (\d+)
--- /dev/null
+timeout: 50
+state_order: sequence
+states:
+ state_1:
+ description: Baseline load-generator test for load-generator -> 5 home servers. Home servers auto-accept requests.
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ #
+ # proto_load configuration via environment variables
+ #
+
+ export TEST_CONF_START_PPS=5
+ export TEST_CONF_MAX_PPS=10
+ # Duration must be equal or greater than max pps for proto_load to calculate "pps_accepted" effectively
+ export TEST_CONF_DURATION=5
+ export TEST_CONF_STEP=5
+ export TEST_CONF_PARALLEL=1
+ export TEST_CONF_MAX_BACKLOG=1000
+ export TEST_CONF_REPEAT="no"
+
+ TEST_CONF_NUM_MESSAGES=0
+ for ((pps=$TEST_CONF_START_PPS; pps<=$TEST_CONF_MAX_PPS; pps+=$TEST_CONF_STEP)); do
+ TEST_CONF_NUM_MESSAGES=$((TEST_CONF_NUM_MESSAGES + TEST_CONF_DURATION * pps))
+ done
+ export TEST_CONF_NUM_MESSAGES
+
+ #
+ # Delay to allow the rest of the environment to startup before starting load generation.
+ #
+ sleep 20
+
+ #
+ # Starting load-generator server which will generate traffic based on env configuration
+ # from above.
+ #
+ printf "Starting load-generator with the following configuration:\n"
+ printf " Start PPS: %s\n" "$TEST_CONF_START_PPS"
+ printf " Max PPS: %s\n" "$TEST_CONF_MAX_PPS"
+ printf " Duration (secs): %s\n" "$TEST_CONF_DURATION"
+ printf " Step PPS: %s\n" "$TEST_CONF_STEP"
+ printf " Parallel: %s\n" "$TEST_CONF_PARALLEL"
+ printf " Max Backlog: %s\n" "$TEST_CONF_MAX_BACKLOG"
+ printf " Num Messages: %s\n" "$TEST_CONF_NUM_MESSAGES"
+
+ freeradius &
+
+ tail -f /dev/null
+
+ detach: true
+ verify:
+ timeout: 40
+ trigger_mode: unordered
+ state_2:
+ description: Check load-generator Status-Server stats
+ host:
+ load-generator:
+ actions:
+ - execute_command:
+ command: |
+ # This command will query the load-generator's built-in Status-Server for statistics. Port 1820 configured to be used for Status-Server.
+ printf 'User-Name := "testuser"\nUser-Password := "testpass"' | /usr/bin/radclient -x localhost:1820 status testing123
+ verify:
+ timeout: 5
+ trigger_mode: unordered
+ triggers:
+ - load-generator-status-server:
+ json:
+ Status-Server-Result:
+ pattern:
+ reg_pattern: PASS
+ Access-Request-Counter:
+ pattern:
+ reg_pattern: (\d+)
+ Access-Accept-Counter:
+ pattern:
+ reg_pattern: (\d+)