2022-01-21 23:08:29 +00:00
|
|
|
"""
|
2022-12-05 17:58:23 +00:00
|
|
|
Module for commands sent to the PiSCSI backend service.
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
|
|
|
|
2023-10-31 21:54:04 +00:00
|
|
|
import logging
|
|
|
|
from pathlib import PurePath, Path
|
|
|
|
from functools import lru_cache
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
import piscsi_interface_pb2 as proto
|
|
|
|
from piscsi.return_codes import ReturnCodes
|
|
|
|
from piscsi.socket_cmds import SocketCmds
|
2023-10-31 21:54:04 +00:00
|
|
|
|
|
|
|
from piscsi.common_settings import (
|
|
|
|
CFG_DIR,
|
|
|
|
PROPERTIES_SUFFIX,
|
|
|
|
ARCHIVE_FILE_SUFFIXES,
|
|
|
|
)
|
|
|
|
|
|
|
|
from util import unarchiver
|
2022-01-21 23:08:29 +00:00
|
|
|
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
class PiscsiCmds:
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
2022-12-05 17:58:23 +00:00
|
|
|
Class for commands sent to the PiSCSI backend service.
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
2022-11-30 05:19:17 +00:00
|
|
|
|
2022-01-21 23:08:29 +00:00
|
|
|
def __init__(self, sock_cmd: SocketCmds, token=None, locale="en"):
|
|
|
|
self.sock_cmd = sock_cmd
|
|
|
|
self.token = token
|
|
|
|
self.locale = locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
def send_pb_command(self, command):
|
|
|
|
if logging.getLogger().isEnabledFor(logging.DEBUG):
|
|
|
|
logging.debug(self.format_pb_command(command))
|
|
|
|
|
|
|
|
return self.sock_cmd.send_pb_command(command.SerializeToString())
|
|
|
|
|
2023-10-31 21:54:04 +00:00
|
|
|
def list_images(self):
|
|
|
|
"""
|
|
|
|
Sends a IMAGE_FILES_INFO command to the server
|
|
|
|
Returns a (dict) with (bool) status, (str) msg, and (list) of (dict)s files
|
|
|
|
"""
|
|
|
|
from piscsi.file_cmds import FileCmds
|
|
|
|
|
|
|
|
self.file_cmd = FileCmds(piscsi=self)
|
|
|
|
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
|
|
|
data = self.send_pb_command(command)
|
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
|
|
|
|
server_info = self.get_server_info()
|
|
|
|
files = []
|
|
|
|
for file in result.image_files_info.image_files:
|
|
|
|
prop_file_path = Path(CFG_DIR) / f"{file.name}.{PROPERTIES_SUFFIX}"
|
|
|
|
# Add properties meta data for the image, if matching prop file is found
|
|
|
|
if prop_file_path.exists():
|
|
|
|
process = self.file_cmd.read_drive_properties(prop_file_path)
|
|
|
|
prop = process["conf"]
|
|
|
|
else:
|
|
|
|
prop = False
|
|
|
|
|
|
|
|
archive_contents = []
|
|
|
|
if PurePath(file.name).suffix.lower()[1:] in ARCHIVE_FILE_SUFFIXES:
|
|
|
|
try:
|
|
|
|
archive_info = self._get_archive_info(
|
|
|
|
f"{server_info['image_dir']}/{file.name}",
|
|
|
|
_cache_extra_key=file.size,
|
|
|
|
)
|
|
|
|
|
|
|
|
properties_files = [
|
|
|
|
x["path"]
|
|
|
|
for x in archive_info["members"]
|
|
|
|
if x["path"].endswith(PROPERTIES_SUFFIX)
|
|
|
|
]
|
|
|
|
|
|
|
|
for member in archive_info["members"]:
|
|
|
|
if member["is_dir"] or member["is_resource_fork"]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if PurePath(member["path"]).suffix.lower()[1:] == PROPERTIES_SUFFIX:
|
|
|
|
member["is_properties_file"] = True
|
|
|
|
elif f"{member['path']}.{PROPERTIES_SUFFIX}" in properties_files:
|
2024-03-22 07:19:13 +00:00
|
|
|
member["related_properties_file"] = (
|
|
|
|
f"{member['path']}.{PROPERTIES_SUFFIX}"
|
|
|
|
)
|
2023-10-31 21:54:04 +00:00
|
|
|
|
|
|
|
archive_contents.append(member)
|
|
|
|
except (unarchiver.LsarCommandError, unarchiver.LsarOutputError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
size_mb = "{:,.1f}".format(file.size / 1024 / 1024)
|
|
|
|
dtype = proto.PbDeviceType.Name(file.type)
|
|
|
|
files.append(
|
|
|
|
{
|
|
|
|
"name": file.name,
|
|
|
|
"size": file.size,
|
|
|
|
"size_mb": size_mb,
|
|
|
|
"detected_type": dtype,
|
|
|
|
"prop": prop,
|
|
|
|
"archive_contents": archive_contents,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return {"status": result.status, "msg": result.msg, "files": files}
|
|
|
|
|
2022-01-21 23:08:29 +00:00
|
|
|
def get_server_info(self):
|
|
|
|
"""
|
|
|
|
Sends a SERVER_INFO command to the server.
|
|
|
|
Returns a dict with:
|
|
|
|
- (bool) status
|
2022-12-05 17:58:23 +00:00
|
|
|
- (str) version (PiSCSI version number)
|
|
|
|
- (list) of (str) log_levels (the log levels PiSCSI supports)
|
2022-01-21 23:08:29 +00:00
|
|
|
- (str) current_log_level
|
|
|
|
- (list) of (int) reserved_ids
|
|
|
|
- (str) image_dir, path to the default images directory
|
|
|
|
- (int) scan_depth, the current images directory scan depth
|
2022-12-05 17:58:23 +00:00
|
|
|
- 5 distinct (list)s of (str)s with file endings recognized by PiSCSI
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.SERVER_INFO
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
2022-11-30 05:19:17 +00:00
|
|
|
version = (
|
|
|
|
str(result.server_info.version_info.major_version)
|
|
|
|
+ "."
|
|
|
|
+ str(result.server_info.version_info.minor_version)
|
|
|
|
+ "."
|
|
|
|
+ str(result.server_info.version_info.patch_version)
|
|
|
|
)
|
2022-09-10 01:58:35 +00:00
|
|
|
log_levels = list(result.server_info.log_level_info.log_levels)
|
2022-01-21 23:08:29 +00:00
|
|
|
current_log_level = result.server_info.log_level_info.current_log_level
|
|
|
|
reserved_ids = list(result.server_info.reserved_ids_info.ids)
|
|
|
|
image_dir = result.server_info.image_files_info.default_image_folder
|
|
|
|
scan_depth = result.server_info.image_files_info.depth
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
# Creates lists of file endings recognized by PiSCSI
|
2022-01-21 23:08:29 +00:00
|
|
|
mappings = result.server_info.mapping_info.mapping
|
|
|
|
schd = []
|
|
|
|
scrm = []
|
|
|
|
scmo = []
|
|
|
|
sccd = []
|
|
|
|
for dtype in mappings:
|
SASI code removal, error handling update, bug fixes, code cleanup (#806)
Summary ov most important changes triggered by the SASI code removal:
- Removed the SASI controller code
- New controller management. There is a new controller base class AbstractController and a class ControllerManager managing the controller lifecycle. The lifecycle management was removed from rasci.cpp and is covered by unit tests.
- New device management. The DeviceFactory manages the device lifecycle instead of rascsi.cpp. The new code is covered by unit tests.
- The lifecycle managment uses C++ collections with variable size instead of arrays with hard-coded sizes.
- The ScsiController method contains most of what was previously contained in scsidev_ctrl.cpp plus the code from sasidev_ctrl.cpp that was relevant for SCSI.
- scsi_command_util contains helper methods used for identical SCSI command implementations of more than one device
- Devices know their controllers, so that the controller instance does not need to be passed to each SCSI command. This change helps to decouple the devices from the controller. The phase_handler interface is also part of this decoupling.
- Use scsi_command_exception for propagating SCSI command execution errors, This resolves issues with the previous error handling, which was based on return values and often on magic numbers.
- Removed legacy SCSI error codes, all errors are now encoded by sense_key::, asc:: and status::.
- Fixed various warnings reported with -Wextra, -Weffc++ and -Wpedantic.
- Use constructor member initialization lists (recommended for ISO C++)
- Consistently use new/delete instead of malloc/free (recommended for ISO C++), resulting in better type safety and error handling
- Replaced variable sized arrays on the stack (violates ISO C++ and can cause a stack overflow)
- Replaced NULL by nullptr (recommended for C++), resulting in better type safety
- Use more const member functions in order to avoid side effects
- The format device page can now also be changed for hard disk drives (Fujitsu M2624S supports this, for instance), not just for MOs.
- Better encapsulation, updated access specifiers in many places
- Removed unused methods and method arguments
- Fixed a number of TODOs
- Added/updated unit tests for a lot of non-legacy classes
- Makefile support for creating HTML coverage reports with lcov/genhtml
2022-09-03 14:53:53 +00:00
|
|
|
if mappings[dtype] == proto.PbDeviceType.SCHD:
|
2022-01-21 23:08:29 +00:00
|
|
|
schd.append(dtype)
|
|
|
|
elif mappings[dtype] == proto.PbDeviceType.SCRM:
|
|
|
|
scrm.append(dtype)
|
|
|
|
elif mappings[dtype] == proto.PbDeviceType.SCMO:
|
|
|
|
scmo.append(dtype)
|
|
|
|
elif mappings[dtype] == proto.PbDeviceType.SCCD:
|
|
|
|
sccd.append(dtype)
|
|
|
|
|
|
|
|
return {
|
|
|
|
"status": result.status,
|
|
|
|
"version": version,
|
|
|
|
"log_levels": log_levels,
|
|
|
|
"current_log_level": current_log_level,
|
|
|
|
"reserved_ids": reserved_ids,
|
|
|
|
"image_dir": image_dir,
|
|
|
|
"scan_depth": scan_depth,
|
|
|
|
"schd": schd,
|
|
|
|
"scrm": scrm,
|
|
|
|
"scmo": scmo,
|
|
|
|
"sccd": sccd,
|
2022-11-30 05:19:17 +00:00
|
|
|
}
|
2022-01-21 23:08:29 +00:00
|
|
|
|
|
|
|
def get_reserved_ids(self):
|
|
|
|
"""
|
|
|
|
Sends a RESERVED_IDS_INFO command to the server.
|
|
|
|
Returns a dict with:
|
|
|
|
- (bool) status
|
|
|
|
- (list) of (int) ids -- currently reserved SCSI IDs
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.RESERVED_IDS_INFO
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
scsi_ids = []
|
|
|
|
for scsi_id in result.reserved_ids_info.ids:
|
|
|
|
scsi_ids.append(str(scsi_id))
|
|
|
|
|
|
|
|
return {"status": result.status, "ids": scsi_ids}
|
|
|
|
|
|
|
|
def get_network_info(self):
|
|
|
|
"""
|
|
|
|
Sends a NETWORK_INTERFACES_INFO command to the server.
|
|
|
|
Returns a dict with:
|
|
|
|
- (bool) status
|
2022-12-05 17:58:23 +00:00
|
|
|
- (list) of (str) ifs (network interfaces detected by PiSCSI)
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.NETWORK_INTERFACES_INFO
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
ifs = result.network_interfaces_info.name
|
2022-09-10 01:58:35 +00:00
|
|
|
return {"status": result.status, "ifs": list(ifs)}
|
2022-01-21 23:08:29 +00:00
|
|
|
|
|
|
|
def get_device_types(self):
|
|
|
|
"""
|
|
|
|
Sends a DEVICE_TYPES_INFO command to the server.
|
|
|
|
Returns a dict with:
|
|
|
|
- (bool) status
|
2022-02-19 08:04:14 +00:00
|
|
|
- (dict) device_types, where keys are the four letter device type acronym,
|
|
|
|
and the value is a (dict) of supported parameters and their default values.
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.DEVICE_TYPES_INFO
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
2022-02-19 08:04:14 +00:00
|
|
|
device_types = {}
|
|
|
|
for device in result.device_types_info.properties:
|
|
|
|
params = {}
|
|
|
|
for key, value in device.properties.default_params.items():
|
|
|
|
params[key] = value
|
2022-02-21 17:27:31 +00:00
|
|
|
device_types[proto.PbDeviceType.Name(device.type)] = {
|
2022-11-30 05:19:17 +00:00
|
|
|
"removable": device.properties.removable,
|
|
|
|
"supports_file": device.properties.supports_file,
|
|
|
|
"params": params,
|
|
|
|
"block_sizes": list(device.properties.block_sizes),
|
|
|
|
}
|
2022-01-21 23:08:29 +00:00
|
|
|
return {"status": result.status, "device_types": device_types}
|
|
|
|
|
2022-02-21 17:27:31 +00:00
|
|
|
def get_removable_device_types(self):
|
|
|
|
"""
|
|
|
|
Returns a (list) of (str) of four letter device acronyms
|
|
|
|
that are of the removable type.
|
|
|
|
"""
|
|
|
|
device_types = self.get_device_types()
|
|
|
|
removable_device_types = []
|
|
|
|
for device, value in device_types["device_types"].items():
|
|
|
|
if value["removable"]:
|
|
|
|
removable_device_types.append(device)
|
|
|
|
return removable_device_types
|
|
|
|
|
|
|
|
def get_disk_device_types(self):
|
|
|
|
"""
|
2022-02-27 05:46:35 +00:00
|
|
|
Returns a (list) of (str) of four letter device acronyms
|
2022-02-21 17:27:31 +00:00
|
|
|
that take image files as arguments.
|
|
|
|
"""
|
|
|
|
device_types = self.get_device_types()
|
|
|
|
disk_device_types = []
|
|
|
|
for device, value in device_types["device_types"].items():
|
|
|
|
if value["supports_file"]:
|
|
|
|
disk_device_types.append(device)
|
|
|
|
return disk_device_types
|
|
|
|
|
|
|
|
def get_peripheral_device_types(self):
|
|
|
|
"""
|
|
|
|
Returns a (list) of (str) of four letter device acronyms
|
|
|
|
that don't take image files as arguments.
|
|
|
|
"""
|
|
|
|
device_types = self.get_device_types()
|
|
|
|
image_device_types = self.get_disk_device_types()
|
|
|
|
peripheral_device_types = [
|
2022-11-30 05:19:17 +00:00
|
|
|
x for x in device_types["device_types"] if x not in image_device_types
|
|
|
|
]
|
2022-02-21 17:27:31 +00:00
|
|
|
return peripheral_device_types
|
|
|
|
|
2022-01-21 23:08:29 +00:00
|
|
|
def get_image_files_info(self):
|
|
|
|
"""
|
|
|
|
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
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.token
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
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,
|
2022-11-30 05:19:17 +00:00
|
|
|
}
|
2022-01-21 23:08:29 +00:00
|
|
|
|
2022-02-19 08:04:14 +00:00
|
|
|
def attach_device(self, scsi_id, **kwargs):
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
|
|
|
Takes (int) scsi_id and kwargs containing 0 or more device properties
|
|
|
|
|
|
|
|
If the current attached device is a removable device wihout media inserted,
|
|
|
|
this sends a INJECT command to the server.
|
|
|
|
If there is no currently attached device, this sends the ATTACH command to the server.
|
|
|
|
|
|
|
|
Returns (bool) status and (str) msg
|
|
|
|
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
devices = proto.PbDeviceDefinition()
|
|
|
|
devices.id = int(scsi_id)
|
|
|
|
|
2022-10-09 20:50:20 +00:00
|
|
|
if kwargs.get("device_type"):
|
|
|
|
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
|
|
|
|
if kwargs.get("unit"):
|
|
|
|
devices.unit = kwargs["unit"]
|
|
|
|
if kwargs.get("params") and isinstance(kwargs["params"], dict):
|
|
|
|
for param in kwargs["params"]:
|
|
|
|
devices.params[param] = kwargs["params"][param]
|
2022-01-21 23:08:29 +00:00
|
|
|
|
|
|
|
# Handling the inserting of media into an attached removable type device
|
|
|
|
device_type = kwargs.get("device_type", None)
|
|
|
|
currently_attached = self.list_devices(scsi_id, kwargs.get("unit"))["device_list"]
|
|
|
|
if currently_attached:
|
|
|
|
current_type = currently_attached[0]["device_type"]
|
|
|
|
else:
|
|
|
|
current_type = None
|
|
|
|
|
2022-02-21 17:27:31 +00:00
|
|
|
removable_device_types = self.get_removable_device_types()
|
|
|
|
if device_type in removable_device_types and current_type in removable_device_types:
|
2022-01-21 23:08:29 +00:00
|
|
|
if current_type != device_type:
|
|
|
|
parameters = {
|
|
|
|
"device_type": device_type,
|
2022-11-30 05:19:17 +00:00
|
|
|
"current_device_type": current_type,
|
2022-01-21 23:08:29 +00:00
|
|
|
}
|
|
|
|
return {
|
|
|
|
"status": False,
|
|
|
|
"return_code": ReturnCodes.ATTACHIMAGE_COULD_NOT_ATTACH,
|
|
|
|
"parameters": parameters,
|
2022-11-30 05:19:17 +00:00
|
|
|
}
|
2022-01-21 23:08:29 +00:00
|
|
|
command.operation = proto.PbOperation.INSERT
|
2022-02-19 08:04:14 +00:00
|
|
|
|
2022-01-21 23:08:29 +00:00
|
|
|
# Handling attaching a new device
|
|
|
|
else:
|
|
|
|
command.operation = proto.PbOperation.ATTACH
|
2022-10-09 20:50:20 +00:00
|
|
|
if kwargs.get("vendor"):
|
|
|
|
devices.vendor = kwargs["vendor"]
|
|
|
|
if kwargs.get("product"):
|
|
|
|
devices.product = kwargs["product"]
|
|
|
|
if kwargs.get("revision"):
|
|
|
|
devices.revision = kwargs["revision"]
|
|
|
|
if kwargs.get("block_size"):
|
|
|
|
devices.block_size = int(kwargs["block_size"])
|
2022-01-21 23:08:29 +00:00
|
|
|
|
|
|
|
command.devices.append(devices)
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
|
|
|
def detach_by_id(self, scsi_id, unit=None):
|
|
|
|
"""
|
|
|
|
Takes (int) scsi_id and optional (int) unit.
|
|
|
|
Sends a DETACH command to the server.
|
|
|
|
Returns (bool) status and (str) msg.
|
|
|
|
"""
|
|
|
|
devices = proto.PbDeviceDefinition()
|
|
|
|
devices.id = int(scsi_id)
|
|
|
|
if unit is not None:
|
|
|
|
devices.unit = int(unit)
|
|
|
|
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.DETACH
|
|
|
|
command.devices.append(devices)
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
|
|
|
def detach_all(self):
|
|
|
|
"""
|
|
|
|
Sends a DETACH_ALL command to the server.
|
|
|
|
Returns (bool) status and (str) msg.
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.DETACH_ALL
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
|
|
|
def eject_by_id(self, scsi_id, unit=None):
|
|
|
|
"""
|
|
|
|
Takes (int) scsi_id and optional (int) unit.
|
|
|
|
Sends an EJECT command to the server.
|
|
|
|
Returns (bool) status and (str) msg.
|
|
|
|
"""
|
|
|
|
devices = proto.PbDeviceDefinition()
|
|
|
|
devices.id = int(scsi_id)
|
|
|
|
if unit is not None:
|
|
|
|
devices.unit = int(unit)
|
|
|
|
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.EJECT
|
|
|
|
command.devices.append(devices)
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
|
|
|
def list_devices(self, scsi_id=None, unit=None):
|
|
|
|
"""
|
|
|
|
Takes optional (int) scsi_id and optional (int) unit.
|
|
|
|
Sends a DEVICES_INFO command to the server.
|
|
|
|
If no scsi_id is provided, returns a (list) of (dict)s of all attached devices.
|
|
|
|
If scsi_id is is provided, returns a (list) of one (dict) for the given device.
|
|
|
|
If no attached device is found, returns an empty (list).
|
|
|
|
Returns (bool) status, (list) of dicts device_list
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.DEVICES_INFO
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
|
|
|
# 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 is not None:
|
|
|
|
device = proto.PbDeviceDefinition()
|
|
|
|
device.id = int(scsi_id)
|
|
|
|
if unit is not None:
|
|
|
|
device.unit = int(unit)
|
|
|
|
command.devices.append(device)
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
|
|
|
|
device_list = []
|
|
|
|
|
|
|
|
# Return an empty (list) if no devices are attached
|
|
|
|
if not result.devices_info.devices:
|
|
|
|
return {"status": False, "device_list": []}
|
|
|
|
|
|
|
|
image_files_info = self.get_image_files_info()
|
|
|
|
i = 0
|
|
|
|
while i < len(result.devices_info.devices):
|
|
|
|
did = result.devices_info.devices[i].id
|
|
|
|
dunit = result.devices_info.devices[i].unit
|
|
|
|
dtype = proto.PbDeviceType.Name(result.devices_info.devices[i].type)
|
|
|
|
dstat = result.devices_info.devices[i].status
|
|
|
|
dprop = result.devices_info.devices[i].properties
|
|
|
|
|
|
|
|
# Building the status string
|
|
|
|
dstat_msg = []
|
|
|
|
if dprop.read_only:
|
|
|
|
dstat_msg.append("Read-Only")
|
|
|
|
if dstat.protected and dprop.protectable:
|
|
|
|
dstat_msg.append("Write-Protected")
|
|
|
|
if dstat.removed and dprop.removable:
|
|
|
|
dstat_msg.append("No Media")
|
|
|
|
if dstat.locked and dprop.lockable:
|
|
|
|
dstat_msg.append("Locked")
|
|
|
|
|
|
|
|
dpath = result.devices_info.devices[i].file.name
|
|
|
|
dfile = dpath.replace(image_files_info["images_dir"] + "/", "")
|
2022-09-10 01:58:35 +00:00
|
|
|
dparam = dict(result.devices_info.devices[i].params)
|
2022-01-21 23:08:29 +00:00
|
|
|
dven = result.devices_info.devices[i].vendor
|
|
|
|
dprod = result.devices_info.devices[i].product
|
|
|
|
drev = result.devices_info.devices[i].revision
|
|
|
|
dblock = result.devices_info.devices[i].block_size
|
|
|
|
dsize = int(result.devices_info.devices[i].block_count) * int(dblock)
|
|
|
|
|
2022-11-30 05:19:17 +00:00
|
|
|
device_list.append(
|
|
|
|
{
|
|
|
|
"id": did,
|
|
|
|
"unit": dunit,
|
|
|
|
"device_type": dtype,
|
|
|
|
"status": ", ".join(dstat_msg),
|
|
|
|
"image": dpath,
|
|
|
|
"file": dfile,
|
|
|
|
"params": dparam,
|
|
|
|
"vendor": dven,
|
|
|
|
"product": dprod,
|
|
|
|
"revision": drev,
|
|
|
|
"block_size": dblock,
|
|
|
|
"size": dsize,
|
|
|
|
}
|
|
|
|
)
|
2022-01-21 23:08:29 +00:00
|
|
|
i += 1
|
|
|
|
|
|
|
|
return {"status": result.status, "msg": result.msg, "device_list": device_list}
|
|
|
|
|
|
|
|
def reserve_scsi_ids(self, reserved_scsi_ids):
|
|
|
|
"""
|
|
|
|
Sends the RESERVE_IDS command to the server to reserve SCSI IDs.
|
|
|
|
Takes a (list) of (str) as argument.
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.RESERVE_IDS
|
|
|
|
command.params["ids"] = ",".join(reserved_scsi_ids)
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
|
|
|
def set_log_level(self, log_level):
|
|
|
|
"""
|
|
|
|
Sends a LOG_LEVEL command to the server.
|
|
|
|
Takes (str) log_level as an argument.
|
|
|
|
Returns (bool) status and (str) msg.
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.LOG_LEVEL
|
|
|
|
command.params["level"] = str(log_level)
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
2022-11-18 00:21:18 +00:00
|
|
|
def shutdown(self, mode):
|
2022-01-21 23:08:29 +00:00
|
|
|
"""
|
|
|
|
Sends a SHUT_DOWN command to the server.
|
|
|
|
Takes (str) mode as an argument.
|
2022-11-18 00:21:18 +00:00
|
|
|
The backend will use system calls to reboot or shut down the system.
|
|
|
|
It can also shut down the backend process itself.
|
2022-01-21 23:08:29 +00:00
|
|
|
Returns (bool) status and (str) msg.
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.SHUT_DOWN
|
|
|
|
command.params["mode"] = str(mode)
|
|
|
|
command.params["token"] = self.token
|
|
|
|
command.params["locale"] = self.locale
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
|
|
|
|
|
|
|
def is_token_auth(self):
|
|
|
|
"""
|
|
|
|
Sends a CHECK_AUTHENTICATION command to the server.
|
2022-12-05 17:58:23 +00:00
|
|
|
Tells you whether PiSCSI backend is protected by a token password or not.
|
2022-01-21 23:08:29 +00:00
|
|
|
Returns (bool) status and (str) msg.
|
|
|
|
"""
|
|
|
|
command = proto.PbCommand()
|
|
|
|
command.operation = proto.PbOperation.CHECK_AUTHENTICATION
|
|
|
|
|
2022-12-04 14:31:57 +00:00
|
|
|
data = self.send_pb_command(command)
|
2022-01-21 23:08:29 +00:00
|
|
|
result = proto.PbResult()
|
|
|
|
result.ParseFromString(data)
|
|
|
|
return {"status": result.status, "msg": result.msg}
|
2022-12-04 14:31:57 +00:00
|
|
|
|
|
|
|
def format_pb_command(self, command):
|
|
|
|
"""
|
|
|
|
Formats the Protobuf command for output
|
|
|
|
"""
|
|
|
|
message = f"Sending: {proto.PbOperation.Name(command.operation)}"
|
|
|
|
|
|
|
|
params = {
|
|
|
|
name: "***" if name == "token" else value
|
|
|
|
for (name, value) in sorted(command.params.items())
|
|
|
|
}
|
|
|
|
message += f", params: {params}"
|
|
|
|
|
|
|
|
for device in command.devices:
|
|
|
|
formatted_device = {
|
|
|
|
key: value
|
|
|
|
for (key, value) in {
|
|
|
|
"id": device.id,
|
|
|
|
"unit": device.unit,
|
|
|
|
"type": proto.PbDeviceType.Name(device.type) if device.type else None,
|
|
|
|
"params": device.params,
|
|
|
|
"vendor": device.vendor,
|
|
|
|
"product": device.product,
|
|
|
|
"revision": device.revision,
|
|
|
|
}.items()
|
|
|
|
if key == "id" or value
|
|
|
|
}
|
|
|
|
message += f", device: {formatted_device}"
|
|
|
|
|
|
|
|
return message
|
2023-10-31 21:54:04 +00:00
|
|
|
|
|
|
|
# noinspection PyMethodMayBeStatic
|
|
|
|
@lru_cache(maxsize=32)
|
|
|
|
def _get_archive_info(self, file_path, **kwargs):
|
|
|
|
"""
|
|
|
|
Cached wrapper method to improve performance, e.g. on index screen
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return unarchiver.inspect_archive(file_path)
|
|
|
|
except (unarchiver.LsarCommandError, unarchiver.LsarOutputError) as error:
|
|
|
|
logging.error(str(error))
|
|
|
|
raise
|