diff --git a/.github/workflows/buiild.yml b/.github/workflows/buiild.yml index a351a5f..bb54f21 100644 --- a/.github/workflows/buiild.yml +++ b/.github/workflows/buiild.yml @@ -1,4 +1,4 @@ -name: Build and publish image to ghcr.io/epics-containers +name: Check that ibek-support support modules will build in a container on: push: @@ -45,6 +45,11 @@ jobs: if: runner.os == 'macos' uses: docker-practice/actions-setup-docker@fd7ecdac7caf8e2c541a48c9bcc280aae59a8461 + # Dockerfiles expect the repo to be in a subdirectory as its normally a submodule + # and it gets IOC the scripts from that super-module + - name: Move IOC + run: mv _test_ioc ../ioc + - name: Log in to GitHub Docker Registry uses: docker/login-action@v1 with: @@ -57,4 +62,4 @@ jobs: ARCH: ${{ matrix.architecture }} PLATFORM: ${{ matrix.platform }} CACHE: ${{ env.CACHE }} - run: tests/_test_support_build.sh + run: .submodule/tests/_test_support_build.sh diff --git a/_test_ioc/config/st.cmd b/_test_ioc/config/st.cmd new file mode 100755 index 0000000..2c79bc1 --- /dev/null +++ b/_test_ioc/config/st.cmd @@ -0,0 +1,32 @@ +# EXAMPLE IOC Instance to demonstrate the ADSimDetector Generic IOC + +cd "$(TOP)" + +dbLoadDatabase "dbd/ioc.dbd" +ioc_registerRecordDeviceDriver(pdbbase) + +# simDetectorConfig(portName, maxSizeX, maxSizeY, dataType, maxBuffers, maxMemory) +simDetectorConfig("EXAMPLE.CAM", 2560, 2160, 1, 50, 0) + +# NDPvaConfigure(portName, queueSize, blockingCallbacks, NDArrayPort, NDArrayAddr, pvName, maxMemory, priority, stackSize) +NDPvaConfigure("EXAMPLE.PVA", 2, 0, "EXAMPLE.CAM", 0, "EXAMPLE:IMAGE", 0, 0, 0) + +# NDFileHDF5Configure(portName, queueSize, blockingCallbacks, NDArrayPort, NDArrayAddr) +NDFileHDF5Configure("EXAMPLE.HDF", 2, 0, "EXAMPLE.CAM", 0) + +startPVAServer + +# instantiate Database records for Sim Detector +dbLoadRecords (simDetector.template, "P=EXAMPLE, R=:CAM:, PORT=EXAMPLE.CAM, TIMEOUT=1, ADDR=0") +dbLoadRecords (NDPva.template, "P=EXAMPLE, R=:PVA:, PORT=EXAMPLE.PVA, ADDR=0, TIMEOUT=1, NDARRAY_PORT=EXAMPLE.CAM, NDARRAY_ADR=0, ENABLED=1") +dbLoadRecords (NDFileHDF5.template, "P=EXAMPLE, R=:HDF:, PORT=EXAMPLE.HDF, ADDR=0, TIMEOUT=1, XMLSIZE=2048, NDARRAY_PORT=EXAMPLE.CAM, NDARRAY_ADDR=0, ENABLED=1, SCANRATE=I/O Intr") + +# also make Database records for DEVIOCSTATS +dbLoadRecords(iocAdminSoft.db, "IOC=EXAMPLE") +dbLoadRecords(iocAdminScanMon.db, "IOC=EXAMPLE") + +# start IOC shell +iocInit + +# poke some records +dbpf "EXAMPLE:CAM:AcquirePeriod", "0.1" diff --git a/_test_ioc/liveness.sh b/_test_ioc/liveness.sh new file mode 100755 index 0000000..861e5c4 --- /dev/null +++ b/_test_ioc/liveness.sh @@ -0,0 +1,40 @@ +#!/bin/bash +TOP=/repos/epics/ioc +cd ${TOP} +CONFIG_DIR=${TOP}/config + +set -ex + +CONFIG_DIR=/repos/epics/ioc/config +THIS_SCRIPT=$(realpath ${0}) +override=${CONFIG_DIR}/liveness.sh + +if [[ -f ${override} && ${override} != ${THIS_SCRIPT} ]]; then + exec bash ${override} +fi + +if [[ ${K8S_IOC_LIVENESS_ENABLED} != 'true' ]]; then + exit 0 +fi + +# use devIOCStats UPTIME as the default liveness PV +# but allow override from the environment +K8S_IOC_PV=${K8S_IOC_PV:-"${IOC_PREFIX}:UPTIME"} + +# use default CA PORT or override from the environment +K8S_IOC_PORT=${K8S_IOC_PORT:-5064} + +export EPICS_CA_ADDR_LIST=${K8S_IOC_ADDRESS} +export EPICS_CA_SERVER_PORT=${K8S_IOC_PORT} + +# verify that the IOC is running +if caget ${K8S_IOC_PV} ; then + exit 0 +else + # send the error message to the container's main process stdout + echo "Liveness check failed for ${IOC_NAME}" > /proc/1/fd/1 + echo "Failing PV: ${K8S_IOC_PV}" > /proc/1/fd/2 + echo "Address list: ${EPICS_CA_ADDR_LIST}" > /proc/1/fd/2 + echo "CA Port: ${EPICS_CA_SERVER_PORT}" > /proc/1/fd/2 + exit 1 +fi diff --git a/_test_ioc/start.sh b/_test_ioc/start.sh new file mode 100755 index 0000000..43bba74 --- /dev/null +++ b/_test_ioc/start.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# +# The epics-containers IOC startup script. +# +# This script is used to start an EPICS IOC in a Kubernetes pod. Implementers +# of generic IOCs are free to replace this script with their own. But +# this script as is should work for most IOCs. +# +# When a generic IOC runs in a kubernetes pod it is expected to have +# a config folder that defines the IOC instance. +# The helm chart for the generic IOC will mount the config folder +# as a configMap and this turns a generic IOC into aspecific IOC instance. +# +# Here we support the following set of options for the contents of +# the config folder: +# +# 1. start.sh ****************************************************************** +# If the config folder contains a start.sh script it will be executed. +# This allows the instance implementer to provide a conmpletely custom +# startup script. +# +# 2. ioc.yaml ************************************************************* +# If the config folder contains an ioc.yaml file we invoke the ibek tool to +# generate the startup script and database. Then launch with the generated +# startup script. +# +# 3. st.cmd + ioc.subst ********************************************************* +# If the config folder contains a st.cmd script and a ioc.subst file then +# optionally generate ioc.db from the ioc.subst file and use the st.cmd script +# as the IOC startup script. Note that the expanded database file will +# be generated in /tmp/ioc.db +# +# 4. empty config folder ******************************************************* +# If the config folder is empty then this IOC will launch the example in +# ./example folder +# +# RTEMS IOCS - RTEMS IOC startup files can be generated using 2,3,4 above. For +# RTEMS we do not execute the ioc inside of the pod. Instead we: +# - copy the IOC directory to the RTEMS mount point +# - send a reboot command to the RTEMS crate +# - start a telnet session to the RTEMS IOC console +# + +set -x -e + +# environment setup ************************************************************ + +TOP=$(realpath $(dirname $0)) +cd ${TOP} +CONFIG_DIR=${TOP}/config + +# add module paths to environment for use in ioc startup script +source ${SUPPORT}/configure/RELEASE.shell + +# override startup script +override=${CONFIG_DIR}/start.sh +# source YAML for IOC Builder for EPICS on Kubernetes (ibek) +ibek_src=${CONFIG_DIR}/ioc.yaml +# Startup script for EPICS IOC generated by ibek +ioc_startup=${CONFIG_DIR}/st.cmd +# expanded database file +epics_db=/tmp/ioc.db + + +# 1. start.sh ****************************************************************** + +if [ -f ${override} ]; then + exec ${override} + +# 2. ioc.yaml ****************************************************************** + +elif [ -f ${ibek_src} ]; then + # Database generation script generated by ibek + db_src=/tmp/make_db.sh + final_ioc_startup=/tmp/st.cmd + + # get ibek the support yaml files this ioc's support modules + defs=/ctools/*/*.ibek.support.yaml + ibek build-startup ${ibek_src} ${defs} --out ${final_ioc_startup} --db-out ${db_src} + + # build expanded database using the db_src shell script + if [ -f ${db_src} ]; then + bash ${db_src} > ${epics_db} + fi + +# 3. st.cmd + ioc.substitutions ************************************************ + +elif [ -f ${ioc_startup} ] ; then + if [ -f ${CONFIG_DIR}/ioc.substitutions ]; then + # generate ioc.db from ioc.substitutions, including all templates from SUPPORT + includes=$(for i in ${SUPPORT}/*/db; do echo -n "-I $i "; done) + msi ${includes} -S ${CONFIG_DIR}/ioc.substitutions -o ${epics_db} + fi + final_ioc_startup=${ioc_startup} + +# 4. empty config folder ******************************************************* + +else + final_ioc_startup=${TOP}/example/st.cmd +fi + +# Launch the IOC *************************************************************** + +if [[ ${TARGET_ARCHITECTURE} == "rtems" ]] ; then + echo "RTEMS IOC startup - copying IOC to RTEMS mount point ..." + cp -r ${IOC} ${K8S_IOC_ROOT} + sleep 100 +else + # Execute the IOC binary and pass the startup script as an argument + exec ${IOC}/bin/linux-x86_64/ioc ${final_ioc_startup} +fi diff --git a/_test_ioc/stop.sh b/_test_ioc/stop.sh new file mode 100644 index 0000000..9d92a1e --- /dev/null +++ b/_test_ioc/stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +TOP=/repos/epics/ioc +cd ${TOP} +CONFIG_DIR=${TOP}/config + +override=${CONFIG_DIR}/stop.sh + +if [[ -f ${override} ]]; then + exec bash ${override} +elif [[ ${RTEMS_VME_AUTO_REBOOT} == 'true' ]] ; then + # This is a placeholder for a script that is called when the pod is stopped. + # Placing your own stop.sh in the config directory will override this script. +fi + + diff --git a/tests/Verify.asyn.sh b/tests/Verify.asyn.sh new file mode 100644 index 0000000..ce1ae74 --- /dev/null +++ b/tests/Verify.asyn.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "HELLO WORLD" \ No newline at end of file diff --git a/tests/Verify.busy.sh b/tests/Verify.busy.sh new file mode 100644 index 0000000..8ce1569 --- /dev/null +++ b/tests/Verify.busy.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo TODO add tests for busy support module diff --git a/tests/_test_support_build.sh b/tests/_test_support_build.sh index fd8b061..1597b4b 100755 --- a/tests/_test_support_build.sh +++ b/tests/_test_support_build.sh @@ -14,13 +14,13 @@ # CACHE: the directory to use for caching # PLATFORM: the platform to build for (linux/amd64 or linux/arm64) -THIS_FOLDER=$(dirname $(readlink -f $0)) +set -xe + +THIS_FOLDER=$(dirname ${0}) # ibek-support is normally a submodule of the Docker Context so we need to # pass the container context as the folder above the ibek-support folder CONTEXT=$(realpath ${THIS_FOLDER}/../..) -set -xe - BASE_VERSION="23.9.2" ARCH=${ARCH:-linux} @@ -61,7 +61,7 @@ do_build() { --build-arg BASE=${BASE_VERSION} --build-arg REGISTRY=ghcr.io/epics-containers --target ${TARGET} - --layers + --load -t test_image_only -f ${DOCKERFILE} " @@ -100,8 +100,12 @@ for dockerfile in ${THIS_FOLDER}/Dockerfile*; do # The above check is sufficient to show that the generic IOC will load and # run and that all the necessary runtime libraries are in place. # - # for more detailed testing add a Verify.xxx script where xxx is the + # for more detailed testing add a Verify.xxx.sh script where xxx is the # the same as the suffix on the Dockerfile. See Verify.asyn for an example. + VERIFY=Verify."${dockerfile#*.}" + if [[ -f ${THIS_FOLDER}/${VERIFY} ]] ; then + $THIS_FOLDER/${VERIFY} test_me + fi $docker stop -t0 test_me