diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3e572b..006984f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,14 +14,14 @@ jobs: build: runs-on: ${{ matrix.runner }} outputs: - # image-arm64: ${{ steps.gen-output.outputs.image-arm64 }} + image-arm64: ${{ steps.gen-output.outputs.image-arm64 }} image-x64: ${{ steps.gen-output.outputs.image-x64 }} strategy: fail-fast: false matrix: runner: - ubuntu-24.04 - # - ubuntu-24.04-arm + - ubuntu-24.04-arm steps: - name: Checkout code uses: actions/checkout@v4 @@ -77,7 +77,7 @@ jobs: runs-on: ubuntu-24.04 needs: build env: - # DOCKER_APP_IMAGE_ARM64: ${{ needs.build.outputs.image-arm64 }} + DOCKER_APP_IMAGE_ARM64: ${{ needs.build.outputs.image-arm64 }} DOCKER_APP_IMAGE_X64: ${{ needs.build.outputs.image-x64 }} outputs: image: ${{ steps.meta.outputs.tags }} @@ -106,7 +106,7 @@ jobs: run: | docker buildx imagetools create \ --tag "$DOCKER_METADATA_OUTPUT_TAGS" \ - "$DOCKER_APP_IMAGE_X64" + "$DOCKER_APP_IMAGE_ARM64" "$DOCKER_APP_IMAGE_X64" test: runs-on: ubuntu-24.04 @@ -143,7 +143,7 @@ jobs: - name: Run tests if: ${{ always() }} run: | - docker compose run app /opt/app/test/run_tests.py + docker compose exec app venv/bin/python test/run_tests.py env: WOWZA_LICENSE_KEY: ${{ secrets.WOWZA_LICENSE_KEY }} @@ -153,7 +153,8 @@ jobs: docker compose cp app:/opt/app/artifacts ./ docker compose logs > artifacts/docker-compose-services.log docker compose config > artifacts/docker-compose.merged.yml - + env: + WOWZA_LICENSE_KEY: EZZZZ-INTEN-TIONA-LLYRE-DACT-EDFROM-BUILDLOGS - name: Upload the test report if: ${{ always() }} uses: actions/upload-artifact@v4 diff --git a/Dockerfile b/Dockerfile index e32f62b..9cf6f81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # Target: base # -FROM wowzamedia/wowza-streaming-engine-linux:4.8.25 AS base +FROM wowzamedia/wowza-streaming-engine-linux:4.9.6 AS base # ============================================================================= # Ports @@ -27,28 +27,34 @@ RUN apt-get update -qq RUN apt-get install -y --no-install-recommends \ curl \ dnsutils \ - iputils-ping \ - lsb-core + iputils-ping # ============================================================================= # Java -# The upstream Wowza image ships with OpenJDK 9.0.4+11, which is not a -# long-term support release and, importantly, doesn't know about containers: -# https://www.wowza.com/community/t/creating-a-production-worthy-docker-image/53957/3 +# The upstream Wowza image may ship with an outdated OpenJDK, and +# previously (even as of 4.8.25) did not include an LTS release or +# one that was aware of containers. # # Luckily, Wowza supports manually replacing the JRE: # https://www.wowza.com/docs/manually-install-and-troubleshoot-java-on-wowza-streaming-engine +# ... so this updates to the latest version of OpenJDK 21 (LTS). # # (Unfortunately, this by itself isn't enough to get Wowza to properly # calculate its own max heap size, so we still need to set that explicitly # in Tune.xml. Possibly a future version of Wowza will be clever enough to # use -XX:MaxRAMPercentage instead.) -RUN apt-get install -y --no-install-recommends openjdk-11-jre-headless +RUN apt-get install -y --no-install-recommends openjdk-21-jre-headless RUN rm -rf /usr/local/WowzaStreamingEngine/java -RUN ln -s /usr/lib/jvm/java-11-openjdk-amd64 /usr/local/WowzaStreamingEngine/java + +# for some reason, OpenJDK's default directory includes the architecture +# name and does not symlink it to something more straightforward like +# /usr/lib/jvm/java-21-openjdk so we have to detect the architecture + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) ln -s "/usr/lib/jvm/java-21-openjdk-${arch}" /usr/local/WowzaStreamingEngine/java # ============================================================================= # Global configuration @@ -66,6 +72,10 @@ RUN usermod -u $APP_UID $APP_USER && \ # transfer now-orphaned files to new wowza user (-h to chown symlinks) RUN find / -xdev -nouser -exec chown -h $APP_USER:$APP_USER {} \; +# set variables to be used by envsubst to overwrite the default wowza config +ENV SUPERVISORD_PID_FILE=/tmp/supervisord.pid +ENV SUPERVISORD_SOCKET_FILE=/tmp/supervisor.sock + # ============================================================================= # Set working directory @@ -74,12 +84,13 @@ WORKDIR /opt/app # ============================================================================= # Tests -RUN apt-get install -y --no-install-recommends python3-pip -RUN pip3 install unittest-xml-reporting +RUN apt-get install -y --no-install-recommends python3-pip python3-venv +RUN python3 -m venv venv +RUN venv/bin/pip3 install unittest-xml-reporting COPY --chown=$APP_USER test /opt/app/test -# Put artifacts where Jenkins can get at them +# Put artifacts where Github Actions can get at them RUN mkdir /opt/app/artifacts && \ chown $APP_USER:$APP_USER /opt/app/artifacts @@ -96,8 +107,22 @@ RUN for app in vod live; \ # Copy our scripts, configs, templates, etc. into the container COPY --chown=$APP_USER WowzaStreamingEngine /usr/local/WowzaStreamingEngine COPY --chown=$APP_USER log4j-templates /opt/app/log4j-templates +COPY --chown=$APP_USER supervisor_templates /opt/app/supervisor_templates COPY --chown=$APP_USER bin /opt/app/bin +# create supervisord config files from templates +RUN apt-get install -y --no-install-recommends gettext +RUN envsubst < \ + supervisor_templates/supervisord.conf.tmpl > \ + /etc/supervisor/supervisord.conf +RUN envsubst < \ + supervisor_templates/conf.d/WowzaStreamingEngine.conf.tmpl > \ + /etc/supervisor/conf.d/WowzaStreamingEngine.conf +RUN envsubst < \ + supervisor_templates/conf.d/WowzaStreamingEngineManager.conf.tmpl > \ + /etc/supervisor/conf.d/WowzaStreamingEngineManager.conf + + # ============================================================================= # Additional Java libraries @@ -138,7 +163,11 @@ RUN apt-get remove -y zip USER $APP_USER +# ============================================================================= +# Healthcheck +HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=10 CMD curl -s http://localhost:8087 > /dev/null || exit 1 + # ============================================================================= # Default command -CMD ["/opt/app/bin/docker-entrypoint.sh"] +ENTRYPOINT ["/opt/app/bin/docker-entrypoint.sh"] diff --git a/WowzaStreamingEngine/bin/startup.sh b/WowzaStreamingEngine/bin/startup.sh new file mode 100755 index 0000000..cc51631 --- /dev/null +++ b/WowzaStreamingEngine/bin/startup.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# this is a BerkeleyLibrary modified version of the WSE startup script + +# check for root access. If not, put up message and exit +# if [ "$(/usr/bin/id -u)" -ne "0" ] ; then +# echo "The Wowza Streaming Engine requires root access to start. Please run script again using sudo." +# exit +# fi + +systemctl >> /dev/null 2>&1 +if [ $? -eq 0 ]; then + # Restart XRM service + SERVICE_NAME="xrmd.service" + systemctl list-units --full -all | grep -Fq $SERVICE_NAME + + if [ $? -eq 0 ]; then + echo "Restarting XRM service" + systemctl restart $SERVICE_NAME + . /opt/xilinx/xcdr/setup.sh + fi +fi + +. /usr/local/WowzaStreamingEngine/bin/setenv.sh +mode=standalone +if [ "$#" -eq 1 ]; +then +mode=$1 +fi + +#chmod 600 /usr/local/WowzaStreamingEngine/conf/jmxremote.password +#chmod 600 /usr/local/WowzaStreamingEngine/conf/jmxremote.access + +# NOTE: Here you can configure the JVM's built in JMX interface. +# See the "Server Management Console and Monitoring" chapter +# of the "User's Guide" for more information on how to configure the +# remote JMX interface in the [install-dir]/conf/Server.xml file. + +JMXOPTIONS=-Dcom.sun.management.jmxremote=true +#JMXOPTIONS="$JMXOPTIONS -Djava.rmi.server.hostname=192.168.1.7" +#JMXOPTIONS="$JMXOPTIONS -Dcom.sun.management.jmxremote.port=1099" +#JMXOPTIONS="$JMXOPTIONS -Dcom.sun.management.jmxremote.authenticate=true" +#JMXOPTIONS="$JMXOPTIONS -Dcom.sun.management.jmxremote.ssl=false" +#JMXOPTIONS="$JMXOPTIONS -Dcom.sun.management.jmxremote.password.file=$WMSCONFIG_HOME/conf/jmxremote.password" +#JMXOPTIONS="$JMXOPTIONS -Dcom.sun.management.jmxremote.access.file=$WMSCONFIG_HOME/conf/jmxremote.access" + +ulimit -n 64000 > /dev/null 2>&1 + +rc=144 +while [ $rc -eq 144 ] +do + +WMSTUNE_OPTS=`$WMSAPP_HOME/bin/tune.sh $mode` +export LD_PRELOAD=`$WMSAPP_HOME/bin/ldpreload.sh` + +# log interceptor com.wowza.wms.logging.LogNotify - see Javadocs for ILogNotify + +$_EXECJAVA $WMSTUNE_OPTS $JMXOPTIONS -Dorg.slf4j.simpleLogger.defaultLogLevel=warn -Dcom.wowza.wms.runmode="$mode" -Dcom.wowza.wms.native.base="linux" -Dlog4j.configurationFile="$WMSCONFIG_HOME/conf/log4j2-config.xml" -Dcom.wowza.wms.AppHome="$WMSAPP_HOME" -Dcom.wowza.wms.ConfigURL="$WMSCONFIG_URL" -Dcom.wowza.wms.ConfigHome="$WMSCONFIG_HOME" -cp $WMSAPP_HOME/bin/wms-bootstrap.jar com.wowza.wms.bootstrap.Bootstrap start + +rc=$? +if [ $rc -ge 10 ] && [ $rc -le 15 ] ; then + WSE_EXIT_CODE=$rc + $_EXECJAVA $WMSTUNE_OPTS $JMXOPTIONS -Dcom.wowza.wms.runmode="$mode" -Dcom.wowza.wms.native.base="linux" -Dlog4j.configurationFile="$WMSCONFIG_HOME/conf/log4j2-config.xml" -Dcom.wowza.wms.AppHome="$WMSAPP_HOME" -Dcom.wowza.wms.ConfigURL="$WMSCONFIG_URL" -Dcom.wowza.wms.ConfigHome="$WMSCONFIG_HOME" -cp $WMSAPP_HOME/bin/wms-bootstrap.jar com.wowza.wms.bootstrap.Bootstrap startLicenseUpdateServer + rc=$? +fi +done diff --git a/bin/docker-entrypoint.sh b/bin/docker-entrypoint.sh index 88c1188..26f8331 100755 --- a/bin/docker-entrypoint.sh +++ b/bin/docker-entrypoint.sh @@ -5,10 +5,6 @@ # Streaming Media Engine Manager in subprocesses, and waits # for them to exit. -# TODO: now that we're running the manager and server in the same container, -# consider getting rid of this (or simplifying it) in favor of the -# upstream container's /sbin/entrypoint.sh, which uses supervisord - BASENAME=$(basename ${BASH_SOURCE}) echo "${BASENAME} running" @@ -26,58 +22,17 @@ if [ ! -z "${WOWZA_ENABLE_DOCUMENTATION_SERVER}" ]; then "${WOWZA_BIN}"/enable-documentation-server.sh fi -# ######################################## -# Start server and manager in background - -# shellcheck source=start-server.sh -echo "Invoking ${WOWZA_BIN}"/start-server.sh -"${WOWZA_BIN}"/start-server.sh & -SERVER_PID=$! -echo "SERVER_PID=${SERVER_PID}" - -echo "Invoking ${WMSMGR_HOME}"/bin/startmgr.sh -"${WMSMGR_HOME}"/bin/startmgr.sh & -MANAGER_PID=$! -echo "MANAGER_PID=${MANAGER_PID}" - -# ######################################## -# Make sure child processes exit cleanly - -ensure_clean_exit() { - trap - SIGINT SIGTERM # clear the trap - echo "Killing process $$ and all subprocesses" - kill -- -$$ -} - -sigint_received() { - echo "SIGINT received" - ensure_clean_exit -} - -sigterm_received() { - echo "SIGTERM received" - ensure_clean_exit -} +# load secrets into wowza env vars. by default, the Wowza entrypoint +# creates the keyfile in question, which is why we pass the variable +# in using a different name. +. "${WOWZA_BIN}/secrets.sh" -trap sigint_received SIGINT -trap sigterm_received SIGTERM +export WSE_MGR_USER=$WOWZA_MANAGER_USER +export WSE_MGR_PASS=$WOWZA_MANAGER_PASSWORD +export WSE_LIC=$WOWZA_LICENSE_KEY # ######################################## -# Wait for manager or server to exit - -wait -n -EXIT_STATUS=$? - -# Whichever exited, kill the other one -if ! kill -0 $SERVER_PID 2> /dev/null; then - echo "Wowza Streaming Engine (PID ${SERVER_PID}) exited with ${EXIT_STATUS}" - echo "Stopping Wowza Streaming Engine Manager (PID ${MANAGER_PID})" - kill -- -$MANAGER_PID 2> /dev/null -elif ! kill -0 $MANAGER_PID 2> /dev/null; then - echo "Wowza Streaming Engine Manager (PID ${MANAGER_PID}) exited with ${EXIT_STATUS}" - echo "Stopping Wowza Streaming Engine (PID ${SERVER_PID})" - kill -- -$SERVER_PID 2> /dev/null -fi +# Start server and manager by handing off to Wowza's entrypoint -echo "Exiting with status ${EXIT_STATUS}" -exit $EXIT_STATUS +echo Invoking Wowza\'s /sbin/entrypoint.sh +exec /sbin/entrypoint.sh "$@" diff --git a/docker-compose.yml b/docker-compose.yml index e035943..a8c39a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: - WOWZA_MANAGER_USER=${WOWZA_MANAGER_USER} - WOWZA_MANAGER_PASSWORD=${WOWZA_MANAGER_PASSWORD} - WOWZA_ENABLE_DOCUMENTATION_SERVER=yes - # Uncommment these when editing / running tests locally. + # Uncomment these when editing / running tests locally. # (Note: slows performance on macOS Catalina, for some reason.) # volumes: # - ./test:/opt/app/test diff --git a/lib-ucblit/log4j-api-2.17.0.jar b/lib-ucblit/log4j-api-2.17.0.jar deleted file mode 100644 index e39dab0..0000000 Binary files a/lib-ucblit/log4j-api-2.17.0.jar and /dev/null differ diff --git a/lib-ucblit/log4j-core-2.17.0.jar b/lib-ucblit/log4j-core-2.17.0.jar deleted file mode 100644 index b853057..0000000 Binary files a/lib-ucblit/log4j-core-2.17.0.jar and /dev/null differ diff --git a/lib-ucblit/log4j-layout-template-json-2.17.0.jar b/lib-ucblit/log4j-layout-template-json-2.17.0.jar deleted file mode 100644 index 69e06a1..0000000 Binary files a/lib-ucblit/log4j-layout-template-json-2.17.0.jar and /dev/null differ diff --git a/lib-ucblit/log4j-layout-template-json-2.23.1.jar b/lib-ucblit/log4j-layout-template-json-2.23.1.jar new file mode 100644 index 0000000..6984b4d Binary files /dev/null and b/lib-ucblit/log4j-layout-template-json-2.23.1.jar differ diff --git a/supervisor_templates/conf.d/WowzaStreamingEngine.conf.tmpl b/supervisor_templates/conf.d/WowzaStreamingEngine.conf.tmpl new file mode 100644 index 0000000..ba8fa17 --- /dev/null +++ b/supervisor_templates/conf.d/WowzaStreamingEngine.conf.tmpl @@ -0,0 +1,10 @@ +[program:WowzaStreamingEngine] +priority=10 +directory=/usr/local/WowzaStreamingEngine/bin +command=/usr/local/WowzaStreamingEngine/bin/startup.sh +user=${APP_USER} +autostart=true +autorestart=true +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes = 0 +redirect_stderr=true diff --git a/supervisor_templates/conf.d/WowzaStreamingEngineManager.conf.tmpl b/supervisor_templates/conf.d/WowzaStreamingEngineManager.conf.tmpl new file mode 100644 index 0000000..41592d5 --- /dev/null +++ b/supervisor_templates/conf.d/WowzaStreamingEngineManager.conf.tmpl @@ -0,0 +1,10 @@ +[program:WowzaStreamingEngineManager] +priority=20 +directory=/usr/local/WowzaStreamingEngine/manager/bin +command=/usr/local/WowzaStreamingEngine/manager/bin/startmgr.sh +user=${APP_USER} +autostart=true +autorestart=true +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes = 0 +redirect_stderr=true diff --git a/supervisor_templates/supervisord.conf.tmpl b/supervisor_templates/supervisord.conf.tmpl new file mode 100644 index 0000000..9d4168b --- /dev/null +++ b/supervisor_templates/supervisord.conf.tmpl @@ -0,0 +1,30 @@ +; supervisor config file + +[unix_http_server] +file=${SUPERVISORD_SOCKET_FILE} ; (the path to the socket file) +chmod=0700 ; socket file mode (default 0700) + +[supervisord] +logfile=/dev/null ; supervisor logs to stdout +logfile_maxbytes = 0 ; forcibly disable log rotation +pidfile=${SUPERVISORD_PID_FILE} ; (supervisord pidfile;default supervisord.pid) +childlogdir=/usr/local/supervisor ; ('AUTO' child log dir, default $TEMP) +user=${APP_USER} + +; the below section must remain in the config file for RPC +; (supervisorctl/web interface) to work, additional interfaces may be +; added by defining them in separate rpcinterface: sections +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix://${SUPERVISORD_SOCKET_FILE} ; use a unix:// URL for a unix socket + +; The [include] section can just contain the "files" setting. This +; setting can list multiple files (separated by whitespace or +; newlines). It can also contain wildcards. The filenames are +; interpreted as relative to this file. Included files *cannot* +; include files themselves. + +[include] +files = /etc/supervisor/conf.d/*.conf diff --git a/test/README.md b/test/README.md index 1823f43..443bae5 100644 --- a/test/README.md +++ b/test/README.md @@ -34,13 +34,13 @@ From the project root directory, in another terminal, you can: ## Testing in a standalone container -Alternatively, you can run the tests and server in the same container (as the Jenkins build +Alternatively, you can run the tests and server in the same container (as the Github Actions build does). Note that this does not require exposing any ports to the host. ```sh -docker-compose run wowza /opt/app/test/run_tests.py +docker-compose run app /opt/app/test/run_tests.py ``` -In this case, remember to rebuild the `wowza` container before testing, to make sure your latest +In this case, remember to rebuild the `app` container before testing, to make sure your latest additions to the test suite are included. diff --git a/test/run_tests.py b/test/run_tests.py index 02a8bf8..bbecb7e 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -4,26 +4,18 @@ """run_tests.py To be used as a Docker command when running the Wowza server and tests in a -standalone container. This script: - -1. starts the Wowza server using ``bin/start-server.sh`` -2. waits for the server to start (as indicated by the ``REST API: ready`` - message in the server's output) -3. runs the tests in ``wowza_test.py`` -4. stops the server +standalone container. This script runs the tests in ``wowza_test.py`` and +generates JUnit-compatible xml reports. """ import os import pathlib -import subprocess import sys from tests import wowza_test -os.environ['WOWZA_MANAGER_PASSWORD'] = 'wowza' # TODO: is this a good idea? APP_ROOT = pathlib.Path('/opt/app/') -SERVER_SH = APP_ROOT / 'bin' / 'start-server.sh' REPORTS_DIR = APP_ROOT / 'artifacts' / 'unittest' TIMEOUT_SECONDS = 60 @@ -32,43 +24,19 @@ def log(msg): print(msg, file=sys.stderr) -def start_server(): - log(f"Starting Wowza server with %s" % SERVER_SH) - process = subprocess.Popen(SERVER_SH, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8') - for line in process.stdout: - print(line.rstrip()) - if 'REST API: ready' in line: - log("Wowza server started") - break - return process - - def ensure_reports_dir(): REPORTS_DIR.mkdir(parents=True, exist_ok=True) return REPORTS_DIR def main(): - process = start_server() - status = process.poll() - try: - if status is not None: - log(f"%s exited with %d" % (SERVER_SH, status)) - exit(1) - - reports_dir = ensure_reports_dir() - report_file = reports_dir / 'wowza_test.xml' - - log(f"Writing test report to {report_file}") - result = wowza_test.WowzaTest.runTestsWithXMLReport(report_file) - if not result.wasSuccessful(): - exit(1) + reports_dir = ensure_reports_dir() + report_file = reports_dir / 'wowza_test.xml' - finally: - log("Stopping Wowza server") - process.terminate() - process.wait(TIMEOUT_SECONDS) - log("Wowza server stopped") + log(f"Writing test report to {report_file}") + result = wowza_test.WowzaTest.runTestsWithXMLReport(report_file) + if not result.wasSuccessful(): + exit(1) if __name__ == "__main__":