diff --git a/configuration/sample-development.engine.ini b/configuration/sample-development.engine.ini index 28aff73fb9171cb85093db88b8b5f601e84d217c..b2bae36cc1a6e3d0c8e750814facff2e8d29b486 100644 --- a/configuration/sample-development.engine.ini +++ b/configuration/sample-development.engine.ini @@ -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 mailsubject_prefix="[Aura Engine]" -[dataurls] -# The URL to get the Calendar via PV/Steering -api_calendar_url="http://localhost:8000/api/v1/playout" -# The URL to get show details via PV/Steering -api_show_url="http://localhost:8000/api/v1/shows/${ID}/" +[api] + +# STEERING +api_steering_status = "http://localhost:8000/api/v1/playout" +# 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 -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 exposed_api_url=http://localhost:3333/api/v1/ @@ -121,13 +127,13 @@ soundsystem="alsa" # with alsa you have to write the devicenames like hw:0 # with pulse and jack => an non empty value means it is used # devices with empty string are ignored and not used -input_device_0="" +input_device_0="hw:0" input_device_1="" input_device_2="" input_device_3="" input_device_4="" # same same, but different -output_device_0="default" +output_device_0="hw:0" output_device_1="" output_device_2="" output_device_3="" diff --git a/configuration/sample-docker.engine.ini b/configuration/sample-docker.engine.ini index d23e407d39b2560a4fa976a1d84e43157383eb9e..ae5b326717f6966ccc404fb234cbc3b7cfb0a41c 100644 --- a/configuration/sample-docker.engine.ini +++ b/configuration/sample-docker.engine.ini @@ -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 mailsubject_prefix="[Aura Engine]" -[dataurls] -# The URL to get the Calendar via PV/Steering -api_calendar_url="http://localhost:8000/api/v1/playout" -# The URL to get show details via PV/Steering -api_show_url="http://localhost:8000/api/v1/shows/${ID}/" +[api] + +# STEERING +api_steering_status = "http://localhost:8000/api/v1/playout" +# 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 -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 exposed_api_url=http://localhost:3333/api/v1/ @@ -120,13 +126,13 @@ soundsystem="alsa" # with alsa you have to write the devicenames like hw:0 # with pulse and jack => an non empty value means it is used # devices with empty string are ignored and not used -input_device_0="" +input_device_0="hw:0" input_device_1="" input_device_2="" input_device_3="" input_device_4="" # same same, but different -output_device_0="default" +output_device_0="hw:0" output_device_1="" output_device_2="" output_device_3="" diff --git a/configuration/sample-production.engine.ini b/configuration/sample-production.engine.ini index d23e407d39b2560a4fa976a1d84e43157383eb9e..ae5b326717f6966ccc404fb234cbc3b7cfb0a41c 100644 --- a/configuration/sample-production.engine.ini +++ b/configuration/sample-production.engine.ini @@ -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 mailsubject_prefix="[Aura Engine]" -[dataurls] -# The URL to get the Calendar via PV/Steering -api_calendar_url="http://localhost:8000/api/v1/playout" -# The URL to get show details via PV/Steering -api_show_url="http://localhost:8000/api/v1/shows/${ID}/" +[api] + +# STEERING +api_steering_status = "http://localhost:8000/api/v1/playout" +# 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 -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 exposed_api_url=http://localhost:3333/api/v1/ @@ -120,13 +126,13 @@ soundsystem="alsa" # with alsa you have to write the devicenames like hw:0 # with pulse and jack => an non empty value means it is used # devices with empty string are ignored and not used -input_device_0="" +input_device_0="hw:0" input_device_1="" input_device_2="" input_device_3="" input_device_4="" # same same, but different -output_device_0="default" +output_device_0="hw:0" output_device_1="" output_device_2="" output_device_3="" diff --git a/docs/configuration-guide.md b/docs/configuration-guide.md index 75f6d8d54d68704887ff8d644aa22d30dfbce980..53c24003842108f87883266a53ce85caafcba680 100644 --- a/docs/configuration-guide.md +++ b/docs/configuration-guide.md @@ -112,7 +112,7 @@ Configure monitoring parameters such as admin emails in the `[monitoring]` secti ## 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 web applications which access the API. diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 9c656672b61c7fd18a4360ec11ec7510c5c78582..78570ce733fe92c64ea7a62bde0c357107b25d17 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -44,17 +44,21 @@ using a fancy calendar interface. 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 - 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 - 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 to be played and its meta-data: + # TANK + api_tank_status = "http://localhost:8040/ui/" # 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> diff --git a/docs/installation-development.md b/docs/installation-development.md index a9fb3204f1d2e2d64cb07745356b765893d1a6e2..97ba490964e38d4838a725f0f6510bb1557547e4 100644 --- a/docs/installation-development.md +++ b/docs/installation-development.md @@ -124,13 +124,17 @@ Now, specify at least following settings to get started: Set the URLs to the *Steering* and *Tank* API: ```ini -[dataurls] -# The URL to get the Calendar via PV/Steering -api_calendar_url="http://localhost:8000/api/v1/playout" -# The URL to get show details via PV/Steering -api_show_url="http://localhost:8000/api/v1/shows/${ID}/" -# The URL to get playlist details via Tank -api_playlist_url="http://localhost:8040/api/v1/shows/${SLUG}/playlists" + # STEERING + api_steering_status = "http://localhost:8000/api/v1/" + # 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 + api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists" ``` Ensure that the Liquidsoap installation path is valid: diff --git a/docs/installation-production.md b/docs/installation-production.md index 110693a743ec3611267b8e766986784a23f94ba9..fc1f208af4a8b800f470e683e5842e52baf686dd 100644 --- a/docs/installation-production.md +++ b/docs/installation-production.md @@ -175,13 +175,17 @@ Now, specify at least following settings to get started: Set the URLs to the *Steering* and *Tank* API: ```ini -[dataurls] -# The URL to get the Calendar via Steering -api_calendar_url="https://aura-test.o94.at/steering/api/v1/playout" -# The URL to get show details via Steering -api_show_url="http://aura-test.o94.at/steering/api/v1/shows/${ID}/" -# The URL to get playlist details via Tank -api_playlist_url="http://aura-test.o94.at/tank/api/v1/shows/${SLUG}/playlists" + # STEERING + api_steering_status = "http://localhost:8000/api/v1/" + # 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 + api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists" ``` Ensure that the Liquidsoap installation path is valid: diff --git a/guru.py b/guru.py index 240769ba36df94aea79c84c452d8236d92cd8430..9ac3143b66d7d4f89bb1c7989fdd1b3d2797e571 100755 --- a/guru.py +++ b/guru.py @@ -123,7 +123,7 @@ class Guru(): self.parser.add_argument("-sd", "--shutdown", action="store_true", dest="shutdown", default=False, help="Shutting down aura server") # 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") # send a redis message diff --git a/meta.py b/meta.py index b4c5956bf37d27c81532d23348bc4ba8117e816c..0993d55d3c148067731ff80c7486b30575bb4b03 100644 --- a/meta.py +++ b/meta.py @@ -3,8 +3,8 @@ __author__ = "David Trattnig and Gottfried Gaisbauer" __copyright__ = "Copyright 2017-2020, Aura Engine Team" __credits__ = ["David Trattnig", "Gottfried Gaisbauer", "Michael Liebler"] __license__ = "GNU Affero General Public License (AGPL) Version 3" -__version__ = "0.6.3" -__version_info__ = (0, 6, 3) +__version__ = "0.6.4" +__version_info__ = (0, 6, 4) __maintainer__ = "David Trattnig" __email__ = "david.trattnig@subsquare.at" __status__ = "Development" \ No newline at end of file diff --git a/modules/base/exceptions.py b/modules/base/exceptions.py index c17c3618e25bac2d3c6934d538068183a2ce78d2..d77d205a9558cc2e70416d9f4969de1065f4b96b 100644 --- a/modules/base/exceptions.py +++ b/modules/base/exceptions.py @@ -47,6 +47,9 @@ class NoActiveEntryException(Exception): # Monitoring Exceptions +class EngineMalfunctionException(Exception): + pass + class MailingException(Exception): pass diff --git a/modules/core/monitor.py b/modules/core/monitor.py new file mode 100644 index 0000000000000000000000000000000000000000..f2bc75a41116894a68dd315d6adb3c921964b023 --- /dev/null +++ b/modules/core/monitor.py @@ -0,0 +1,130 @@ +# +# 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 diff --git a/modules/core/startup.py b/modules/core/startup.py index 6ba236381c2096fb442cf1a8c0e2e6d824417010..32e7f60a8f7e7cff0fa416e5ec65116c077206d8 100644 --- a/modules/core/startup.py +++ b/modules/core/startup.py @@ -23,11 +23,12 @@ import logging import datetime import threading 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.utils import TerminalColors, SimpleUtil, EngineUtil - +from modules.core.monitor import Monitoring class StartupThread(threading.Thread): """ @@ -39,7 +40,7 @@ class StartupThread(threading.Thread): active_entry = None soundsystem = None scheduler = None - + monitoring = None def __init__(self, soundsystem): """ @@ -49,7 +50,7 @@ class StartupThread(threading.Thread): self.logger = logging.getLogger("AuraEngine") self.soundsystem = soundsystem self.scheduler = soundsystem.scheduler - + self.monitoring = Monitoring(soundsystem.config, soundsystem) def run(self): @@ -58,6 +59,15 @@ class StartupThread(threading.Thread): """ try: 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.scheduler.on_ready() diff --git a/modules/scheduling/calender_fetcher.py b/modules/scheduling/calender_fetcher.py index 814f8a038ea66df4f08e23e797edb327af5d900a..fda26ad588adc42c553d9511c074064379ead155 100644 --- a/modules/scheduling/calender_fetcher.py +++ b/modules/scheduling/calender_fetcher.py @@ -34,10 +34,9 @@ class CalendarFetcher: """ self.config = config self.logger = logging.getLogger("AuraEngine") - self.__set_url__("api_calendar_url") - self.__set_url__("api_playlist_url") - self.__set_url__("api_show_url") - + self.__set_url__("api_steering_calendar") + self.__set_url__("api_tank_playlist") + self.__set_url__("api_steering_show") # # PUBLIC METHODS @@ -54,11 +53,11 @@ class CalendarFetcher: self.logger.debug("Fetching schedules from STEERING") self.fetched_schedule_data = self.__fetch_schedule_data__() 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 return None 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 return None @@ -67,11 +66,11 @@ class CalendarFetcher: self.logger.debug("Fetching playlists from TANK") self.__fetch_schedule_playlists__() 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 return None 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 return None @@ -129,7 +128,7 @@ class CalendarFetcher: Returns: ([Schedule]): An array of schedules """ - servicetype = "api_calendar_url" + servicetype = "api_steering_calendar" schedule = None # fetch data from steering @@ -193,7 +192,7 @@ class CalendarFetcher: Returns: (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"])) json_response = self.__fetch_data__(servicetype, url) @@ -221,7 +220,7 @@ class CalendarFetcher: Returns: ([Schedule]): Array of playlists """ - servicetype = "api_playlist_url" + servicetype = "api_tank_playlist" # fetch playlists from TANK if not "show_slug" in schedule: