From a4e8aa23dcec3679b5a3cbd5866ec94d2339b9ff Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Sun, 19 Sep 2021 18:54:37 +0200
Subject: [PATCH] L2SS-379: Added device to monitor, stop, start our docker
 containers.

---
 devices/devices/docker.py                     | 163 ++++++++++++++++++
 docker-compose/device-docker.yml              |  44 +++++
 .../lofar-device-base/lofar-requirements.txt  |   1 +
 3 files changed, 208 insertions(+)
 create mode 100644 devices/devices/docker.py
 create mode 100644 docker-compose/device-docker.yml

diff --git a/devices/devices/docker.py b/devices/devices/docker.py
new file mode 100644
index 000000000..f4ed88eb0
--- /dev/null
+++ b/devices/devices/docker.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the Docker project
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+""" Docker Device Server for LOFAR2.0
+
+"""
+
+# TODO(Corne): Remove sys.path.append hack once packaging is in place!
+import os, sys
+currentdir = os.path.dirname(os.path.realpath(__file__))
+parentdir = os.path.dirname(currentdir)
+sys.path.append(parentdir)
+
+# PyTango imports
+from tango import DebugIt
+from tango.server import run, command
+from tango.server import device_property, attribute
+from tango import AttrWriteType
+# Additional import
+
+from device_decorators import *
+
+from clients.docker_client import DockerClient
+from clients.attribute_wrapper import attribute_wrapper
+from common.lofar_logging import device_logging_to_python, log_exceptions
+from common.lofar_git import get_version
+
+__all__ = ["Docker", "main"]
+
+@device_logging_to_python()
+class Docker(hardware_device):
+    """
+
+    **Properties:**
+
+    - Device Property
+        OPC_Server_Name
+            - Type:'DevString'
+        OPC_Server_Port
+            - Type:'DevULong'
+        OPC_Time_Out
+            - Type:'DevDouble'
+    """
+
+    # -----------------
+    # Device Properties
+    # -----------------
+
+    Docker_Base_URL = device_property(
+        dtype='DevString',
+        mandatory=False,
+        default_value="unix:///var/run/docker.sock"
+    )
+
+    # ----------
+    # Attributes
+    # ----------
+    version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version())
+    archiver_maria_db_R = attribute_wrapper(comms_annotation=["archiver-maria-db"], datatype=numpy.bool_)
+    archiver_maria_db_RW = attribute_wrapper(comms_annotation=["archiver-maria-db"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    databaseds_R = attribute_wrapper(comms_annotation=["databaseds"], datatype=numpy.bool_)
+    databaseds_RW = attribute_wrapper(comms_annotation=["databaseds"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    device_pcc_R = attribute_wrapper(comms_annotation=["device_pcc"], datatype=numpy.bool_)
+    device_pcc_RW = attribute_wrapper(comms_annotation=["device_pcc"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    device_sdp_R = attribute_wrapper(comms_annotation=["device_sdp"], datatype=numpy.bool_)
+    device_sdp_RW = attribute_wrapper(comms_annotation=["device_sdp"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    device_sst_R = attribute_wrapper(comms_annotation=["device_sst"], datatype=numpy.bool_)
+    device_sst_RW = attribute_wrapper(comms_annotation=["device_sst"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    device_xst_R = attribute_wrapper(comms_annotation=["device_xst"], datatype=numpy.bool_)
+    device_xst_RW = attribute_wrapper(comms_annotation=["device_xst"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    device_unb2_R = attribute_wrapper(comms_annotation=["device_unb2"], datatype=numpy.bool_)
+    device_unb2_RW = attribute_wrapper(comms_annotation=["device_unb2"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    docker_R = attribute_wrapper(comms_annotation=["docker"], datatype=numpy.bool_)
+    # docker_RW is not available, as we cannot start our own container`
+    dsconfig_R = attribute_wrapper(comms_annotation=["dsconfig"], datatype=numpy.bool_)
+    dsconfig_RW = attribute_wrapper(comms_annotation=["dsconfig"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    elk_R = attribute_wrapper(comms_annotation=["elk"], datatype=numpy.bool_)
+    elk_RW = attribute_wrapper(comms_annotation=["elk"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    grafana_R = attribute_wrapper(comms_annotation=["grafana"], datatype=numpy.bool_)
+    grafana_RW = attribute_wrapper(comms_annotation=["grafana"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    hdbpp_cm_R = attribute_wrapper(comms_annotation=["hdbpp-cm"], datatype=numpy.bool_)
+    hdbpp_cm_RW = attribute_wrapper(comms_annotation=["hdbpp-cm"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    hdbpp_es_R = attribute_wrapper(comms_annotation=["hdbpp-es"], datatype=numpy.bool_)
+    hdbpp_es_RW = attribute_wrapper(comms_annotation=["hdbpp-es"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    itango_R = attribute_wrapper(comms_annotation=["itango"], datatype=numpy.bool_)
+    itango_RW = attribute_wrapper(comms_annotation=["itango"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    jupyter_R = attribute_wrapper(comms_annotation=["jupyter"], datatype=numpy.bool_)
+    jupyter_RW = attribute_wrapper(comms_annotation=["jupyter"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    prometheus_R = attribute_wrapper(comms_annotation=["prometheus"], datatype=numpy.bool_)
+    prometheus_RW = attribute_wrapper(comms_annotation=["prometheus"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+    tangodb_R = attribute_wrapper(comms_annotation=["tangodb"], datatype=numpy.bool_)
+    # tangodb_RW is not available, as we cannot start tango from this device if it is down
+    tango_prometheus_exporter_R = attribute_wrapper(comms_annotation=["tango-prometheus-exporter"], datatype=numpy.bool_)
+    tango_prometheus_exporter_RW = attribute_wrapper(comms_annotation=["tango-prometheus-exporter"], datatype=numpy.bool_, access=AttrWriteType.READ_WRITE)
+
+    @log_exceptions()
+    def delete_device(self):
+        """Hook to delete resources allocated in init_device.
+
+        This method allows for any memory or other resources allocated in the
+        init_device method to be released.  This method is called by the device
+        destructor and by the device Init command (a Tango built-in).
+        """
+        self.debug_stream("Shutting down...")
+
+        self.Off()
+        self.debug_stream("Shut down.  Good bye.")
+
+    # --------
+    # overloaded functions
+    # --------
+    @log_exceptions()
+    def configure_for_off(self):
+        """ user code here. is called when the state is set to OFF """
+        # Stop keep-alive
+        try:
+            self.opcua_connection.stop()
+        except Exception as e:
+            self.warn_stream("Exception while stopping OPC ua connection in configure_for_off function: {}. Exception ignored".format(e))
+
+    @log_exceptions()
+    def configure_for_initialise(self):
+        """ user code here. is called when the state is set to INIT """
+
+        # set up the Docker client
+        self.docker_client = DockerClient(self.Docker_Base_URL, self.Fault, self)
+
+        # map an access helper class
+        for i in self.attr_list():
+            try:
+                i.set_comm_client(self.docker_client)
+            except Exception as e:
+                # use the pass function instead of setting read/write fails
+                i.set_pass_func()
+                self.warn_stream("error while setting the attribute {} read/write function. {}".format(i, e))
+
+        self.docker_client.start()
+
+    # --------
+    # Commands
+    # --------
+
+
+# ----------
+# Run server
+# ----------
+def main(args=None, **kwargs):
+    """Main function of the Docker module."""
+
+    from common.lofar_logging import configure_logger
+    configure_logger()
+
+    return run((Docker,), args=args, **kwargs)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/docker-compose/device-docker.yml b/docker-compose/device-docker.yml
new file mode 100644
index 000000000..78f43f18e
--- /dev/null
+++ b/docker-compose/device-docker.yml
@@ -0,0 +1,44 @@
+#
+# Docker compose file that launches an interactive iTango session.
+#
+# Connect to the interactive session with 'docker attach itango'.
+# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q>
+#
+# Defines:
+#   - itango: iTango interactive session
+#
+# Requires:
+#   - lofar-device-base.yml
+#
+version: '2'
+
+services:
+  device-docker:
+    image: device-docker
+    # build explicitly, as docker-compose does not understand a local image
+    # being shared among services.
+    build:
+        context: lofar-device-base
+        args:
+            SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
+    container_name: ${CONTAINER_NAME_PREFIX}device-docker
+    networks:
+      - control
+    ports:
+      - "5705:5705" # unique port for this DS
+    volumes:
+      - ${TANGO_LOFAR_CONTAINER_MOUNT}
+      - /var/run/docker.sock:/var/run/docker.sock:rw
+    privileged: true
+    environment:
+      - TANGO_HOST=${TANGO_HOST}
+    entrypoint:
+      - /usr/local/bin/wait-for-it.sh
+      - ${TANGO_HOST}
+      - --timeout=30
+      - --strict
+      - --
+      # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
+      # can't know about our Docker port forwarding
+      - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/docker.py LTS -v -ORBendPoint giop:tcp:0:5705 -ORBendPointPublish giop:tcp:${HOSTNAME}:5705
+    restart: on-failure
diff --git a/docker-compose/lofar-device-base/lofar-requirements.txt b/docker-compose/lofar-device-base/lofar-requirements.txt
index 57ab2a14f..2214412a4 100644
--- a/docker-compose/lofar-device-base/lofar-requirements.txt
+++ b/docker-compose/lofar-device-base/lofar-requirements.txt
@@ -4,3 +4,4 @@ python-logstash-async
 gitpython
 PyMySQL[rsa]
 sqlalchemy
+docker
-- 
GitLab