diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d4ccac8..9d2fd2d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -15,15 +15,16 @@ "EC_PROJECT": "${localWorkspaceFolderBasename}" }, "features": { + // add quality of life features for developers including git config integration "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": false, - "installOhMyZsh": false, - "installOhMyZshConfig": false, + // don't upgrade to make this similar to the runtime container "upgradePackages": false } }, // IMPORTANT for this devcontainer to work with docker EC_REMOTE_USER must be - // set to vscode. For podman it should be left blank. + // set to vscode. You will run as vscode with full sudo rights. + // For podman it should be left blank. You will run as root but host mounts + // will be owned by your user. "remoteUser": "${localEnv:EC_REMOTE_USER}", "customizations": { "vscode": { @@ -34,7 +35,7 @@ "redhat.vscode-yaml", "ryanluker.vscode-coverage-gutters", "epicsdeb.vscode-epics", - "ms-python.black-formatter" + "charliermarsh.ruff" ] } }, @@ -47,15 +48,17 @@ "runArgs": [ // Allow the container to access the host X11 display and EPICS CA "--net=host", - // Make sure SELinux does not disable with access to host filesystems like tmp + // Make sure SELinux does not disable write access to host filesystems like tmp "--security-opt=label=disable" ], - "workspaceMount": "source=${localWorkspaceFolder},target=/epics/${localWorkspaceFolderBasename},type=bind", - "workspaceFolder": "/epics/${localWorkspaceFolderBasename}", + // Mount the parent of the project folder so we can access peer projects + "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", + // mount in other useful files from the host "mounts": [ - // Mount some useful local files from the user's home directory - // By mounting the parent of the workspace we can work on multiple peer projects - "source=${localWorkspaceFolder}/../,target=/repos,type=bind", + // we also mount the project folder into a know location in the container + // this is where the ibek-support and ioc folders reside in the container build + // in this way the devcontainer and runtime look very similar + "source=${localWorkspaceFolder},target=/epics/generic-source,type=bind", // this provides eternal bash history in and out of the container "source=${localEnv:HOME}/.bash_eternal_history,target=/root/.bash_eternal_history,type=bind", // this bashrc hooks up the .bashrc_dev_container in the following mount diff --git a/.gitignore b/.gitignore index c0a2532..b21013b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,8 @@ repos* # while working on ibek with this project somethimes include it as subfolder ibek -# The ioc source tree is created here from a template inside the generic ioc -# repo. -# Remove from .gitignore if you want to customize the template. -/ioc/ - # dont save workspaces as other users will have differing folders *workspace + +# config folder is there to be replaced there is just a dummy with Readme. +ioc/config diff --git a/Dockerfile b/Dockerfile index e9ed9d2..40b5d6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,13 +6,17 @@ ARG REGISTRY=ghcr.io/epics-containers FROM ${REGISTRY}/epics-base-${TARGET_ARCHITECTURE}-developer:${BASE} AS developer +# The devcontainer mounts the project root to /epics/generic-source +# Using the same location here makes devcontainer/runtime differences transparent. +ENV SOURCE_FOLDER=/epics/generic-source +# connect ioc source folder its know location +RUN ln -s ${SOURCE_FOLDER}/ioc ${IOC} + # Get latest ibek while in development. Will come from epics-base when stable COPY requirements.txt requirements.txt RUN pip install --upgrade -r requirements.txt -# The devcontainer mounts the project root to /epics/ioc-adsimdetector. Using -# the same location here makes devcontainer/runtime differences transparent. -WORKDIR /epics/ioc-pmac/ibek-support +WORKDIR ${SOURCE_FOLDER}/ibek-support # copy the global ibek files COPY ibek-support/_global/ _global @@ -41,15 +45,16 @@ RUN motor/install.sh R7-3-1 COPY ibek-support/pmac/ pmac/ RUN pmac/install.sh 2-6-2b1 -# create IOC source tree, generate Makefile and compile IOC Instance -RUN ibek ioc build +# get the ioc source and build it +COPY ioc ${SOURCE_FOLDER}/ioc +RUN cd ${IOC} && make ##### runtime preparation stage ################################################ FROM developer AS runtime_prep # get the products from the build stage and reduce to runtime assets only -RUN ibek ioc extract-runtime-assets /assets +RUN ibek ioc extract-runtime-assets /assets ${SOURCE_FOLDER}/ibek* ##### runtime stage ############################################################ diff --git a/ibek-support b/ibek-support index 1fc994b..035d2d4 160000 --- a/ibek-support +++ b/ibek-support @@ -1 +1 @@ -Subproject commit 1fc994bda7906a52594b4ce04a91551f39c5fd6e +Subproject commit 035d2d47f7a2a3cbcadecb95294a973fa3fd7246 diff --git a/ioc/.gitignore b/ioc/.gitignore new file mode 100644 index 0000000..f815a88 --- /dev/null +++ b/ioc/.gitignore @@ -0,0 +1,10 @@ +*~ +O.* +bin +dbd +db +data +lib +.svn* +iocs/*IOC* +.idea \ No newline at end of file diff --git a/ioc/Makefile b/ioc/Makefile new file mode 100644 index 0000000..2c82d61 --- /dev/null +++ b/ioc/Makefile @@ -0,0 +1,7 @@ +TOP = . +include $(TOP)/configure/CONFIG + +DIRS += configure +DIRS += iocApp/src + +include $(TOP)/configure/RULES_TOP diff --git a/ioc/configure/CONFIG b/ioc/configure/CONFIG new file mode 100644 index 0000000..722d9ca --- /dev/null +++ b/ioc/configure/CONFIG @@ -0,0 +1,30 @@ +# CONFIG - Load build configuration data +# +# Do not make changes to this file! + +# Allow user to override where the build rules come from +RULES = $(EPICS_BASE) + +# RELEASE files point to other application tops +include $(TOP)/configure/RELEASE +-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).Common +ifdef T_A +-include $(TOP)/configure/RELEASE.Common.$(T_A) +-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A) +endif + +CONFIG = $(RULES)/configure +include $(CONFIG)/CONFIG +-include $(CONFIG)/CONFIG.Dls + +# Override the Base definition: +INSTALL_LOCATION = $(TOP) + +# CONFIG_SITE files contain other build configuration settings +include $(TOP)/configure/CONFIG_SITE +-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).Common +ifdef T_A + -include $(TOP)/configure/CONFIG_SITE.Common.$(T_A) + -include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A) +endif + diff --git a/ioc/configure/CONFIG_SITE b/ioc/configure/CONFIG_SITE new file mode 100644 index 0000000..7eaf11b --- /dev/null +++ b/ioc/configure/CONFIG_SITE @@ -0,0 +1,5 @@ +# CONFIG_SITE + +CROSS_COMPILER_TARGET_ARCHS = +CHECK_RELEASE = NO + diff --git a/ioc/configure/Makefile b/ioc/configure/Makefile new file mode 100644 index 0000000..9254309 --- /dev/null +++ b/ioc/configure/Makefile @@ -0,0 +1,8 @@ +TOP=.. + +include $(TOP)/configure/CONFIG + +TARGETS = $(CONFIG_TARGETS) +CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS))) + +include $(TOP)/configure/RULES diff --git a/ioc/configure/RELEASE b/ioc/configure/RELEASE new file mode 100644 index 0000000..57c536e --- /dev/null +++ b/ioc/configure/RELEASE @@ -0,0 +1,5 @@ +# The following definitions must be changed for each site +# +# Common prefixes +SUPPORT=/epics/support +include $(SUPPORT)/configure/RELEASE diff --git a/ioc/configure/RULES b/ioc/configure/RULES new file mode 100644 index 0000000..3425740 --- /dev/null +++ b/ioc/configure/RULES @@ -0,0 +1,7 @@ +# RULES + +-include $(CONFIG)/RULES.Dls +include $(CONFIG)/RULES + +# Library should be rebuilt because LIBOBJS may have changed. +$(LIBNAME): ../Makefile diff --git a/ioc/configure/RULES.ioc b/ioc/configure/RULES.ioc new file mode 100644 index 0000000..901987c --- /dev/null +++ b/ioc/configure/RULES.ioc @@ -0,0 +1,2 @@ +#RULES.ioc +include $(CONFIG)/RULES.ioc diff --git a/ioc/configure/RULES_DIRS b/ioc/configure/RULES_DIRS new file mode 100644 index 0000000..3ba269d --- /dev/null +++ b/ioc/configure/RULES_DIRS @@ -0,0 +1,2 @@ +#RULES_DIRS +include $(CONFIG)/RULES_DIRS diff --git a/ioc/configure/RULES_TOP b/ioc/configure/RULES_TOP new file mode 100644 index 0000000..d09d668 --- /dev/null +++ b/ioc/configure/RULES_TOP @@ -0,0 +1,3 @@ +#RULES_TOP +include $(CONFIG)/RULES_TOP + diff --git a/ioc/iocApp/src/Makefile b/ioc/iocApp/src/Makefile new file mode 100644 index 0000000..f7d5648 --- /dev/null +++ b/ioc/iocApp/src/Makefile @@ -0,0 +1,21 @@ +# Generic IOC Makefile + +TOP = ../.. +include $(TOP)/configure/CONFIG + +PROD_IOC = ioc +DBD += ioc.dbd +ioc_DBD += base.dbd + +# add in the dbds collected by ibek during container build +ioc_DBD += $(shell cat /epics/support/configure/dbd_list) + +ioc_SRCS += ioc_registerRecordDeviceDriver.cpp + +# add in the libs collected by ibek during container build +ioc_LIBS += $(shell cat /epics/support/configure/lib_list) + +ioc_LIBS += $(EPICS_BASE_IOC_LIBS) +ioc_SRCS += iocMain.cpp + +include $(TOP)/configure/RULES diff --git a/ioc/iocApp/src/iocMain.cpp b/ioc/iocApp/src/iocMain.cpp new file mode 100644 index 0000000..e45dda1 --- /dev/null +++ b/ioc/iocApp/src/iocMain.cpp @@ -0,0 +1,20 @@ +/* This file was automatically generated on Fri 25 May 2018 08:06:10 BST from + * source: /home/hgv27681/R3.14.12.3/support/pmac/etc/makeIocs/lab.xml + * + * *** Please do not edit this file: edit the source file instead. *** + * */ +#include "epicsExit.h" +#include "epicsThread.h" +#include "iocsh.h" + +int main(int argc, char *argv[]) +{ + if(argc>=2) { + iocsh(argv[1]); + epicsThreadSleep(.2); + } + iocsh(NULL); + epicsExit(0); + return 0; +} + diff --git a/ioc/liveness.sh b/ioc/liveness.sh new file mode 100755 index 0000000..b0f5c0c --- /dev/null +++ b/ioc/liveness.sh @@ -0,0 +1,40 @@ +#!/bin/bash +TOP=/epics/ioc +cd ${TOP} +CONFIG_DIR=${TOP}/config + +set -ex + +CONFIG_DIR=/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/ioc/start.sh b/ioc/start.sh new file mode 100755 index 0000000..b20b72f --- /dev/null +++ b/ioc/start.sh @@ -0,0 +1,153 @@ +#!/bin/bash + +description=' + + 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 a specific 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 completely custom + startup script. Any other files that the script needs can also be placed + in the config folder. + + The presence of this file overrides all other options. + + WARNING: config maps are restricted to 1MB total. + + 2. ioc.yaml ************************************************************* + If the config folder contains a yaml file we invoke the ibek tool to + generate the startup script and database. Then launch with the generated + startup script. The file name should be the name of the ioc with a 'yaml' + extension e.g. bl38p-ea-panda-02.yaml. Using a unique name allows for: + + ioc_name: "{{ ioc_yaml_file_name }}" + + at the top of the file and in turn "{{ ioc_name }}"" can be used in any + of the fields within the file. + + 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 ${RUNTIME_DIR}/ioc.db + + 4. empty config folder ******************************************************* + If the config folder is empty this message will be displayed. + + + RTEMS IOCS - RTEMS IOC startup files can be generated using any of the 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 and connect that to + stdio of the pod. + +' + +# error reporting ************************************************************* + +function ibek_error { + echo "${1}" + + # Wait indefinitely so the container does not exit and restart continually. + while true; do + sleep 1000 + done +} + +# environment setup ************************************************************ + +set -x -e + +export TOP=$(realpath $(dirname $0)) +cd ${TOP} +CONFIG_DIR=${TOP}/config + +# add module paths to environment for use in ioc startup script +if [[ -f ${SUPPORT}/configure/RELEASE.shell ]]; then + source ${SUPPORT}/configure/RELEASE.shell +fi + +# override startup script +override=${CONFIG_DIR}/start.sh +# source YAML for IOC Builder for EPICS on Kubernetes (ibek) +ibek_yamls=(${CONFIG_DIR}/*.yaml) +# Startup script for EPICS IOC generated by ibek +ioc_startup=${CONFIG_DIR}/st.cmd + +# folder for runtime assets +export RUNTIME_DIR=${EPICS_ROOT}/runtime +mkdir -p ${RUNTIME_DIR} + +# expanded database file +epics_db=${RUNTIME_DIR}/ioc.db + +# in case there are multiple YAML, pick the first one in the glob +ibek_src=${ibek_yamls[0]} + +# 1. start.sh override script ************************************************** +if [ -f ${override} ]; then + exec bash ${override} +# 2. ioc.yaml ****************************************************************** +elif [ -f ${ibek_src} ]; then + + if [[ ${#ibek_yams[@]} > 1 ]]; then + ibek_error "ERROR: Multiple YAML files found in ${CONFIG_DIR}." + fi + + # Database generation script generated by ibek + db_src=${RUNTIME_DIR}/ioc.subst + final_ioc_startup=${RUNTIME_DIR}/st.cmd + + # get the ibek support yaml files this ioc's support modules + defs=/epics/ibek-defs/*.ibek.support.yaml + ibek runtime generate ${ibek_src} ${defs} --out ${final_ioc_startup} --db-out ${db_src} + + # build expanded database using msi + if [ -f ${db_src} ]; then + includes=$(for i in ${SUPPORT}/*/db; do echo -n "-I $i "; done) + bash -c "msi -o${epics_db} ${includes} -I${RUNTIME_DIR} -S${db_src}" + fi + +# 2. st.cmd + ioc.subst ************************************************ +elif [ -f ${ioc_startup} ] ; then + + if [ -f ${CONFIG_DIR}/ioc.subst ]; then + # generate ioc.db from ioc.subst, including all templates from SUPPORT + includes=$(for i in ${SUPPORT}/*/db; do echo -n "-I $i "; done) + msi ${includes} -I${RUNTIME_DIR} -S ${CONFIG_DIR}/ioc.subst -o ${epics_db} + fi + final_ioc_startup=${ioc_startup} +# 4. empty config folder *************************************************** +else + echo "No startup assets found in ${CONFIG_DIR}" + echo + + ibek_error "${description}" +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/ioc/stop.sh b/ioc/stop.sh new file mode 100644 index 0000000..f734409 --- /dev/null +++ b/ioc/stop.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +TOP=/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. + + # No action is required here for RTEMS because the start.sh script will + # monitor for SIG_TERM and reboot the IOC when it is received. +fi + + diff --git a/requirements.txt b/requirements.txt index a34e873..452119c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -ibek==1.5.3 -# to install direct from github during development in the dev branch: -#git+https://github.com/epics-containers/ibek.git@dev +ibek==1.6.2 +# to install direct from github during development in a branch +# git+https://github.com/epics-containers/ibek.git@fix-extract-assets \ No newline at end of file diff --git a/requirements_ec.txt b/requirements_ec.txt index ac3632c..6033a85 100644 --- a/requirements_ec.txt +++ b/requirements_ec.txt @@ -1,3 +1,3 @@ -epics-containers-cli==2.6.4 -# to install direct from github during development in the dev branch: +epics-containers-cli==2.6.11 +# to install direct from github during development in the e.g. dev branch: #git+https://github.com/epics-containers/epics-containers-cli.git@dev