From 201f61d1f43ca5e044797d38dc6985941b09f744 Mon Sep 17 00:00:00 2001
From: David Trattnig <david.trattnig@o94.at>
Date: Mon, 27 Apr 2020 16:37:30 +0200
Subject: [PATCH] Improved startup and shutdown.

---
 .vscode/launch.json                           | 27 ++++--
 configuration/sample.engine.ini               |  6 +-
 configuration/supervisor/aura-engine-api.conf |  2 +-
 configuration/supervisor/aura-engine-lqs.conf | 14 ---
 ...aura-engine-core.conf => aura-engine.conf} |  2 +-
 configuration/systemd/aura-engine-api.service |  3 +-
 .../systemd/aura-engine-liquidsoap.service    | 13 ---
 ...ngine-core.service => aura-engine.service} |  0
 engine-api.py                                 |  2 +-
 engine-core.py                                | 86 ++++++++++++++++---
 guru.py                                       |  2 +-
 modules/communication/redis/adapter.py        | 16 +++-
 modules/core/startup.py                       |  2 +-
 modules/scheduling/scheduler.py               |  5 +-
 run.sh                                        | 42 +++++----
 15 files changed, 150 insertions(+), 72 deletions(-)
 delete mode 100644 configuration/supervisor/aura-engine-lqs.conf
 rename configuration/supervisor/{aura-engine-core.conf => aura-engine.conf} (95%)
 delete mode 100644 configuration/systemd/aura-engine-liquidsoap.service
 rename configuration/systemd/{aura-engine-core.service => aura-engine.service} (100%)

