added code to initialize db with models and recodres
This commit is contained in:
@@ -5,6 +5,7 @@ Backend base module
|
||||
import logging
|
||||
from io import StringIO
|
||||
from logging.config import dictConfig
|
||||
from logging.handlers import MemoryHandler
|
||||
from typing import Union
|
||||
|
||||
import coloredlogs as coloredlogs
|
||||
@@ -28,6 +29,8 @@ __email__ = "it@t-kurze.de"
|
||||
# __status__ = "Production"
|
||||
__status__ = "Development"
|
||||
|
||||
from .tools.send_mail import get_smtp_default_handler
|
||||
|
||||
dictConfig({
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
@@ -82,6 +85,9 @@ dictConfig({
|
||||
main_logger = logging.getLogger("lrc")
|
||||
|
||||
# following might be dangerous, as buffer might be filled without a mechanism to empty it
|
||||
smtp_error_handler = get_smtp_default_handler(subject="Warnings, errors and worse...!")
|
||||
mem_handler = MemoryHandler(capacity=10, flushLevel=logging.ERROR, target=smtp_error_handler)
|
||||
mem_handler.setLevel(logging.WARNING)
|
||||
# error_log_stream = StringIO()
|
||||
# error_log_stream_handler = logging.StreamHandler(stream=error_log_stream)
|
||||
# error_log_stream_handler.setLevel(logging.ERROR)
|
||||
|
||||
@@ -8,7 +8,8 @@ import ssl
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
|
||||
from backend import app, db
|
||||
from backend.models import pre_fill_table
|
||||
from backend.models import room_model, recorder_model
|
||||
from backend.recorder_adapters import get_defined_recorder_adapters
|
||||
from backend.tools.model_updater import update_recorder_models_database
|
||||
|
||||
|
||||
@@ -30,7 +31,8 @@ def main():
|
||||
except Exception as e:
|
||||
logging.critical(e)
|
||||
|
||||
pre_fill_table()
|
||||
room_model.pre_fill_table()
|
||||
recorder_model.pre_fill_table()
|
||||
update_recorder_models_database()
|
||||
|
||||
app.run(debug=True, host="0.0.0.0")
|
||||
|
||||
Binary file not shown.
@@ -1,10 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Models for lecture recorder
|
||||
"""
|
||||
import importlib
|
||||
import json
|
||||
import pkgutil
|
||||
import os
|
||||
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
@@ -14,11 +18,18 @@ from sqlalchemy import or_
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from backend.models.virtual_command_model import virtual_command_recorder_command_table, virtual_command_recorder_table
|
||||
from backend.recorder_adapters import get_defined_recorder_adapters
|
||||
from backend.tools.helpers import file_md5
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
KNOWN_RECORDERS = {re.compile(r'(SMP)[\s]*([\d]+)[\s]*.?[\s]*([\S]*)'): 'SMP',
|
||||
re.compile(
|
||||
r'(LectureRecorder X2|LectureRecorder|VGADVI Recorder|DVI Broadcaster DL|DVIRecorderDL)'): 'Epiphan'}
|
||||
|
||||
|
||||
class RecorderModel(db.Model):
|
||||
__table_args__ = {'extend_existing': True}
|
||||
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
|
||||
last_time_modified = db.Column(db.DateTime, nullable=True, default=None)
|
||||
@@ -27,7 +38,8 @@ class RecorderModel(db.Model):
|
||||
notes = db.Column(db.Unicode(255), unique=False, nullable=True, default=None)
|
||||
recorder_commands = db.relationship('RecorderCommand', back_populates='recorder_model')
|
||||
recorders = db.relationship('Recorder', back_populates='recorder_model')
|
||||
checksum = db.Column(db.String(63), unique=True, nullable=False)
|
||||
checksum = db.Column(db.String(63), unique=True,
|
||||
nullable=False) # checksum of the recorder commands! (see: model_updater.py)
|
||||
_requires_user = db.Column(db.Integer, default=False, name='requires_user')
|
||||
_requires_password = db.Column(db.Integer, default=True, name='requires_password')
|
||||
|
||||
@@ -65,6 +77,7 @@ class RecorderModel(db.Model):
|
||||
|
||||
|
||||
class Recorder(db.Model):
|
||||
__table_args__ = {'extend_existing': True}
|
||||
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
|
||||
last_time_modified = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
|
||||
@@ -76,6 +89,8 @@ class Recorder(db.Model):
|
||||
room_id = db.Column(db.Integer, db.ForeignKey('room.id'))
|
||||
room = db.relationship('Room', uselist=False, back_populates='recorder') # one-to-one relation (uselist=False)
|
||||
ip = db.Column(db.String(15), unique=True, nullable=True, default=None)
|
||||
configured_options_json_string = db.Column(db.UnicodeText, default='')
|
||||
firmware_version = db.Column(db.String, nullable=True, default=None)
|
||||
ip6 = db.Column(db.String(46), unique=True, nullable=True, default=None)
|
||||
network_name = db.Column(db.String(127), unique=True, nullable=True, default=None)
|
||||
telnet_port = db.Column(db.Integer, unique=False, nullable=False, default=23)
|
||||
@@ -84,7 +99,8 @@ class Recorder(db.Model):
|
||||
password = db.Column(db.String, nullable=True, default=None)
|
||||
recorder_model_id = db.Column(db.Integer, db.ForeignKey('recorder_model.id'))
|
||||
recorder_model = db.relationship('RecorderModel', back_populates='recorders')
|
||||
virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_table, back_populates='recorders')
|
||||
virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_table,
|
||||
back_populates='recorders')
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Recorder, self).__init__(**kwargs)
|
||||
@@ -106,6 +122,17 @@ class Recorder(db.Model):
|
||||
assert len(value) > 2
|
||||
return value
|
||||
|
||||
@hybrid_property
|
||||
def configured_options(self) -> list:
|
||||
return json.loads(self.configured_options_json_string)
|
||||
|
||||
@configured_options.setter
|
||||
def configured_options(self, value: list):
|
||||
self.configured_options_json_string = json.dumps(value)
|
||||
|
||||
def add_configured_option(self, value: str):
|
||||
self.configured_options_json_string = json.dumps(self.configured_options.append(value))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -118,10 +145,11 @@ class Recorder(db.Model):
|
||||
|
||||
|
||||
class RecorderCommand(db.Model):
|
||||
__table_args__ = {'extend_existing': True}
|
||||
"""Table containing permissions associated with groups."""
|
||||
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
|
||||
last_time_modified = db.Column(db.DateTime, nullable=True, default=None)
|
||||
last_time_modified = db.Column(db.DateTime, nullable=True, default=datetime.utcnow())
|
||||
name = db.Column(db.Unicode(63), unique=True, nullable=False)
|
||||
alternative_name = db.Column(db.Unicode(63), unique=True, nullable=True, default=None)
|
||||
disabled = db.Column(db.Boolean, default=False)
|
||||
@@ -130,7 +158,7 @@ class RecorderCommand(db.Model):
|
||||
recorder_model = db.relationship('RecorderModel', back_populates='recorder_commands')
|
||||
recorder_model_id = db.Column(db.Integer, db.ForeignKey('recorder_model.id'))
|
||||
virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_command_table,
|
||||
back_populates='recorder_commands')
|
||||
back_populates='recorder_commands')
|
||||
|
||||
@staticmethod
|
||||
def get_all():
|
||||
@@ -145,3 +173,35 @@ class RecorderCommand(db.Model):
|
||||
@parameters.setter
|
||||
def parameters(self, parameters_dict: dict):
|
||||
self.parameters_string = json.dumps(parameters_dict)
|
||||
|
||||
|
||||
def pre_create_recorders():
|
||||
models = set()
|
||||
f = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, 'models', 'initial_recorders.json'))
|
||||
with open(f, 'r') as json_file:
|
||||
recorders = json.load(json_file)['recorders']
|
||||
for r in recorders:
|
||||
type = r.get('type')
|
||||
firmware_version = r.get('firmware_version', None)
|
||||
for k_r in KNOWN_RECORDERS:
|
||||
if match := k_r.search(type):
|
||||
print(KNOWN_RECORDERS[k_r])
|
||||
print(match)
|
||||
print(match.groups())
|
||||
|
||||
models.add(r.get('type', ''))
|
||||
models.discard('')
|
||||
print(models)
|
||||
|
||||
|
||||
def pre_fill_table():
|
||||
try:
|
||||
pre_create_recorders()
|
||||
print(db.session.commit())
|
||||
except IntegrityError as e:
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# pre_create_recorder_models()
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import pkgutil
|
||||
import sys
|
||||
import telnetlib
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
defined_recorder_adapters = None
|
||||
|
||||
# monkey patching of telnet lib
|
||||
from pprint import pprint
|
||||
|
||||
original_read_until = telnetlib.Telnet.read_until
|
||||
original_write = telnetlib.Telnet.write
|
||||
|
||||
@@ -92,6 +97,16 @@ class TelnetAdapter(ABC):
|
||||
|
||||
|
||||
class RecorderAdapter:
|
||||
def __init__(self, address: str, user: str, password: str):
|
||||
self.address = address
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_recorder_params(cls) -> dict:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _get_name(self):
|
||||
pass
|
||||
@@ -108,13 +123,15 @@ class RecorderAdapter:
|
||||
pass
|
||||
|
||||
|
||||
def get_defined_recorder_adapters():
|
||||
def get_defined_recorder_adapters() -> list:
|
||||
rec_adapters_module = importlib.import_module(".recorder_adapters", package='backend')
|
||||
rec_adapter_class = getattr(rec_adapters_module, "RecorderAdapter") # needed, otherwise subclass check may fail
|
||||
models = []
|
||||
found_packages = list(pkgutil.iter_modules(sys.modules[__name__].__path__))
|
||||
found_packages = list(pkgutil.iter_modules(rec_adapters_module.__path__))
|
||||
for f_p in found_packages:
|
||||
importer = f_p[0]
|
||||
rec_model_module = importer.find_module(f_p[1]).load_module(f_p[1])
|
||||
rec_model = {'id': f_p[1], 'name': f_p[1], 'commands': {}}
|
||||
rec_model = {'id': f_p[1], 'name': f_p[1], 'commands': {}, 'path': rec_model_module.__file__}
|
||||
if hasattr(rec_model_module, 'RECORDER_MODEL_NAME'):
|
||||
rec_model['name'] = rec_model_module.RECORDER_MODEL_NAME
|
||||
if hasattr(rec_model_module, 'REQUIRES_USER'):
|
||||
@@ -122,7 +139,9 @@ def get_defined_recorder_adapters():
|
||||
if hasattr(rec_model_module, 'REQUIRES_PW'):
|
||||
rec_model['requires_password'] = rec_model_module.REQUIRES_PW
|
||||
for name, obj in inspect.getmembers(rec_model_module, inspect.isclass):
|
||||
if issubclass(obj, RecorderAdapter):
|
||||
if issubclass(obj, rec_adapter_class) and name != "RecorderAdapter":
|
||||
rec_model['id'] = rec_model['id'] + "." + obj.__name__
|
||||
rec_model['class'] = obj
|
||||
commands = {}
|
||||
for method_name, method in inspect.getmembers(obj, predicate=inspect.isfunction):
|
||||
if len(method_name) > 0 and "_" == method_name[0]:
|
||||
@@ -141,3 +160,19 @@ def get_defined_recorder_adapters():
|
||||
rec_model["commands"] = commands
|
||||
models.append(rec_model)
|
||||
return models
|
||||
|
||||
|
||||
def get_recorder_adapter_by_id(id: str, **kwargs):
|
||||
global defined_recorder_adapters
|
||||
if defined_recorder_adapters is None:
|
||||
defined_recorder_adapters = get_defined_recorder_adapters()
|
||||
for rec_adapter in defined_recorder_adapters:
|
||||
if id in rec_adapter.get('id', '').split("."):
|
||||
return rec_adapter['class'](**kwargs)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(get_defined_recorder_adapters())
|
||||
get_recorder_adapter_by_id('SMP35x', address="172.22.246.207", password="123mzsmp")
|
||||
exit()
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
# Copyright (c) 2019. Tobias Kurze
|
||||
"""
|
||||
This is the recorder adapter implementation for Epiphan Recorders. The following Epiphan recorder models are supported:
|
||||
- LectureRecorder X2
|
||||
- LectureRecorder
|
||||
- VGADVI Recorder
|
||||
- DVI Broadcaster DL
|
||||
- DVIRecorderDL
|
||||
"""
|
||||
import shutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
@@ -14,39 +22,44 @@ from backend.recorder_adapters import RecorderAdapter
|
||||
# HOST = "localhost"
|
||||
from backend.tools.exception_decorator import exception_decorator
|
||||
|
||||
RECORDER_MODEL_NAME = "Epiphan Recorder Adapter (for: )"
|
||||
|
||||
BASE_URL = "http://172.23.8.102" # Audimax SMP 351
|
||||
|
||||
USER = "admin"
|
||||
PW = "lrgrashof+-"
|
||||
|
||||
|
||||
class EpiphanV1(RecorderAdapter):
|
||||
|
||||
def __init__(self, url: str, admin_user: str, admin_pw: str):
|
||||
if not url.startswith('http'):
|
||||
url = 'http://' + url
|
||||
self.url = url
|
||||
self.user = admin_user
|
||||
self.password = admin_pw
|
||||
class Epiphan(RecorderAdapter):
|
||||
def __init__(self, address: str, user: str, password: str, firmware_version: str = "", **kwargs):
|
||||
if not address.startswith('http'):
|
||||
address = 'http://' + address
|
||||
super().__init__(address, user, password)
|
||||
self.firmware_version = firmware_version
|
||||
self.session = requests.Session()
|
||||
self.session.auth = HTTPBasicAuth(self.user, self.password)
|
||||
|
||||
@classmethod
|
||||
def get_recorder_params(cls) -> dict:
|
||||
return {'_requires_user': True,
|
||||
'_requires_password': True}
|
||||
|
||||
def _get_name(self):
|
||||
pass
|
||||
return RECORDER_MODEL_NAME
|
||||
|
||||
def _get_version(self):
|
||||
pass
|
||||
|
||||
@exception_decorator(ConnectionError)
|
||||
def get_recording_status(self) -> dict:
|
||||
res = self.session.get(self.url + "/admin/ajax/recorder_status.cgi")
|
||||
res = self.session.get(self.address + "/admin/ajax/recorder_status.cgi")
|
||||
if res.ok:
|
||||
return res.json()
|
||||
raise LrcException(res.text, res.status_code)
|
||||
|
||||
@exception_decorator(ConnectionError)
|
||||
def get_sysinfo(self) -> dict:
|
||||
res = self.session.get(self.url + "/ajax/sysinfo.cgi")
|
||||
res = self.session.get(self.address + "/ajax/sysinfo.cgi")
|
||||
if res.ok:
|
||||
return res.json()
|
||||
raise LrcException(res.text, res.status_code)
|
||||
@@ -63,13 +76,13 @@ class EpiphanV1(RecorderAdapter):
|
||||
return self.get_recording_status().get('seconds', None)
|
||||
|
||||
def start_recording(self):
|
||||
res = self.session.get(self.url + "/admin/ajax/start_recorder.cgi")
|
||||
res = self.session.get(self.address + "/admin/ajax/start_recorder.cgi")
|
||||
if not res.ok:
|
||||
raise LrcException(res.text, res.status_code)
|
||||
time.sleep(2) # just a little bit of waiting time -> it takes a bit for the Epiphan to update its state
|
||||
|
||||
def stop_recording(self):
|
||||
res = self.session.get(self.url + "/admin/ajax/stop_recorder.cgi")
|
||||
res = self.session.get(self.address + "/admin/ajax/stop_recorder.cgi")
|
||||
if not res.ok:
|
||||
raise LrcException(res.text, res.status_code)
|
||||
time.sleep(4) # just a little bit of waiting time -> it takes a bit for the Epiphan to update its state
|
||||
@@ -114,7 +127,7 @@ class EpiphanV1(RecorderAdapter):
|
||||
raise LrcException(str(err))
|
||||
|
||||
def get_screenshot(self):
|
||||
ret = self.session.get(self.url + "/admin/grab_frame.cgi?size=256x192&device=DAV93133.vga&_t=1573471990578",
|
||||
ret = self.session.get(self.address + "/admin/grab_frame.cgi?size=256x192&device=DAV93133.vga&_t=1573471990578",
|
||||
stream=True)
|
||||
|
||||
print(ret)
|
||||
@@ -125,7 +138,7 @@ class EpiphanV1(RecorderAdapter):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
e = EpiphanV1(BASE_URL, USER, PW)
|
||||
e = Epiphan(BASE_URL, USER, PW)
|
||||
try:
|
||||
# print(e.is_recording())
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
"""
|
||||
Recorder Adapter for SMP
|
||||
"""
|
||||
import logging
|
||||
|
||||
from backend import LrcException
|
||||
@@ -6,7 +9,7 @@ from backend.tools.exception_decorator import exception_decorator
|
||||
|
||||
logger = logging.getLogger("lrc.recorder_adapters.extron_smp")
|
||||
|
||||
RECORDER_MODEL_NAME = "SMP 351 / 352"
|
||||
RECORDER_MODEL_NAME = "Recorder Adapter for SMP 351 and 352"
|
||||
VERSION = "0.9.0"
|
||||
REQUIRES_USER = False
|
||||
REQUIRES_PW = True
|
||||
@@ -19,14 +22,14 @@ HOST = "129.13.51.109" # Hertz
|
||||
|
||||
USER = "admin"
|
||||
PW = "123mzsmp"
|
||||
#PW = "audimaxsmp"
|
||||
# PW = "audimaxsmp"
|
||||
PW = "smphertz"
|
||||
|
||||
|
||||
class SMP(TelnetAdapter, RecorderAdapter):
|
||||
class SMP35x(TelnetAdapter, RecorderAdapter):
|
||||
def __init__(self, address, password, auto_login=True, **kwargs):
|
||||
super().__init__(address)
|
||||
self.admin_pw = password
|
||||
RecorderAdapter.__init__(self, address, "", password)
|
||||
TelnetAdapter.__init__(self, address)
|
||||
if auto_login:
|
||||
self._login()
|
||||
|
||||
@@ -40,7 +43,7 @@ class SMP(TelnetAdapter, RecorderAdapter):
|
||||
raise LrcException(str(e))
|
||||
self.tn.read_until("\r\nPassword:")
|
||||
# password = getpass.getpass()
|
||||
password = self.admin_pw
|
||||
password = self.password
|
||||
self.tn.write(password + "\n\r")
|
||||
|
||||
out = self.tn.assert_string_in_output("Login Administrator")
|
||||
@@ -51,10 +54,10 @@ class SMP(TelnetAdapter, RecorderAdapter):
|
||||
# TODO: loop until logged in...
|
||||
logger.warning("Could not login (as admin) with given password! {}".format(self.address))
|
||||
print("re-enter pw")
|
||||
self.tn.write(self.admin_pw+"\n\r")
|
||||
self.tn.write(self.password + "\n\r")
|
||||
print(self.tn.assert_string_in_output("Login Administrator"))
|
||||
print("WRONG (admin) password!! Exiting!")
|
||||
print(self.admin_pw)
|
||||
print(self.password)
|
||||
self.tn = None
|
||||
logger.error("Could definitely not login (as admin) with given password! {}".format(self.address))
|
||||
raise Exception("Could not login as administrator with given pw!")
|
||||
@@ -725,6 +728,11 @@ class SMP(TelnetAdapter, RecorderAdapter):
|
||||
some advanced options skipped
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_recorder_params(cls) -> dict:
|
||||
return {'_requires_user': False,
|
||||
'_requires_password': True}
|
||||
|
||||
def get_input_hdcp_status(self, input_number: int):
|
||||
"""
|
||||
returns:
|
||||
@@ -783,9 +791,8 @@ class SMP(TelnetAdapter, RecorderAdapter):
|
||||
return TelnetAdapter._get_response_str(self.tn.read_until_non_empty_line())
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
smp = SMP(HOST, PW, True)
|
||||
smp = SMP35x(HOST, PW, True)
|
||||
|
||||
print(smp)
|
||||
print(smp.get_recording_status())
|
||||
@@ -833,6 +840,6 @@ def main():
|
||||
print(smp.unmute_output(2))
|
||||
print(smp.is_muted(2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
18
backend/tools/helpers.py
Normal file
18
backend/tools/helpers.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import hashlib
|
||||
|
||||
|
||||
|
||||
def calculate_md5_checksum(string_to_md5_sum: str):
|
||||
return hashlib.md5(string_to_md5_sum.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def file_md5(fname: str) -> str:
|
||||
hash_md5 = hashlib.md5()
|
||||
with open(fname, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_md5.update(chunk)
|
||||
return hash_md5.hexdigest()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(file_md5(__file__))
|
||||
@@ -14,10 +14,7 @@ from sqlalchemy import and_
|
||||
from backend import db
|
||||
from backend.models.recorder_model import RecorderModel, RecorderCommand
|
||||
from backend.recorder_adapters import get_defined_recorder_adapters
|
||||
|
||||
|
||||
def calculate_md5_checksum(string_to_md5_sum: str):
|
||||
return hashlib.md5(string_to_md5_sum.encode('utf-8')).hexdigest()
|
||||
from backend.tools.helpers import calculate_md5_checksum
|
||||
|
||||
|
||||
def create_recorder_commands_for_recorder_adapter(command_definitions: dict, recorder_model: RecorderModel):
|
||||
@@ -61,9 +58,9 @@ def update_recorder_models_database():
|
||||
else:
|
||||
if not model_checksum == r_m.checksum:
|
||||
r_m.model_name = r_a["name"]
|
||||
r_m.model_name = r_a["name"]
|
||||
r_m.last_time_modified = datetime.utcnow()
|
||||
r_m.checksum = model_checksum
|
||||
create_recorder_commands_for_recorder_adapter(r_a["commands"], r_m)
|
||||
create_recorder_commands_for_recorder_adapter(r_a["commands"], r_m)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import smtplib
|
||||
import traceback
|
||||
from email.message import EmailMessage
|
||||
from logging.handlers import SMTPHandler
|
||||
from typing import List, Union
|
||||
|
||||
from backend import main_logger, Config
|
||||
from backend import Config
|
||||
|
||||
|
||||
def send_mail(subject: str, msg_body: str, to_mail: List, from_mail: str = Config.FROM_MAIL):
|
||||
@@ -18,7 +19,7 @@ def send_mail(subject: str, msg_body: str, to_mail: List, from_mail: str = Confi
|
||||
s.send_message(msg)
|
||||
s.quit()
|
||||
except Exception as e:
|
||||
main_logger.error(
|
||||
print(
|
||||
"Could not send E-Mail (Exception: {})".format(str(e)))
|
||||
|
||||
|
||||
@@ -43,3 +44,17 @@ def send_error_mail(message_or_exception: Union[str, Exception], subject: str =
|
||||
|
||||
def send_warning_mail():
|
||||
pass
|
||||
|
||||
|
||||
def get_smtp_default_handler(receiver_mail_addresses=Config.ADMIN_E_MAIL_TO, subject: str = None,
|
||||
subject_prefix: str = "[LRC] Log: "):
|
||||
|
||||
subject = subject if subject.startswith("[LRC]") else subject_prefix + subject
|
||||
return SMTPHandler(Config.SMTP_SERVER, Config.FROM_MAIL, receiver_mail_addresses, subject)
|
||||
|
||||
|
||||
def get_smtp_error_handler(receiver_mail_addresses=Config.ERROR_E_MAIL_TO, subject: str = None,
|
||||
subject_prefix: str = "[LRC] Error: "):
|
||||
|
||||
subject = subject if subject.startswith("[LRC]") else subject_prefix + subject
|
||||
return SMTPHandler(Config.SMTP_SERVER, Config.FROM_MAIL, receiver_mail_addresses, subject)
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import threading
|
||||
from io import StringIO
|
||||
from logging.handlers import MemoryHandler
|
||||
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from multiprocessing.pool import ThreadPool
|
||||
from multiprocessing.context import TimeoutError
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
from ics import Calendar
|
||||
|
||||
from backend import LrcException
|
||||
from backend.config import Config
|
||||
from backend.recorder_adapters import RecorderAdapter
|
||||
|
||||
from backend.recorder_adapters.epiphan_base import EpiphanV1
|
||||
from backend.recorder_adapters.extron_smp import SMP
|
||||
from backend.tools.send_mail import send_error_mail
|
||||
from backend.recorder_adapters.epiphan_base import Epiphan
|
||||
from backend.recorder_adapters.extron_smp import SMP35x
|
||||
from backend.tools.send_mail import send_error_mail, get_smtp_error_handler
|
||||
|
||||
logger = logging.getLogger("lrc.tools.simple_state_checker")
|
||||
|
||||
smtp_error_handler = get_smtp_error_handler(subject="Errors have been detected while checking recorder states!")
|
||||
#mem_handler = MemoryHandler(capacity=100, flushLevel=logging.FATAL, target=smtp_error_handler)
|
||||
#mem_handler.setLevel(logging.WARNING)
|
||||
|
||||
rec_err_state_log_stream = StringIO()
|
||||
rec_err_state_log_stream_handler = logging.StreamHandler(stream=rec_err_state_log_stream)
|
||||
rec_err_state_log_stream_handler.setLevel(logging.WARNING)
|
||||
|
||||
logger.addHandler(rec_err_state_log_stream_handler)
|
||||
#logger.addHandler(mem_handler)
|
||||
|
||||
base_url = "https://opencast.bibliothek.kit.edu"
|
||||
|
||||
@@ -37,6 +43,9 @@ config = {'service_urls': {}}
|
||||
|
||||
recorders = None
|
||||
|
||||
agent_states_lock = threading.RLock()
|
||||
agent_states = {}
|
||||
|
||||
|
||||
def get_service_url(service_type: str):
|
||||
if service_type in config['service_urls']:
|
||||
@@ -90,9 +99,9 @@ def notify_users_of_problem(msg: str):
|
||||
|
||||
def get_recorder_adapter(recorder_info: dict) -> RecorderAdapter:
|
||||
if "SMP" in recorder_info["type"]:
|
||||
rec = SMP(recorder_info['ip'], recorder_info['password'])
|
||||
rec = SMP35x(recorder_info['ip'], recorder_info['password'])
|
||||
else:
|
||||
rec = EpiphanV1(recorder_info['ip'], recorder_info["username"], recorder_info["password"])
|
||||
rec = Epiphan(recorder_info['ip'], recorder_info["username"], recorder_info["password"])
|
||||
return rec
|
||||
|
||||
|
||||
@@ -108,9 +117,13 @@ def check_capture_agent_state(a: dict):
|
||||
rec = get_recorder_adapter(recorder_info)
|
||||
if rec.is_recording():
|
||||
logger.info("OK – recorder {} is recording :)".format(a['name']))
|
||||
with agent_states_lock:
|
||||
agent_states[a['name']] = 'OK - recorder is recording'
|
||||
else:
|
||||
logger.info(rec.get_recording_status())
|
||||
logger.error("FATAL - recorder {} must be recording but is not!!!!".format(a['name']))
|
||||
with agent_states_lock:
|
||||
agent_states[a['name']] = 'FATAL - recorder is NOT recording, but should!'
|
||||
except LrcException as e:
|
||||
logger.fatal("Exception occurred: {}".format(str(e)))
|
||||
logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip']))
|
||||
@@ -122,20 +135,51 @@ def check_capture_agent_state(a: dict):
|
||||
rec = get_recorder_adapter(recorder_info)
|
||||
if rec.is_recording():
|
||||
logger.error("FATAL - recorder must not be recording!!!!")
|
||||
with agent_states_lock:
|
||||
agent_states[a['name']] = 'FATAL - recorder IS recording, but should NOT!'
|
||||
else:
|
||||
logger.info("OK – recorder is not recording :)")
|
||||
with agent_states_lock:
|
||||
agent_states[a['name']] = 'OK - recorder is NOT recording'
|
||||
except LrcException as e:
|
||||
logger.fatal("Exception occurred: {}".format(str(e)))
|
||||
logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip']))
|
||||
|
||||
|
||||
def ping_capture_agent(a: dict):
|
||||
recorder_ip = get_recorder_by_name(a['name'])['ip']
|
||||
try:
|
||||
response = subprocess.check_call(
|
||||
['ping', '-W', '10', '-c', '2', recorder_ip],
|
||||
# stderr=subprocess.STDOUT, # get all output
|
||||
stdout=subprocess.DEVNULL, # suppress output
|
||||
stderr=subprocess.DEVNULL,
|
||||
universal_newlines=True # return string not bytes
|
||||
)
|
||||
logger.info("Successfully pinged {} ({}). :-)".format(a['name'], recorder_ip))
|
||||
except subprocess.CalledProcessError:
|
||||
logger.error("Can not ping {} ({})!!".format(a['name'], recorder_ip))
|
||||
|
||||
|
||||
agents = get_capture_agents()
|
||||
logger.info("Got {} capture agents that will be checked...".format(len(agents)))
|
||||
|
||||
for a in agents:
|
||||
agent_states[a['name']] = 'PROBLEMATIC - unknown'
|
||||
|
||||
|
||||
# pool = ThreadPool(5)
|
||||
# pool.map(check_capture_agent_state, agents)
|
||||
|
||||
NUM_THREADS = 5
|
||||
NUM_THREADS = 8
|
||||
|
||||
with ThreadPool(NUM_THREADS) as pool:
|
||||
results = [pool.apply_async(ping_capture_agent, (agent,)) for agent in agents]
|
||||
try:
|
||||
[res.get(timeout=12) for res in results]
|
||||
except TimeoutError as e:
|
||||
logger.error("Timeout while pinging capture agent! {}".format(e))
|
||||
|
||||
|
||||
with ThreadPool(NUM_THREADS) as pool:
|
||||
results = [pool.apply_async(check_capture_agent_state, (agent,)) for agent in agents]
|
||||
@@ -148,4 +192,9 @@ logger.info("DONE checking capture agents / recorders!")
|
||||
|
||||
logged_events = rec_err_state_log_stream.getvalue()
|
||||
if len(logged_events) > 0:
|
||||
logged_events += "\n\n=============\nAgent States:\n\n{}".format(''.join(
|
||||
"{:<48}: {}\n".format(a, agent_states[a]) for a in agent_states
|
||||
))
|
||||
send_error_mail(logged_events, "Errors have been detected while checking recorder states!")
|
||||
|
||||
#mem_handler.close()
|
||||
|
||||
Reference in New Issue
Block a user