Commit 97aa574b authored by David Trattnig's avatar David Trattnig
Browse files

Monitoring for Engine startup.

parent 55835eda
...@@ -33,13 +33,19 @@ from_mail="monitoring@aura.engine" ...@@ -33,13 +33,19 @@ from_mail="monitoring@aura.engine"
# The beginning of the subject. With that you can easily apply filter rules using a mail client # The beginning of the subject. With that you can easily apply filter rules using a mail client
mailsubject_prefix="[Aura Engine]" mailsubject_prefix="[Aura Engine]"
[dataurls] [api]
# The URL to get the Calendar via PV/Steering
api_calendar_url="http://localhost:8000/api/v1/playout" # STEERING
# The URL to get show details via PV/Steering api_steering_status = "http://localhost:8000/api/v1/playout"
api_show_url="http://localhost:8000/api/v1/shows/${ID}/" # The URL to get the Calendar via Steering
api_steering_calendar="http://localhost:8000/api/v1/playout"
# The URL to get show details via Steering
api_steering_show="http://localhost:8000/api/v1/shows/${ID}/"
# TANK
api_tank_status = "http://localhost:8040/ui/"
# The URL to get playlist details via Tank # The URL to get playlist details via Tank
api_playlist_url="http://localhost:8040/api/v1/shows/${SLUG}/playlists" api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
# URL and Port of the API endpoints exposed by engine # URL and Port of the API endpoints exposed by engine
exposed_api_url=http://localhost:3333/api/v1/ exposed_api_url=http://localhost:3333/api/v1/
...@@ -121,13 +127,13 @@ soundsystem="alsa" ...@@ -121,13 +127,13 @@ soundsystem="alsa"
# with alsa you have to write the devicenames like hw:0 # with alsa you have to write the devicenames like hw:0
# with pulse and jack => an non empty value means it is used # with pulse and jack => an non empty value means it is used
# devices with empty string are ignored and not used # devices with empty string are ignored and not used
input_device_0="" input_device_0="hw:0"
input_device_1="" input_device_1=""
input_device_2="" input_device_2=""
input_device_3="" input_device_3=""
input_device_4="" input_device_4=""
# same same, but different # same same, but different
output_device_0="default" output_device_0="hw:0"
output_device_1="" output_device_1=""
output_device_2="" output_device_2=""
output_device_3="" output_device_3=""
......
...@@ -33,13 +33,19 @@ from_mail="monitoring@aura.engine" ...@@ -33,13 +33,19 @@ from_mail="monitoring@aura.engine"
# The beginning of the subject. With that you can easily apply filter rules using a mail client # The beginning of the subject. With that you can easily apply filter rules using a mail client
mailsubject_prefix="[Aura Engine]" mailsubject_prefix="[Aura Engine]"
[dataurls] [api]
# The URL to get the Calendar via PV/Steering
api_calendar_url="http://localhost:8000/api/v1/playout" # STEERING
# The URL to get show details via PV/Steering api_steering_status = "http://localhost:8000/api/v1/playout"
api_show_url="http://localhost:8000/api/v1/shows/${ID}/" # The URL to get the Calendar via Steering
api_steering_calendar="http://localhost:8000/api/v1/playout"
# The URL to get show details via Steering
api_steering_show="http://localhost:8000/api/v1/shows/${ID}/"
# TANK
api_tank_status = "http://localhost:8040/ui/"
# The URL to get playlist details via Tank # The URL to get playlist details via Tank
api_playlist_url="http://localhost:8040/api/v1/shows/${SLUG}/playlists" api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
# URL and Port of the API endpoints exposed by engine # URL and Port of the API endpoints exposed by engine
exposed_api_url=http://localhost:3333/api/v1/ exposed_api_url=http://localhost:3333/api/v1/
...@@ -120,13 +126,13 @@ soundsystem="alsa" ...@@ -120,13 +126,13 @@ soundsystem="alsa"
# with alsa you have to write the devicenames like hw:0 # with alsa you have to write the devicenames like hw:0
# with pulse and jack => an non empty value means it is used # with pulse and jack => an non empty value means it is used
# devices with empty string are ignored and not used # devices with empty string are ignored and not used
input_device_0="" input_device_0="hw:0"
input_device_1="" input_device_1=""
input_device_2="" input_device_2=""
input_device_3="" input_device_3=""
input_device_4="" input_device_4=""
# same same, but different # same same, but different
output_device_0="default" output_device_0="hw:0"
output_device_1="" output_device_1=""
output_device_2="" output_device_2=""
output_device_3="" output_device_3=""
......
...@@ -33,13 +33,19 @@ from_mail="monitoring@aura.engine" ...@@ -33,13 +33,19 @@ from_mail="monitoring@aura.engine"
# The beginning of the subject. With that you can easily apply filter rules using a mail client # The beginning of the subject. With that you can easily apply filter rules using a mail client
mailsubject_prefix="[Aura Engine]" mailsubject_prefix="[Aura Engine]"
[dataurls] [api]
# The URL to get the Calendar via PV/Steering
api_calendar_url="http://localhost:8000/api/v1/playout" # STEERING
# The URL to get show details via PV/Steering api_steering_status = "http://localhost:8000/api/v1/playout"
api_show_url="http://localhost:8000/api/v1/shows/${ID}/" # The URL to get the Calendar via Steering
api_steering_calendar="http://localhost:8000/api/v1/playout"
# The URL to get show details via Steering
api_steering_show="http://localhost:8000/api/v1/shows/${ID}/"
# TANK
api_tank_status = "http://localhost:8040/ui/"
# The URL to get playlist details via Tank # The URL to get playlist details via Tank
api_playlist_url="http://localhost:8040/api/v1/shows/${SLUG}/playlists" api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
# URL and Port of the API endpoints exposed by engine # URL and Port of the API endpoints exposed by engine
exposed_api_url=http://localhost:3333/api/v1/ exposed_api_url=http://localhost:3333/api/v1/
...@@ -120,13 +126,13 @@ soundsystem="alsa" ...@@ -120,13 +126,13 @@ soundsystem="alsa"
# with alsa you have to write the devicenames like hw:0 # with alsa you have to write the devicenames like hw:0
# with pulse and jack => an non empty value means it is used # with pulse and jack => an non empty value means it is used
# devices with empty string are ignored and not used # devices with empty string are ignored and not used
input_device_0="" input_device_0="hw:0"
input_device_1="" input_device_1=""
input_device_2="" input_device_2=""
input_device_3="" input_device_3=""
input_device_4="" input_device_4=""
# same same, but different # same same, but different
output_device_0="default" output_device_0="hw:0"
output_device_1="" output_device_1=""
output_device_2="" output_device_2=""
output_device_3="" output_device_3=""
......
...@@ -112,7 +112,7 @@ Configure monitoring parameters such as admin emails in the `[monitoring]` secti ...@@ -112,7 +112,7 @@ Configure monitoring parameters such as admin emails in the `[monitoring]` secti
## API Endpoints ## API Endpoints
Configure connections to the other Aura components in the `[dataurls]` section. Configure connections to the other Aura components in the `[api]` section.
Sets the API URL exposed to external clients. This is required by the included Sets the API URL exposed to external clients. This is required by the included
web applications which access the API. web applications which access the API.
......
...@@ -44,17 +44,21 @@ using a fancy calendar interface. ...@@ -44,17 +44,21 @@ using a fancy calendar interface.
These data-sources need to be configurated in the "engine.ini" configuration file: These data-sources need to be configurated in the "engine.ini" configuration file:
# STEERING
api_steering_status = "http://localhost:8000/api/v1/"
# The URL to get the Calendar via Steering # The URL to get the Calendar via Steering
api_calendar_url="http://localhost:8000/api/v1/playout" api_steering_calendar="http://localhost:8000/api/v1/playout"
# The URL to get show details via Steering # The URL to get show details via Steering
api_show_url="http://localhost:8000/api/v1/shows/${ID}/" api_steering_show="http://localhost:8000/api/v1/shows/${ID}/"
The AURA Project "**Tank**" on the other hand delivers information on the tracks, related playlists The AURA Project "**Tank**" on the other hand delivers information on the tracks, related playlists
to be played and its meta-data: to be played and its meta-data:
# TANK
api_tank_status = "http://localhost:8040/ui/"
# The URL to get playlist details via Tank # The URL to get playlist details via Tank
api_playlist_url="http://localhost:8040/api/v1/shows/${SLUG}/playlists" api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
More information you can find here: <https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md> More information you can find here: <https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md>
......
...@@ -124,13 +124,17 @@ Now, specify at least following settings to get started: ...@@ -124,13 +124,17 @@ Now, specify at least following settings to get started:
Set the URLs to the *Steering* and *Tank* API: Set the URLs to the *Steering* and *Tank* API:
```ini ```ini
[dataurls] # STEERING
# The URL to get the Calendar via PV/Steering api_steering_status = "http://localhost:8000/api/v1/"
api_calendar_url="http://localhost:8000/api/v1/playout" # The URL to get the Calendar via Steering
# The URL to get show details via PV/Steering api_steering_calendar="http://localhost:8000/api/v1/playout"
api_show_url="http://localhost:8000/api/v1/shows/${ID}/" # The URL to get show details via Steering
# The URL to get playlist details via Tank api_steering_show="http://localhost:8000/api/v1/shows/${ID}/"
api_playlist_url="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
# TANK
api_tank_status = "http://localhost:8040/ui/"
# The URL to get playlist details via Tank
api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
``` ```
Ensure that the Liquidsoap installation path is valid: Ensure that the Liquidsoap installation path is valid:
......
...@@ -175,13 +175,17 @@ Now, specify at least following settings to get started: ...@@ -175,13 +175,17 @@ Now, specify at least following settings to get started:
Set the URLs to the *Steering* and *Tank* API: Set the URLs to the *Steering* and *Tank* API:
```ini ```ini
[dataurls] # STEERING
# The URL to get the Calendar via Steering api_steering_status = "http://localhost:8000/api/v1/"
api_calendar_url="https://aura-test.o94.at/steering/api/v1/playout" # The URL to get the Calendar via Steering
# The URL to get show details via Steering api_steering_calendar="http://localhost:8000/api/v1/playout"
api_show_url="http://aura-test.o94.at/steering/api/v1/shows/${ID}/" # The URL to get show details via Steering
# The URL to get playlist details via Tank api_steering_show="http://localhost:8000/api/v1/shows/${ID}/"
api_playlist_url="http://aura-test.o94.at/tank/api/v1/shows/${SLUG}/playlists"
# TANK
api_tank_status = "http://localhost:8040/ui/"
# The URL to get playlist details via Tank
api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
``` ```
Ensure that the Liquidsoap installation path is valid: Ensure that the Liquidsoap installation path is valid:
......
...@@ -123,7 +123,7 @@ class Guru(): ...@@ -123,7 +123,7 @@ class Guru():
self.parser.add_argument("-sd", "--shutdown", action="store_true", dest="shutdown", default=False, help="Shutting down aura server") self.parser.add_argument("-sd", "--shutdown", action="store_true", dest="shutdown", default=False, help="Shutting down aura server")
# playlist in/output # playlist in/output
self.parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme", default=False, help="Fetch new programmes from api_calendar_url in engine.ini") self.parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme", default=False, help="Fetch new programmes from api_steering_calendar in engine.ini")
self.parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue", default=False, help="Prints message queue") self.parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue", default=False, help="Prints message queue")
# send a redis message # send a redis message
......
...@@ -3,8 +3,8 @@ __author__ = "David Trattnig and Gottfried Gaisbauer" ...@@ -3,8 +3,8 @@ __author__ = "David Trattnig and Gottfried Gaisbauer"
__copyright__ = "Copyright 2017-2020, Aura Engine Team" __copyright__ = "Copyright 2017-2020, Aura Engine Team"
__credits__ = ["David Trattnig", "Gottfried Gaisbauer", "Michael Liebler"] __credits__ = ["David Trattnig", "Gottfried Gaisbauer", "Michael Liebler"]
__license__ = "GNU Affero General Public License (AGPL) Version 3" __license__ = "GNU Affero General Public License (AGPL) Version 3"
__version__ = "0.6.3" __version__ = "0.6.4"
__version_info__ = (0, 6, 3) __version_info__ = (0, 6, 4)
__maintainer__ = "David Trattnig" __maintainer__ = "David Trattnig"
__email__ = "david.trattnig@subsquare.at" __email__ = "david.trattnig@subsquare.at"
__status__ = "Development" __status__ = "Development"
\ No newline at end of file
...@@ -47,6 +47,9 @@ class NoActiveEntryException(Exception): ...@@ -47,6 +47,9 @@ class NoActiveEntryException(Exception):
# Monitoring Exceptions # Monitoring Exceptions
class EngineMalfunctionException(Exception):
pass
class MailingException(Exception): class MailingException(Exception):
pass pass
......
#
# Aura Engine
#
# Copyright (C) 2020 David Trattnig <david.trattnig@subsquare.at>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib
import logging
import os.path
from os import path
from modules.communication.redis.adapter import ClientRedisAdapter
class Monitoring:
"""
Engine Monitoring
"""
logger = None
soundsystem = None
status = None
def __init__(self, config, soundsystem):
"""
Initialize Monitoring
"""
self.logger = logging.getLogger("AuraEngine")
self.config = config
self.soundsystem = soundsystem
self.status = dict()
self.status["soundsystem"] = dict()
def update_status(self):
"""
Requests the current status of all components
"""
self.status["soundsystem"]["mixer"] = self.soundsystem.get_mixer_status()
#self.status["soundsystem"]["recorder"] = self.soundsystem.get_recorder_status()
self.status["redis_ready"] = self.validate_redis_connection()
self.status["api_steering_ready"] = self.validate_url_connection(self.config.get("api_steering_status"))
self.status["api_tank_ready"] = self.validate_url_connection(self.config.get("api_tank_status"))
self.status["audio_store"] = self.validate_directory(self.config.get("audiofolder"))
# Set overall status
if self.has_valid_status():
self.status["engine_status"] = "OK"
else:
self.status["engine_status"] = "INVALID"
def get_status(self):
"""
Retrieves the current monitoring status.
"""
self.update_status()
return self.status
def has_valid_status(self):
"""
Checks if the current status is valid to run engine
"""
if self.status["soundsystem"]["mixer"]["in_filesystem_0"] \
and self.status["redis_ready"] \
and self.status["audio_store"]["exists"]:
return True
return False
def validate_url_connection(self, url):
"""
Checks if connection to passed URL is successful.
"""
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
response.read()
except Exception:
return False
return True
def validate_redis_connection(self):
"""
Checks if the connection to Redis is successful.
"""
try:
cra = ClientRedisAdapter(self.config)
cra.publish("aura", "status")
except:
return False
return True
def validate_directory(self, dir_path):
"""
Checks if a given directory is existing and holds content
"""
status = dict()
status["exists"] = path.exists(dir_path) and os.path.isdir(dir_path)
status["has_content"] = False
if status["exists"]:
status["has_content"] = any([True for _ in os.scandir(dir_path)])
return status
\ No newline at end of file
...@@ -23,11 +23,12 @@ import logging ...@@ -23,11 +23,12 @@ import logging
import datetime import datetime
import threading import threading
import meta import meta
import json
from modules.base.exceptions import NoActiveScheduleException from modules.base.exceptions import NoActiveScheduleException, EngineMalfunctionException
from modules.base.enum import Channel, ChannelType from modules.base.enum import Channel, ChannelType
from modules.base.utils import TerminalColors, SimpleUtil, EngineUtil from modules.base.utils import TerminalColors, SimpleUtil, EngineUtil
from modules.core.monitor import Monitoring
class StartupThread(threading.Thread): class StartupThread(threading.Thread):
""" """
...@@ -39,7 +40,7 @@ class StartupThread(threading.Thread): ...@@ -39,7 +40,7 @@ class StartupThread(threading.Thread):
active_entry = None active_entry = None
soundsystem = None soundsystem = None
scheduler = None scheduler = None
monitoring = None
def __init__(self, soundsystem): def __init__(self, soundsystem):
""" """
...@@ -49,7 +50,7 @@ class StartupThread(threading.Thread): ...@@ -49,7 +50,7 @@ class StartupThread(threading.Thread):
self.logger = logging.getLogger("AuraEngine") self.logger = logging.getLogger("AuraEngine")
self.soundsystem = soundsystem self.soundsystem = soundsystem
self.scheduler = soundsystem.scheduler self.scheduler = soundsystem.scheduler
self.monitoring = Monitoring(soundsystem.config, soundsystem)
def run(self): def run(self):
...@@ -58,6 +59,15 @@ class StartupThread(threading.Thread): ...@@ -58,6 +59,15 @@ class StartupThread(threading.Thread):
""" """
try: try:
self.soundsystem.start() self.soundsystem.start()
status = self.monitoring.get_status()
self.logger.info("Status Monitor:\n%s" % json.dumps(status, indent=4))
if not status["engine_status"] == "OK":
self.logger.info("Engine Status: " + SimpleUtil.red(status["engine_status"]))
raise EngineMalfunctionException
else:
self.logger.info("Engine Status: " + SimpleUtil.green("OK"))
self.logger.info(EngineUtil.engine_info("Engine Core", meta.__version__)) self.logger.info(EngineUtil.engine_info("Engine Core", meta.__version__))
self.scheduler.on_ready() self.scheduler.on_ready()
......
...@@ -34,10 +34,9 @@ class CalendarFetcher: ...@@ -34,10 +34,9 @@ class CalendarFetcher:
""" """
self.config = config self.config = config
self.logger = logging.getLogger("AuraEngine") self.logger = logging.getLogger("AuraEngine")
self.__set_url__("api_calendar_url") self.__set_url__("api_steering_calendar")
self.__set_url__("api_playlist_url") self.__set_url__("api_tank_playlist")
self.__set_url__("api_show_url") self.__set_url__("api_steering_show")
# #
# PUBLIC METHODS # PUBLIC METHODS
...@@ -54,11 +53,11 @@ class CalendarFetcher: ...@@ -54,11 +53,11 @@ class CalendarFetcher:
self.logger.debug("Fetching schedules from STEERING") self.logger.debug("Fetching schedules from STEERING")
self.fetched_schedule_data = self.__fetch_schedule_data__() self.fetched_schedule_data = self.__fetch_schedule_data__()
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
self.logger.critical("Cannot fetch from " + self.url["api_calendar_url"] + "! Reason: " + str(e)) self.logger.critical("Cannot fetch from " + self.url["api_steering_calendar"] + "! Reason: " + str(e))
self.fetched_schedule_data = None self.fetched_schedule_data = None
return None return None
except (urllib.error.URLError, IOError, ValueError) as e: except (urllib.error.URLError, IOError, ValueError) as e:
self.logger.critical("Cannot connect to " + self.url["api_calendar_url"] + "! Reason: " + str(e)) self.logger.critical("Cannot connect to " + self.url["api_steering_calendar"] + "! Reason: " + str(e))
self.fetched_schedule_data = None self.fetched_schedule_data = None
return None return None
...@@ -67,11 +66,11 @@ class CalendarFetcher: ...@@ -67,11 +66,11 @@ class CalendarFetcher:
self.logger.debug("Fetching playlists from TANK") self.logger.debug("Fetching playlists from TANK")
self.__fetch_schedule_playlists__() self.__fetch_schedule_playlists__()
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
self.logger.critical("Cannot fetch from " + self.url["api_playlist_url"] + "! Reason: " + str(e)) self.logger.critical("Cannot fetch from " + self.url["api_tank_playlist"] + "! Reason: " + str(e))
self.fetched_schedule_data = None self.fetched_schedule_data = None
return None return None
except (urllib.error.URLError, IOError, ValueError) as e: except (urllib.error.URLError, IOError, ValueError) as e:
self.logger.critical("Cannot connect to " + self.url["api_playlist_url"] + "! Reason: " + str(e)) self.logger.critical("Cannot connect to " + self.url["api_tank_playlist"] + "! Reason: " + str(e))
self.fetched_schedule_data = None self.fetched_schedule_data = None
return None return None
...@@ -129,7 +128,7 @@ class CalendarFetcher: ...@@ -129,7 +128,7 @@ class CalendarFetcher:
Returns: Returns:
([Schedule]): An array of schedules ([Schedule]): An array of schedules
""" """
servicetype = "api_calendar_url" servicetype = "api_steering_calendar"
schedule = None schedule = None
# fetch data from steering # fetch data from steering
...@@ -193,7 +192,7 @@ class CalendarFetcher: ...@@ -193,7 +192,7 @@ class CalendarFetcher:
Returns: Returns:
(Schedule): The given schedule with additional show fields set. (Schedule): The given schedule with additional show fields set.
""" """
servicetype = "api_show_url" servicetype = "api_steering_show"
url = self.__build_url__(servicetype, "${ID}", str(schedule["show_id"])) url = self.__build_url__(servicetype, "${ID}", str(schedule["show_id"]))
json_response = self.__fetch_data__(servicetype, url) json_response = self.__fetch_data__(servicetype, url)
...@@ -221,7 +220,7 @@ class CalendarFetcher: ...@@ -221,7 +220,7 @@ class CalendarFetcher:
Returns: Returns:
([Schedule]): Array of playlists ([Schedule]): Array of playlists
""" """
servicetype = "api_playlist_url" servicetype = "api_tank_playlist"