diff --git a/.vscode/launch.json b/.vscode/launch.json
index 127cf15f..56ec3c0f 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,14 +5,34 @@
     "version": "0.2.0",
     "configurations": [
         {
-            "name": "Aura Engine - Run",
+            "name": "Start Aura Engine",
             "type": "python",
             "request": "launch",
             "program": "${workspaceFolder}/engine-core.py",
             "console": "integratedTerminal"
         },
         {
-            "name": "Aura Engine API - Run",
+            "name": "Start Engine Core Only",
+            "type": "python",
+            "request": "launch",
+            "program": "${workspaceFolder}/engine-core.py",
+            "args": [
+                "--without-lqs"
+            ],
+            "console": "integratedTerminal"
+        },
+        {
+            "name": "Start Engine LQS Only",
+            "type": "python",
+            "request": "launch",
+            "program": "${workspaceFolder}/engine-core.py",
+            "args": [
+                "--lqs-only"
+            ],
+            "console": "integratedTerminal"
+        },
+        {
+            "name": "Start Engine API",
             "type": "python",
             "request": "launch",
             "stopOnEntry": false,
@@ -23,9 +43,6 @@
             },
             "args": [
                 "run"
-            ],
-            "debugOptions": [
-                "RedirectOutput"
             ]
         },
         {
diff --git a/configuration/sample.engine.ini b/configuration/sample.engine.ini
index f387a475..565b8d6a 100644
--- a/configuration/sample.engine.ini
+++ b/configuration/sample.engine.ini
@@ -75,7 +75,11 @@ fade_out_time="2.5"
 
 # all these settings from here to the bottom require a restart of the liquidsoap server
 
-# Liquidsoap execution delay; Crucial to keep things in sync
+[lqs]
+liquidsoap_path="/home/david/.opam/4.08.0/bin/liquidsoap"
+liquidsoap_working_dir="modules/liquidsoap/"
+
+# Liquidsoap execution delay in seconds; Crucial to keep things in sync
 lqs_delay_offset=1
 
 [user]
diff --git a/configuration/supervisor/aura-engine-api.conf b/configuration/supervisor/aura-engine-api.conf
index 49c38844..fb2a8944 100644
--- a/configuration/supervisor/aura-engine-api.conf
+++ b/configuration/supervisor/aura-engine-api.conf
@@ -2,7 +2,7 @@
 [program:aura-engine-api]
 user = engineuser
 directory = /opt/aura/engine
-command = /opt/aura/engine/run.sh api-prod
+command = /opt/aura/engine/run.sh api
 
 priority = 999
 autostart = true
diff --git a/configuration/supervisor/aura-engine-lqs.conf b/configuration/supervisor/aura-engine-lqs.conf
deleted file mode 100644
index e793be36..00000000
--- a/configuration/supervisor/aura-engine-lqs.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-;/etc/supervisor/conf.d/aura-engine-lqs.conf
-[program:aura-engine-lqs]
-user = engineuser
-directory = /opt/aura/engine
-command = /opt/aura/engine/run.sh lqs
-
-priority = 666
-autostart = false
-autorestart = false
-stopsignal = TERM
-
-redirect_stderr = true
-stdout_logfile = /var/log/aura/engine-lqs-stdout.log
-stderr_logfile = /var/log/aura/engine-lqs-error.log
\ No newline at end of file
diff --git a/configuration/supervisor/aura-engine-core.conf b/configuration/supervisor/aura-engine.conf
similarity index 95%
rename from configuration/supervisor/aura-engine-core.conf
rename to configuration/supervisor/aura-engine.conf
index c1a8e353..b8c09109 100644
--- a/configuration/supervisor/aura-engine-core.conf
+++ b/configuration/supervisor/aura-engine.conf
@@ -4,7 +4,7 @@ user = engineuser
 directory = /opt/aura/engine
 command = /opt/aura/engine/run.sh engine
 
-priority = 333
+priority = 666
 autostart = true
 autorestart = true
 stopsignal = TERM
diff --git a/configuration/systemd/aura-engine-api.service b/configuration/systemd/aura-engine-api.service
index 3a65a601..4659be79 100644
--- a/configuration/systemd/aura-engine-api.service
+++ b/configuration/systemd/aura-engine-api.service
@@ -6,8 +6,7 @@ After=network.target
 Type=simple
 User=engineuser
 WorkingDirectory=/opt/Code/aura/engine
-ExecStart=/opt/aura/engine/run.sh api-prod
-ExecStop=/opt/aura/engine/guru.py --shutdown --quiet
+ExecStart=/opt/aura/engine/run.sh api
 Restart=always
 
 [Install]
diff --git a/configuration/systemd/aura-engine-liquidsoap.service b/configuration/systemd/aura-engine-liquidsoap.service
deleted file mode 100644
index a0b445ae..00000000
--- a/configuration/systemd/aura-engine-liquidsoap.service
+++ /dev/null
@@ -1,13 +0,0 @@
-[Unit]
-Description=Aura Engine - Liquidsoap Server
-After=network.target aura-engine.service
-Wants=aura-engine.service
-
-[Service]
-Type=simple
-User=engineuser
-ExecStart=/usr/bin/liquidsoap /opt/aura/engine/modules/liquidsoap/engine.liq
-Restart=always
-
-[Install]
-WantedBy=multi-user.target
diff --git a/configuration/systemd/aura-engine-core.service b/configuration/systemd/aura-engine.service
similarity index 100%
rename from configuration/systemd/aura-engine-core.service
rename to configuration/systemd/aura-engine.service
diff --git a/engine-api.py b/engine-api.py
index c24ab335..fc025a0d 100644
--- a/engine-api.py
+++ b/engine-api.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3.7
+#!/usr/bin/env python3.7
 
 #
 # Aura Engine
diff --git a/engine-core.py b/engine-core.py
index b327db38..a8c45d2f 100755
--- a/engine-core.py
+++ b/engine-core.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3.7
+#!/usr/bin/env python3.7
 
 #
 # Aura Engine
@@ -67,6 +67,8 @@ class AuraEngine:
     controller = None
     scheduler = None
     lqs = None
+    lqs_startup = None
+
 
 
     def __init__(self):
@@ -74,18 +76,23 @@ class AuraEngine:
         Initializes Engine Core.
         """
         self.config = config
-        AuraLogger(self.config)
-        self.logger = logging.getLogger("AuraEngine")
 
 
-    def startup(self):
+
+    def startup(self, lqs_startup):
         """
         Starts Engine Core.
         """
+        AuraLogger(self.config)
+        self.logger = logging.getLogger("AuraEngine")
+
         from modules.scheduling.scheduler import AuraScheduler
         from modules.core.engine import SoundSystem
         from modules.communication.redis.adapter import ServerRedisAdapter
 
+        # If Liquidsoap should be started automatically
+        self.lqs_startup = lqs_startup
+
         # Check if the database has to be re-created
         if self.config.get("recreate_db") is not None:
             AuraScheduler(self.config, None, None)
@@ -109,19 +116,66 @@ class AuraEngine:
         Called when the engine is initialized, before the Liquidsoap connection is established."
         """
         self.logger.info(SimpleUtil.green("Engine Core initialized - Waiting for Liquidsoap connection ..."))
-        cwd = os.getcwd()
-        cwd += "/modules/liquidsoap/" 
-        command = "/home/david/.opam/4.08.0/bin/liquidsoap"
-        self.lqs = subprocess.Popen([command, "engine.liq"], cwd=cwd, stdout=subprocess.PIPE, shell=False)
+        if self.lqs_startup:
+            self.start_lqs(False, False)
+        else:
+            self.logger.info(SimpleUtil.yellow("Please note, Liquidsoap needs to be started manually."))
+
+
+
+    def start_lqs(self, debug_output, verbose_output):
+        """
+        Starts Liquidsoap.
+        """
+        lqs_path = self.config.get("liquidsoap_path")
+        lqs_cwd = os.getcwd() + "/" + self.config.get("liquidsoap_working_dir")
+        lqs_output = ""
+        lqs_output = self.get_debug_flags(debug_output, verbose_output)
+
+        self.lqs = subprocess.Popen([lqs_path, lqs_output, "engine.liq"], \
+            cwd=lqs_cwd, \
+            stdout=subprocess.PIPE, \
+            shell=False)
+
+
+
+    def get_lqs_cmd(self, debug_output, verbose_output):
+        """
+        Returns a shell command string to start Liquidsoap
+        """
+        lqs_path = self.config.get("liquidsoap_path")
+        lqs_cwd = os.getcwd() + "/" + self.config.get("liquidsoap_working_dir")
+        lqs_output = self.get_debug_flags(debug_output, verbose_output)
+        return "(cd %s && %s %s ./engine.liq)" % (lqs_cwd, lqs_path, lqs_output)
+
+
+
+    def get_debug_flags(self, debug_output, verbose_output):
+        """
+        Build Liquidssoap debug parameters.
+        """
+        output = ""
+        if debug_output:
+            output += "--debug " 
+        if verbose_output:
+            output += "--verbose "
+        return output
 
 
 
     def exit_gracefully(self, signum, frame):
         """
-        Shutdown of the engine. Also terminates the liquidsoap thread.
+        Shutdown of the engine. Also terminates the Liquidsoap thread.
         """
-        self.lqs.terminate()
+        if self.lqs:
+            self.lqs.terminate()
+            self.logger.info("Terminated Liquidsoap")
+
+        if self.messenger:
+            self.messenger.terminate()
+        
         self.logger.info("Gracefully terminated Aura Engine!" + str(self.lqs))
+        sys.exit(0)        
 
 
 
@@ -131,15 +185,25 @@ class AuraEngine:
 
 
 if __name__ == "__main__":        
+
     engine = AuraEngine()
+    lqs_startup = True
+    lqs_cmd = False
 
     signal.signal(signal.SIGINT, engine.exit_gracefully)
     signal.signal(signal.SIGTERM, engine.exit_gracefully)
 
     if len(sys.argv) >= 2:
+        if "--without-lqs" in sys.argv:
+            lqs_startup = False
+        if "--get-lqs-command" in sys.argv:
+            lqs_cmd = True
         if "--use-test-data" in sys.argv:
             engine.config.set("use_test_data", True)
         if "--recreate-database" in sys.argv:
             engine.config.set("recreate_db", True)
 
-    engine.startup()
+    if lqs_cmd:
+        print(engine.get_lqs_cmd(True, True))
+    else:
+        engine.startup(lqs_startup)
diff --git a/guru.py b/guru.py
index e0130a90..240769ba 100755
--- a/guru.py
+++ b/guru.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python3.7
+#!/usr/bin/env python3.7
 
 #
 #  engine
diff --git a/modules/communication/redis/adapter.py b/modules/communication/redis/adapter.py
index 48db04bb..acba10b6 100644
--- a/modules/communication/redis/adapter.py
+++ b/modules/communication/redis/adapter.py
@@ -148,10 +148,7 @@ class ServerRedisAdapter(threading.Thread, RedisMessenger):
             self.execute(RedisChannel.FNP_REPLY.value, self.scheduler.get_act_programme_as_string)
 
         elif item["data"] == "shutdown":
-            self.shutdown_event.set()
-            self.scheduler.stop()
-            self.pubsub.close()
-            self.logger.info("shutdown event received. Bye bye...")
+            self.terminate()
 
         elif item["data"] == "init_player":
             self.execute(RedisChannel.IP_REPLY.value, self.soundsystem.init_player)
@@ -259,6 +256,17 @@ class ServerRedisAdapter(threading.Thread, RedisMessenger):
             self.logger.warning("cannot send message via REDIS: "+str(message))
 
 
+
+    def terminate(self):
+        """
+        Called when thread is stopped or a signal to terminate is received.
+        """
+        self.shutdown_event.set()
+        self.scheduler.terminate()
+        self.pubsub.close()
+        self.logger.info("Shutdown event received. Bye bye ...")
+
+
 # ------------------------------------------------------------------------------------------ #
 class ClientRedisAdapter(RedisMessenger):
 
diff --git a/modules/core/startup.py b/modules/core/startup.py
index 66c7bde6..6ba23638 100644
--- a/modules/core/startup.py
+++ b/modules/core/startup.py
@@ -64,7 +64,7 @@ class StartupThread(threading.Thread):
         except NoActiveScheduleException as e:
             self.logger.info("Nothing scheduled at startup time. Please check if there are follow-up schedules.")
         except Exception as e:
-            self.logger.error("Error while initializing the soundsystem: " + str(e))
+            self.logger.error(SimpleUtil.red("Error while initializing the sound-system: " + str(e)))
 
 
 
diff --git a/modules/scheduling/scheduler.py b/modules/scheduling/scheduler.py
index 406cdf91..68af909b 100644
--- a/modules/scheduling/scheduler.py
+++ b/modules/scheduling/scheduler.py
@@ -798,11 +798,12 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
 
 
 
-    def stop(self):
+    def terminate(self):
         """
-        Called when thread is stopped.
+        Called when thread is stopped or a signal to terminate is received.
         """
         self.exit_event.set()
+        self.logger.info("Shutting down scheduler ...")
 
 
 # ------------------------------------------------------------------------------------------ #
diff --git a/run.sh b/run.sh
index fb7682f4..86ceeb69 100755
--- a/run.sh
+++ b/run.sh
@@ -1,35 +1,47 @@
-#/usr/bin/bash
+#!/bin/bash
 mode="engine"
-debug="--debug"
-#debug="--debug --verbose"
 
 
-if [ -n "$1" ]; then
-	if [[ $1 =~ ^(engine|lqs|api|api-prod)$ ]]; then 
-  		mode=$1 
-	fi
+if [[ $* =~ ^(engine|core|lqs|api-dev|api)$ ]]; then 
+	mode=$1 
 fi
 
 echo "[ Run mode=$mode ]"
 
-if [ $mode == "engine" ]; then
-	/usr/bin/python3.7 aura.py
+
+### Runs Engine Core & Liquidsoap ###
+
+if [[ $mode == "engine" ]]; then
+	/usr/bin/env python3.7 engine-core.py
+fi
+
+### Runs Engine Core only ###
+
+if [[ $mode == "core" ]]; then
+	/usr/bin/env python3.7 engine-core.py --without-lqs
 fi
 
-if [ $mode == "lqs" ]; then
-	(cd modules/liquidsoap/ && liquidsoap $debug ./engine.liq) 
+### Runs Liquidsoap only ###
+
+if [[ $mode == "lqs" ]]; then
+	lqs=$(/usr/bin/env python3.7 engine-core.py --get-lqs-command)
+	eval "$lqs"
 fi
 
-if [ $mode == "api" ]; then
+### Runs the API Server (Development) ###
+
+if [[ $mode == "api-dev" ]]; then
 	echo "Building Web Applications"
 	sh ./script/build-web.sh
 	echo "Starting API Server"
-	/usr/bin/python3.7 api.py
+	/usr/bin/env python3.7 engine-api.py
 fi
 
-if [ $mode == "api-prod" ]; then
+### Runs the API Server (Production) ###
+
+if [[ $mode == "api" ]]; then
 	echo "Building Web Applications"
 	sh ./script/build-web.sh
 	echo "Starting API Server"
-	gunicorn -c configuration/gunicorn.conf.py api:app
+	gunicorn -c configuration/gunicorn.conf.py engine-api:app
 fi
\ No newline at end of file
-- 
GitLab