From bab1556a4a3a3db535d4ea812d2a3c6764a1c8e1 Mon Sep 17 00:00:00 2001 From: Benjamin Zeiss Date: Fri, 18 Feb 2022 00:40:55 +0100 Subject: [PATCH] The protobuf proxy prototype didn't exist in the previous proxy branch for some reason. It's currently broken and needs to be rewritten to work with the python restructuring from the last release. So currently not a working version (but one that used to kinda work to some degree with some old version). --- python/proxy/raproxy_plugins/automount.ini | 6 + python/proxy/raproxy_plugins/automount.py | 120 ++++++++++ .../raproxy_plugins/automount.yapsy-plugin | 10 + python/proxy/raproxy_plugins/echo.py | 22 ++ .../echo.yapsy-plugin.disabled | 10 + .../proxy/raproxy_plugins/raproxy_attach.sh | 11 + .../proxy/raproxy_plugins/raproxy_detach.sh | 11 + python/proxy/raproxy_plugins/raproxy_eject.sh | 11 + python/proxy/raproxy_plugins/raproxy_init.sh | 9 + python/proxy/raproxy_plugins/shellscript.py | 17 ++ .../shellscript.yapsy-plugin.disabled | 10 + python/proxy/rascsi_proxy.py | 224 ++++++++++++++++++ python/proxy/requirements.txt | 2 + .../proxy/service-infra/rascsi-proxy.service | 12 + python/proxy/start.sh | 84 +++++++ 15 files changed, 559 insertions(+) create mode 100644 python/proxy/raproxy_plugins/automount.ini create mode 100644 python/proxy/raproxy_plugins/automount.py create mode 100644 python/proxy/raproxy_plugins/automount.yapsy-plugin create mode 100644 python/proxy/raproxy_plugins/echo.py create mode 100644 python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled create mode 100644 python/proxy/raproxy_plugins/raproxy_attach.sh create mode 100644 python/proxy/raproxy_plugins/raproxy_detach.sh create mode 100644 python/proxy/raproxy_plugins/raproxy_eject.sh create mode 100644 python/proxy/raproxy_plugins/raproxy_init.sh create mode 100644 python/proxy/raproxy_plugins/shellscript.py create mode 100644 python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled create mode 100644 python/proxy/rascsi_proxy.py create mode 100644 python/proxy/requirements.txt create mode 100644 python/proxy/service-infra/rascsi-proxy.service create mode 100644 python/proxy/start.sh diff --git a/python/proxy/raproxy_plugins/automount.ini b/python/proxy/raproxy_plugins/automount.ini new file mode 100644 index 00000000..0d8587a7 --- /dev/null +++ b/python/proxy/raproxy_plugins/automount.ini @@ -0,0 +1,6 @@ +automount_directory=/home/pi/automount +use_sudo=True +sudo=/usr/bin/sudo +mount=/usr/bin/mount +umount=/usr/bin/umount +automount_extension=.automount \ No newline at end of file diff --git a/python/proxy/raproxy_plugins/automount.py b/python/proxy/raproxy_plugins/automount.py new file mode 100644 index 00000000..5d5d98c1 --- /dev/null +++ b/python/proxy/raproxy_plugins/automount.py @@ -0,0 +1,120 @@ +from yapsy.IPlugin import IPlugin +from pathlib import Path +import configparser +import os +import subprocess + + +class RaProxyAutomountPlugin(IPlugin): + def detach(self, scsi_id, type, path): + file = os.path.basename(str(path)) + automount_path = Path(self.config['main']['automount_directory']).joinpath(file) + automount_image_config_file = str(path) + self.config['main']['automount_extension'] + if not Path(automount_image_config_file).is_file(): + print("RASCSI proxy automount image config file [" + automount_image_config_file + "] not found. Skipping " + "mount action.") + return + automount_image_config = configparser.ConfigParser() + with open(automount_image_config_file) as stream: + automount_image_config.read_string("[main]\n" + stream.read()) + + if not automount_image_config.has_option("main","mount_options"): + print("RASCSI proxy automount image config file is invalid and/or does not have a valid key [" + "mount_options]. Skipping mount action.") + return + + if not automount_path.exists(): + try: + automount_path.mkdir(parents=True, exist_ok=True) + except Exception as e: + print(e) + + try: + if self.config['main']['use_sudo']: + command = [self.config['main']['sudo'],self.config['main']['mount'], path, str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) + else: + command = [self.config['main']['mount'], path, str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) +# print("return code: " + str(return_code)) + if return_code != 0: + raise Exception('mounting ' + str(path) + ' failed on detach command. Unknown state!') + print("RASCSI proxy automount detach/eject action: image [" + str(path) + "] has been mounted to [" + str(f'{automount_path}/') + "].") + except Exception as e: + print(e) + + def attach(self, scsi_id, type, path): + file = os.path.basename(str(path)) + automount_path = Path(self.config['main']['automount_directory']).joinpath(file) + if not automount_path.exists(): + print("RASCSI proxy automount image mount directory [" + str(automount_path) + "] not found. Probably not " + "mounted. Skipping unmount" + " action.") + return + + if len(os.listdir(str(automount_path)) ) > 0: + try: + if self.config['main']['use_sudo']: + command = [self.config['main']['sudo'],self.config['main']['umount'],'-l', str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) + else: + command = [self.config['main']['umount'],'-l', str(f'{automount_path}/')] +# print(command) + return_code = subprocess.call(command) +# print("return code: " + str(return_code)) + if return_code != 0: + raise Exception('Umounting ' + str(path) + ' failed on attach command. Unknown state!') + + print("RASCSI proxy automount attach/insert action: image [" + str(path) + "] has been unmounted from [" + str(f'{automount_path}/') + "].") + + except Exception as e: + print(e) + + try: + automount_path.rmdir() + except Exception as e: + print(e) + + def init_hook(self): + print("RASCSI proxy automount plugin: init") + + self.config = configparser.ConfigParser() + + with open('./raproxy_plugins/automount.ini') as stream: + self.config.read_string("[main]\n" + stream.read()) + + path = Path(self.config['main']['automount_directory']) + if not path.exists(): + path.mkdir(parents=True, exist_ok=True) + + def detach_hook(self, scsi_id, type, path): + self.detach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("detaching from scsi id: " + str(scsi_id)) +# print("detaching file type : " + str(type)) +# print("detaching file : " + str(path)) + + def eject_hook(self, scsi_id, type, path): + self.detach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("ejecting from scsi id : " + str(scsi_id)) +# print("ejecting file type : " + str(type)) +# print("ejecting file : " + str(path)) + + def attach_hook(self, scsi_id, type, path): + self.attach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("attaching on scsi id : " + str(scsi_id)) +# print("attaching file type : " + str(type)) +# print("attaching file : " + str(path)) + + def insert_hook(self, scsi_id, type, path): + self.attach(scsi_id, type, path) +# print("automount path: " + str(path)) +# print("inserting on scsi id : " + str(scsi_id)) +# print("inserting file type : " + str(type)) +# print("inserting file : " + str(path)) + diff --git a/python/proxy/raproxy_plugins/automount.yapsy-plugin b/python/proxy/raproxy_plugins/automount.yapsy-plugin new file mode 100644 index 00000000..510a048f --- /dev/null +++ b/python/proxy/raproxy_plugins/automount.yapsy-plugin @@ -0,0 +1,10 @@ +[Core] +Name = RASCSI proxy automount plugin +Module = automount + +[Documentation] +Author = RASCSI Contributors +Version = 0.1 +Website = https://github.com/akuker/RASCSI +Description = RASCSI proxy automount plugin + diff --git a/python/proxy/raproxy_plugins/echo.py b/python/proxy/raproxy_plugins/echo.py new file mode 100644 index 00000000..08aec7ac --- /dev/null +++ b/python/proxy/raproxy_plugins/echo.py @@ -0,0 +1,22 @@ +from yapsy.IPlugin import IPlugin + + +class RaProxyEchoPlugin(IPlugin): + def init_hook(self): + print("RASCSI proxy echo plugin: init") + + def detach_hook(self, scsi_id, type, path): + print("detaching from scsi id: " + str(scsi_id)) + print("detaching file type : " + str(type)) + print("detaching file : " + str(path)) + + def eject_hook(self, scsi_id, type, path): + print("ejecting from scsi id : " + str(scsi_id)) + print("ejecting file type : " + str(type)) + print("ejecting file : " + str(path)) + + def attach_hook(self, scsi_id, type, path): + print("attaching on scsi id : " + str(scsi_id)) + print("attaching file type : " + str(type)) + print("attaching file : " + str(path)) + diff --git a/python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled b/python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled new file mode 100644 index 00000000..8b920d6a --- /dev/null +++ b/python/proxy/raproxy_plugins/echo.yapsy-plugin.disabled @@ -0,0 +1,10 @@ +[Core] +Name = RASCSI proxy echo plugin +Module = echo + +[Documentation] +Author = RASCSI Contributors +Version = 0.1 +Website = https://github.com/akuker/RASCSI +Description = RASCSI proxy echo plugin + diff --git a/python/proxy/raproxy_plugins/raproxy_attach.sh b/python/proxy/raproxy_plugins/raproxy_attach.sh new file mode 100644 index 00000000..9c7060a4 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_attach.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "shell script: attaching from scsi id: " ${scsi_id} +echo "shell script: attaching file type : " ${type} +echo "shell script: attaching file : " ${path} + +exit 0 diff --git a/python/proxy/raproxy_plugins/raproxy_detach.sh b/python/proxy/raproxy_plugins/raproxy_detach.sh new file mode 100644 index 00000000..79f61b22 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_detach.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "shell script: detaching from scsi id: " ${scsi_id} +echo "shell script: detaching file type : " ${type} +echo "shell script: detaching file : " ${path} + +exit 0 diff --git a/python/proxy/raproxy_plugins/raproxy_eject.sh b/python/proxy/raproxy_plugins/raproxy_eject.sh new file mode 100644 index 00000000..d0de2f87 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_eject.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "shell script: ejecting from scsi id : " ${scsi_id} +echo "shell script: ejecting file type : " ${type} +echo "shell script: ejecting file : " ${path} + +exit 0 diff --git a/python/proxy/raproxy_plugins/raproxy_init.sh b/python/proxy/raproxy_plugins/raproxy_init.sh new file mode 100644 index 00000000..8b1f57c1 --- /dev/null +++ b/python/proxy/raproxy_plugins/raproxy_init.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +scsi_id=$1 +type=$2 +path=$3 + +echo "RASCSI proxy shellscript plugin: init" + +exit 0 diff --git a/python/proxy/raproxy_plugins/shellscript.py b/python/proxy/raproxy_plugins/shellscript.py new file mode 100644 index 00000000..eefeb941 --- /dev/null +++ b/python/proxy/raproxy_plugins/shellscript.py @@ -0,0 +1,17 @@ +from yapsy.IPlugin import IPlugin +import subprocess + + +class RaProxyShellscriptPlugin(IPlugin): + def init_hook(self): + subprocess.call(['./raproxy_plugins/raproxy_init.sh']) + + def detach_hook(self, scsi_id, type, path): + subprocess.call(['./raproxy_plugins/raproxy_detach.sh',str(scsi_id),str(type),str(path)]) + + def eject_hook(self, scsi_id, type, path): + subprocess.call(['./raproxy_plugins/raproxy_eject.sh',str(scsi_id),str(type),str(path)]) + + def attach_hook(self, scsi_id, type, path): + subprocess.call(['./raproxy_plugins/raproxy_attach.sh',str(scsi_id),str(type),str(path)]) + diff --git a/python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled b/python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled new file mode 100644 index 00000000..df631576 --- /dev/null +++ b/python/proxy/raproxy_plugins/shellscript.yapsy-plugin.disabled @@ -0,0 +1,10 @@ +[Core] +Name = RASCSI Proxy shell script plugin +Module = shellscript + +[Documentation] +Author = RASCSI Contributors +Version = 0.1 +Website = https://github.com/akuker/RASCSI +Description = RASCSI Proxy Shellscript Plugin + diff --git a/python/proxy/rascsi_proxy.py b/python/proxy/rascsi_proxy.py new file mode 100644 index 00000000..677bbbb5 --- /dev/null +++ b/python/proxy/rascsi_proxy.py @@ -0,0 +1,224 @@ +#! /usr/bin/python + +import rascsi_interface_pb2 as proto +import socket +import socket_cmds +import time +from struct import pack, unpack +from yapsy.PluginManager import PluginManager + +######################### + +def send_pb_command(payload, host='localhost', port=6868): + """ + Takes a (str) containing a serialized protobuf as argument. + Establishes a socket connection with RaSCSI. + """ + + counter = 0 + tries = 20 + error_msg = "" + + import socket + while counter < tries: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect((host, port)) + return socket_cmds.send_over_socket(sock, payload) + except socket.error as error: + counter += 1 + logging.warning("The RaSCSI service is not responding - attempt %s/%s", + str(counter), str(tries)) + error_msg = str(error) + sleep(0.2) + + logging.error(error_msg) + + # After failing all attempts, throw a 404 error + abort(404, _( + u"The RaSCSI Web Interface failed to connect to RaSCSI at %(host)s:%(port)s " + u"with error: %(error_msg)s. The RaSCSI process is not running or may have crashed.", + host=host, port=port, error_msg=error_msg, + ) + ) + +######################### + +def get_image_files_info(host='localhost',port=6868): + """ + Sends a DEFAULT_IMAGE_FILES_INFO command to the server. + Returns a dict with: + - (bool) status + - (str) images_dir, path to images dir + - (list) of (str) image_files + - (int) scan_depth, the current scan depth + """ + command = proto.PbCommand() + command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO + + data = send_pb_command(command.SerializeToString(),host,port) + result = proto.PbResult() + result.ParseFromString(data) + images_dir = result.image_files_info.default_image_folder + image_files = result.image_files_info.image_files + scan_depth = result.image_files_info.depth + return { + "status": result.status, + "images_dir": images_dir, + "image_files": image_files, + "scan_depth": scan_depth, + } + +######################### + +def socket_read_n(conn, n): + """ Read exactly n bytes from the socket. + Raise RuntimeError if the connection closed before + n bytes were read. + """ + buf = bytearray(b'') + while n > 0: + data = conn.recv(n) + if data == '': + raise RuntimeError('unexpected connection close') + buf.extend(data) + n -= len(data) + return bytes(buf) + +######################### + +def deserialize_message(conn, message): + # read the header with the size of the protobuf data + buf = socket_read_n(conn, 4) + size = (int(buf[3]) << 24) + (int(buf[2]) << 16) + (int(buf[1]) << 8) + int(buf[0]) + + buf = socket_read_n(conn, size) + + message.ParseFromString(buf) + +######################### + +def serialize_message(conn, data): + conn.sendall(pack(" /dev/null ; then + echo "python3 could not be found" + echo "Run 'sudo apt install python3' to fix." + ERROR=1 +fi +if ! python3 -m venv --help &> /dev/null ; then + echo "venv could not be found" + echo "Run 'sudo apt install python3-venv' to fix." + ERROR=1 +fi +if [ $ERROR = 1 ] ; then + echo + echo "Fix errors and re-run ./start.sh" + exit 1 +fi + +# Test for two known broken venv states +if test -e venv; then + GOOD_VENV=true + ! test -e venv/bin/activate && GOOD_VENV=false + pip3 list 1> /dev/null + test $? -eq 1 && GOOD_VENV=false + if ! "$GOOD_VENV"; then + echo "Deleting bad python venv" + sudo rm -rf venv + fi +fi + +# Create the venv if it doesn't exist +if ! test -e venv; then + echo "Creating python venv for RaSCSI proxy" + python3 -m venv venv + echo "Activating venv" + source venv/bin/activate + echo "Installing requirements.txt" + pip3 install wheel + pip3 install -r requirements.txt + git rev-parse HEAD > current +fi + +source venv/bin/activate + +# Detect if someone updates the git repo - we need to re-run pip3 install. +set +e +git rev-parse --is-inside-work-tree &> /dev/null +if [[ $? -eq 0 ]]; then + set -e + if ! test -e current; then + git rev-parse > current + elif [ "$(cat current)" != "$(git rev-parse HEAD)" ]; then + echo "New version detected, updating libraries from requirements.txt" + pip3 install -r requirements.txt + git rev-parse HEAD > current + fi +else + echo "Warning: Not running from a valid git repository. Will not be able to update the code." +fi +set -e + +# parse arguments +while [ "$1" != "" ]; do + PARAM=$(echo "$1" | awk -F= '{print $1}') + VALUE=$(echo "$1" | awk -F= '{print $2}') + case $PARAM in + -p | --port) + PORT="--port $VALUE" + ;; + *) + echo "ERROR: unknown parameter \"$PARAM\"" + exit 1 + ;; + esac + shift +done + +echo "Starting RaSCSI proxy..." +python3 rascsi_proxy.py ${PORT}