RASCSI/src/web/ractl_cmds.py
Daniel Markstedt 8a3642bf9a
Move to protobuf for the webapp, major overhaul to easyinstall.sh, code comment translations (#229)
* Making saving and loading config files work with protobuf

* Formatted the Status column, and fixed the available ID logic

* Updated handling of removed status for devices without image file support

* Comment update

* Fixed typo

* Updated logging

* Updated handling of removed status for devices without image file support

* Comment update

* Fixed typo

* Updated logging

* Better handling of device status

* Updated parameter handling

* Updated setting default interfaces

* Revert "Updated setting default interfaces"

This reverts commit 210abc775d.

* Revert "Updated parameter handling"

This reverts commit 35302addd5.

* Abort with a 404 if rascsi is not running. Use any protobuf response to determine whether rascsi is running (should hardly be required anymore due to the other change, but just in case).

* Move id reservation back into __main__

* Remove check for device type when validating Removed image

* Leverage device property data for better status messages

* Remove redundant string sanitation when reading config csv file

* Clean up device list generation

* Cleanup

* Remove duplicates before building valid scsi id list

* Fully translated cfilesystem.h code comments to English; partially translated cfilesystem.cpp

* rascsi supports reserving IDs

* Updated help message

* Replaced BOOL by bool

* Logging update

* Logging update

* Cleanup

* Restructure the easyinstall.sh script to combine the install/update flows, and disallow installing the webapp by itself

* Remove redundant steps handled in Makefile

* Add the functionality to specify connect_type through a parameter

* Add validation to the argument parser allowing only STANDARD and FULLSPEC as options

* Complete translation of code comments for cfilesystem.h; partial translation for cfilesystem.cpp

* Cleanup

* Merge parts of the Network Assistant script by sonique6784; fix the run_choice startup parameter

* Improve on the network setup messages

* Fix routing address

* Add checks for previous configuration; cleanup

* Cleanup

* Remove redundant step in wired setup. Improve messages.

* Cleanup

* Added default parameters to device properties

* Return parameters a device was set up with

* Add flows for configuring custom network settings; adopting some logic by –sonique6784

* Improved device initialization

* Updated default parameter handling

* Updated default parameter handling

* Fixed typo

* Comment updates

* Comment update

* Make iso generation work again, and add error handling to urllib actions

* Manage default parameters in the respective device

* Print available network interfaces. Clean up step and improve descriptive messages.

* Make the script clean up previous configurations

* Make the script only show relevant interfaces

* Partial translation of cfilesystem.cpp

* Do not pass empty parameter string

* Added supports_params flag

* Completely translate code comments in cfilesystem.cpp

* Show rascsi-web status after installing

* Refactoring

* Made comparisons more consistent

* Updated error handling

* Updated exception handling

* Made comparisons more consistent

* Updated error handling

* Overlooked code comment translation

* Renaming

* Better error handling for socket connection

* Disable two NEC hd image types due to issue#232

* Comment update

* NEC sectors size must be 512 bytes

* Updated logging

* Updated vendor name handling

* Updated handling of media loading/unloading

* Comment update

* NEC sectors size must be 512 bytes

* Updated logging

* Updated vendor name handling

* Updated handling of media loading/unloading

* Better handling of removable disks in the web ui

* Added stoppable property and stopped status

* Made MO stoppable

* Removed duplicate code

* Removed duplicate code

* Copy read-only property

* Renaming

* Add an assistant for reserving scsi ids

* Don't show action if no device attached

* Implement a device_info app path, and cut down on device columns always shown

* Cleanup

* Removed duplicate code, added START/STOP

* Improved default parameter handling

* Updated load/eject handling

* Logging update

* Fixed typo

* Verified START/STOP UNIT

* Updated logging

* Updated status handling

* Updated status handling

* More status handling updates

* Logging update

* Made instance fields local variables

* Removed duplicate code, added START/STOP

* Improved default parameter handling

* Updated load/eject handling

* Logging update

* Fixed typo

* Verified START/STOP UNIT

* Updated logging

* Updated status handling

* Updated status handling

* More status handling updates

* Logging update

* Made instance fields local variables

* Made disk_t private

* Made some data structures private

* Fixed ARM compile issue

* Fast forward instead of rebase existing git repo

* Fixed ctapdriver initialization issue

* Reset read-only status when opening an image file

* Cleanup

* Cleanup

* Made logging more consistent

* Updated log level

* Cleanup

* Log load/eject on error level for testing

* Revert "Log load/eject on error level for testing"

This reverts commit d35a15ea8e.

* Assume drive is not ready after having been stopped

* Updated status handling

* Make the csv config files store all relevant device data for reading

* Read 9 column csv config files

* Fixed typo

* Rebuild manpage

* Fixed issue #234 (MODE SENSE (10) returns wrong mode parameter header)

* Removed unused code

* Enum data type update

* Removed duplicate range check

* Removed duplicate code

* Removed more duplicate code

* Logging update

* SCCD sector size was not meant to be configurable

* Better error handling for csv reading and writing

* Updated configurable sector size properties

* Removed assertion

* Improved error handling

* Updated error handling

* Re-added special error handling only relevant for SASI

* Added TODOs

* Comment update

* Added override modifier

* Removed obsolete debug flag (related code was not called)

* Comment and logging updates

* Removed obsolete try/catch

* Revert "Removed obsolete try/catch"

This reverts commit 39ca12d8b1.

* Comment update

* Removed duplicate code

* Updated error messages, use more foreach loops

* Avoid storing RaSCSI generated product info in config file

* Updated logging

* Logging update

* Save config files in json instead of csv

* Fix bugs with json config loading

* Refactoring & remove unused code

* Refactoring

* Display upper case file endings in file management list

* Only show product vendor for non-RaSCSI devices in the device list

* Translate code comment

* Refactoring

* Fix bad identation

* Improve valid file extension handling

* Add validation when attaching removable media

* Display valid file endings under the file list

* Cleanup

* Don't store 0 block size

* Fix indentation

* Read and write config files in key:pair format

* Add section for controlling logging

* README update

* Added block_count

* Cleanup, fix typos

* Support attaching CD-ROM with custom block size

* Evaluate block size when inserting a media

* rasctl display capacity if available

* Info message update

* Use kwargs for device attachment

* Fix bugs in attach_image kwargs; make config file more readable

* POC for attaching device with profile

* Only list product types valid for the particular image file

* Perform validation of HDD image size based on the product profile

* Implement sidecar config files for drive images.

* Added missing product name to NEC vital product data

* MO block size depends on capacity only

* Better error handling for device sidecar config loading

* Extended property/status display

* Property display update

* Updated error handling

* Handle image sizes in bytes internally

* Revert change

* Resolve bad merge

Co-authored-by: Uwe Seimet <Uwe.Seimet@seimet.de>
2021-09-14 21:51:12 -05:00

329 lines
12 KiB
Python

import logging
from settings import *
import rascsi_interface_pb2 as proto
def get_server_info():
command = proto.PbCommand()
command.operation = proto.PbOperation.SERVER_INFO
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
version = str(result.server_info.major_version) + "." +\
str(result.server_info.minor_version) + "." +\
str(result.server_info.patch_version)
log_levels = result.server_info.log_levels
current_log_level = result.server_info.current_log_level
return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level}
def validate_scsi_id(scsi_id):
from re import match
if match("[0-7]", str(scsi_id)) != None:
return {"status": True, "msg": "Valid SCSI ID."}
else:
return {"status": False, "msg": "Invalid SCSI ID. Should be a number between 0-7"}
def get_valid_scsi_ids(devices, invalid_list, occupied_ids):
for device in devices:
# Make it possible to insert images on top of a
# removable media device currently without an image attached
if "No Media" in device["status"]:
occupied_ids.remove(device["id"])
# Combine lists and remove duplicates
invalid_ids = list(set(invalid_list + occupied_ids))
valid_ids = list(range(8))
for id in invalid_ids:
valid_ids.remove(int(id))
valid_ids.reverse()
return valid_ids
def get_type(scsi_id):
device = proto.PbDeviceDefinition()
device.id = int(scsi_id)
command = proto.PbCommand()
command.operation = proto.PbOperation.DEVICE_INFO
command.devices.append(device)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
# Assuming that only one PbDevice object is present in the response
try:
result_type = proto.PbDeviceType.Name(result.device_info.devices[0].type)
return {"status": result.status, "msg": result.msg, "device_type": result_type}
except:
return {"status": result.status, "msg": result.msg, "device_type": None}
def attach_image(scsi_id, **kwargs):
# Handling the inserting of media into an attached removable type device
currently_attached = get_type(scsi_id)["device_type"]
device_type = kwargs.get("device_type", None)
if device_type in REMOVABLE_DEVICE_TYPES and currently_attached in REMOVABLE_DEVICE_TYPES:
if currently_attached != device_type:
return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."}
else:
return insert(scsi_id, kwargs.get("image", ""))
# Handling attaching a new device
else:
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
if "device_type" in kwargs.keys():
logging.warning(kwargs["device_type"])
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
if "unit" in kwargs.keys():
devices.unit = kwargs["unit"]
if "image" in kwargs.keys():
if kwargs["image"] not in [None, ""]:
devices.params.append(kwargs["image"])
if "params" in kwargs.keys():
for p in kwargs["params"]:
devices.params.append(p)
if "vendor" in kwargs.keys():
if kwargs["vendor"] not in [None, ""]:
devices.vendor = kwargs["vendor"]
if "product" in kwargs.keys():
if kwargs["product"] not in [None, ""]:
devices.product = kwargs["product"]
if "revision" in kwargs.keys():
if kwargs["revision"] not in [None, ""]:
devices.revision = kwargs["revision"]
if "block_size" in kwargs.keys():
if kwargs["block_size"] not in [None, ""]:
devices.block_size = int(kwargs["block_size"])
command = proto.PbCommand()
command.operation = proto.PbOperation.ATTACH
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def detach_by_id(scsi_id):
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
command = proto.PbCommand()
command.operation = proto.PbOperation.DETACH
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def detach_all():
command = proto.PbCommand()
command.operation = proto.PbOperation.DETACH_ALL
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def eject_by_id(scsi_id):
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
command = proto.PbCommand()
command.operation = proto.PbOperation.EJECT
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def insert(scsi_id, image):
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
devices.params.append(image)
command = proto.PbCommand()
command.operation = proto.PbOperation.INSERT
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def attach_daynaport(scsi_id):
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
devices.type = proto.PbDeviceType.SCDP
command = proto.PbCommand()
command.operation = proto.PbOperation.ATTACH
command.devices.append(devices)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def list_devices(scsi_id=None):
from os import path
command = proto.PbCommand()
command.operation = proto.PbOperation.DEVICE_INFO
# If method is called with scsi_id parameter, return the info on those devices
# Otherwise, return the info on all attached devices
if scsi_id != None:
device = proto.PbDeviceDefinition()
device.id = int(scsi_id)
command.devices.append(device)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
device_list = []
occupied_ids = []
n = 0
while n < len(result.device_info.devices):
did = result.device_info.devices[n].id
dun = result.device_info.devices[n].unit
dtype = proto.PbDeviceType.Name(result.device_info.devices[n].type)
dstat = result.device_info.devices[n].status
dprop = result.device_info.devices[n].properties
# Building the status string
# TODO: This formatting should probably be moved elsewhere
dstat_msg = []
if dprop.read_only == True:
dstat_msg.append("Read-Only")
if dstat.protected == True and dprop.protectable == True:
dstat_msg.append("Write-Protected")
if dstat.removed == True and dprop.removable == True:
dstat_msg.append("No Media")
if dstat.locked == True and dprop.lockable == True:
dstat_msg.append("Locked")
dpath = result.device_info.devices[n].file.name
dfile = path.basename(dpath)
dparam = result.device_info.devices[n].params
dven = result.device_info.devices[n].vendor
dprod = result.device_info.devices[n].product
drev = result.device_info.devices[n].revision
dblock = result.device_info.devices[n].block_size
device_list.append({"id": did, "un": dun, "device_type": dtype, \
"status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\
"vendor": dven, "product": dprod, "revision": drev, "block_size": dblock})
occupied_ids.append(did)
n += 1
return device_list, occupied_ids
def sort_and_format_devices(device_list, occupied_ids):
# Add padding devices and sort the list
for id in range(8):
if id not in occupied_ids:
device_list.append({"id": id, "type": "-", \
"status": "-", "file": "-", "product": "-"})
# Sort list of devices by id
device_list.sort(key=lambda dic: str(dic["id"]))
return device_list
def reserve_scsi_ids(reserved_scsi_ids):
command = proto.PbCommand()
command.operation = proto.PbOperation.RESERVE
command.params.append(reserved_scsi_ids)
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def set_log_level(log_level):
'''Sends a command to the server to change the log level. Takes target log level as an argument'''
command = proto.PbCommand()
command.operation = proto.PbOperation.LOG_LEVEL
command.params.append(str(log_level))
data = send_pb_command(command.SerializeToString())
result = proto.PbResult()
result.ParseFromString(data)
return {"status": result.status, "msg": result.msg}
def send_pb_command(payload):
# Host and port number where rascsi is listening for socket connections
HOST = 'localhost'
PORT = 6868
counter = 0
tries = 100
error_msg = ""
import socket
while counter < tries:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
return send_over_socket(s, payload)
except socket.error as error:
counter += 1
logging.warning("The RaSCSI service is not responding - attempt " + \
str(counter) + "/" + str(tries))
error_msg = str(error)
logging.error(error_msg)
# After failing all attempts, throw a 404 error
from flask import abort
abort(404, "Failed to connect to RaSCSI at " + str(HOST) + ":" + str(PORT) + \
" with error: " + error_msg + ". Is the RaSCSI service running?")
def send_over_socket(s, payload):
from struct import pack, unpack
# Prepending a little endian 32bit header with the message size
s.send(pack("<i", len(payload)))
s.send(payload)
# Receive the first 4 bytes to get the response header
response = s.recv(4)
if len(response) >= 4:
# Extracting the response header to get the length of the response message
response_length = unpack("<i", response)[0]
# Reading in chunks, to handle a case where the response message is very large
chunks = []
bytes_recvd = 0
while bytes_recvd < response_length:
chunk = s.recv(min(response_length - bytes_recvd, 2048))
if chunk == b'':
from flask import abort
logging.error("Read an empty chunk from the socket. Socket connection may have dropped unexpectedly.")
abort(503, "Lost connection to RaSCSI. Please go back and try again. If the issue persists, please report a bug.")
chunks.append(chunk)
bytes_recvd = bytes_recvd + len(chunk)
response_message = b''.join(chunks)
return response_message
else:
from flask import abort
logging.error("The response from RaSCSI did not contain a protobuf header.")
abort(500, "Did not get a valid response from RaSCSI. Please go back and try again. If the issue persists, please report a bug.")