diff options
author | Maxim Devaev <[email protected]> | 2022-11-21 07:19:35 +0300 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2022-11-21 07:19:35 +0300 |
commit | be25f5a0268910616c8089cc200b8be69a9ed9d6 (patch) | |
tree | 0e840d5916e0baa1786fc8fb7f1d01494c2ef07c /kvmd | |
parent | 703cc2b9814793a974a50de222dbdaa0cf6ef3f3 (diff) |
removed relay msd
Diffstat (limited to 'kvmd')
-rw-r--r-- | kvmd/plugins/msd/__init__.py | 19 | ||||
-rw-r--r-- | kvmd/plugins/msd/relay/__init__.py | 293 | ||||
-rw-r--r-- | kvmd/plugins/msd/relay/drive.py | 142 | ||||
-rw-r--r-- | kvmd/plugins/msd/relay/gpio.py | 79 |
4 files changed, 0 insertions, 533 deletions
diff --git a/kvmd/plugins/msd/__init__.py b/kvmd/plugins/msd/__init__.py index 21afd86b..ffe4cf76 100644 --- a/kvmd/plugins/msd/__init__.py +++ b/kvmd/plugins/msd/__init__.py @@ -85,21 +85,6 @@ class MsdImageExistsError(MsdOperationError): super().__init__("This image is already exists") -class MsdMultiNotSupported(MsdOperationError): - def __init__(self) -> None: - super().__init__("This MSD does not support storing multiple images") - - -class MsdCdromNotSupported(MsdOperationError): - def __init__(self) -> None: - super().__init__("This MSD does not support CD-ROM switching") - - -class MsdRwNotSupported(MsdOperationError): - def __init__(self) -> None: - super().__init__("This MSD does not support RW switching") - - # ===== class BaseMsdReader: def get_state(self) -> dict: @@ -281,10 +266,6 @@ class MsdFileWriter(BaseMsdWriter): # pylint: disable=too-many-instance-attribu def is_complete(self) -> bool: return (self.__written >= self.__file_size) - def get_file(self) -> aiofiles.base.AiofilesContextManager: - assert self.__file is not None - return self.__file - async def open(self) -> "MsdFileWriter": assert self.__file is None get_logger(1).info("Writing %r image (%d bytes) to MSD ...", self.__name, self.__file_size) diff --git a/kvmd/plugins/msd/relay/__init__.py b/kvmd/plugins/msd/relay/__init__.py deleted file mode 100644 index a93d5e34..00000000 --- a/kvmd/plugins/msd/relay/__init__.py +++ /dev/null @@ -1,293 +0,0 @@ -# ========================================================================== # -# # -# KVMD - The main PiKVM daemon. # -# # -# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU 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 General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see <https://www.gnu.org/licenses/>. # -# # -# ========================================================================== # - - -import asyncio -import contextlib -import dataclasses -import functools - -from typing import AsyncGenerator - -from ....logging import get_logger - -from .... import aiotools - -from ....yamlconf import Option - -from ....validators.basic import valid_bool -from ....validators.basic import valid_number -from ....validators.basic import valid_int_f1 -from ....validators.basic import valid_float_f01 -from ....validators.os import valid_abs_path -from ....validators.hw import valid_gpio_pin - -from .. import MsdError -from .. import MsdIsBusyError -from .. import MsdOfflineError -from .. import MsdConnectedError -from .. import MsdDisconnectedError -from .. import MsdMultiNotSupported -from .. import MsdCdromNotSupported -from .. import MsdRwNotSupported -from .. import BaseMsdReader -from .. import BaseMsd -from .. import MsdFileWriter - -from .gpio import Gpio - -from .drive import DeviceInfo - - -# ===== -class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes - def __init__( # pylint: disable=super-init-not-called,too-many-arguments - self, - upload_chunk_size: int, - sync_chunk_size: int, - - gpio_device_path: str, - target_pin: int, - reset_inverted: bool, - reset_pin: int, - - device_path: str, - init_delay: float, - init_retries: int, - reset_delay: float, - ) -> None: - - self.__upload_chunk_size = upload_chunk_size - self.__sync_chunk_size = sync_chunk_size - - self.__device_path = device_path - self.__init_delay = init_delay - self.__init_retries = init_retries - - self.__gpio = Gpio(gpio_device_path, target_pin, reset_pin, reset_inverted, reset_delay) - - self.__device_info: (DeviceInfo | None) = None - self.__connected = False - - self.__device_writer: (MsdFileWriter | None) = None - - self.__notifier = aiotools.AioNotifier() - self.__region = aiotools.AioExclusiveRegion(MsdIsBusyError, self.__notifier) - - @classmethod - def get_plugin_options(cls) -> dict: - return { - "upload_chunk_size": Option(65536, type=functools.partial(valid_number, min=1024)), - "sync_chunk_size": Option(4194304, type=functools.partial(valid_number, min=1024)), - - "gpio_device": Option("/dev/gpiochip0", type=valid_abs_path, unpack_as="gpio_device_path"), - "target_pin": Option(-1, type=valid_gpio_pin), - "reset_pin": Option(-1, type=valid_gpio_pin), - "reset_inverted": Option(False, type=valid_bool), - - "device": Option("", type=valid_abs_path, unpack_as="device_path"), - "init_delay": Option(1.0, type=valid_float_f01), - "init_retries": Option(5, type=valid_int_f1), - "reset_delay": Option(1.0, type=valid_float_f01), - } - - def sysprep(self) -> None: - logger = get_logger(0) - self.__gpio.open() - logger.info("Using %r as MSD", self.__device_path) - try: - aiotools.run_sync(self.__load_device_info()) - except Exception as err: - log = (logger.error if isinstance(err, MsdError) else logger.exception) - log("MSD is offline: %s", err) - - async def get_state(self) -> dict: - storage: (dict | None) = None - drive: (dict | None) = None - if self.__device_info: - storage = { - "size": self.__device_info.size, - "free": self.__device_info.free, - "uploading": (self.__device_writer.get_state() if self.__device_writer else None), - } - drive = { - "image": (self.__device_info.image and dataclasses.asdict(self.__device_info.image)), - "connected": self.__connected, - } - return { - "enabled": True, - "online": bool(self.__device_info), - "busy": self.__region.is_busy(), - "storage": storage, - "drive": drive, - "features": { - "multi": False, - "cdrom": False, - "rw": False, - }, - } - - async def poll_state(self) -> AsyncGenerator[dict, None]: - prev_state: dict = {} - while True: - state = await self.get_state() - if state != prev_state: - yield state - prev_state = state - await self.__notifier.wait() - - @aiotools.atomic_fg - async def reset(self) -> None: - await aiotools.run_region_task( - "Can't reset MSD or operation was not completed", - self.__region, self.__inner_reset, - ) - - @aiotools.atomic_fg - async def __inner_reset(self) -> None: - await self.__gpio.reset() - self.__gpio.switch_to_local() - self.__connected = False - await self.__load_device_info() - get_logger(0).info("MSD reset has been successful") - - @aiotools.atomic_fg - async def cleanup(self) -> None: - try: - await self.__close_device_writer() - finally: - self.__gpio.close() - - # ===== - - @aiotools.atomic_fg - async def set_params( - self, - name: (str | None)=None, - cdrom: (bool | None)=None, - rw: (bool | None)=None, - ) -> None: - - async with self.__working(): - if name is not None: - raise MsdMultiNotSupported() - if cdrom is not None: - raise MsdCdromNotSupported() - if rw is not None: - raise MsdRwNotSupported() - - @aiotools.atomic_fg - async def set_connected(self, connected: bool) -> None: - async with self.__working(): - async with self.__region: - if connected: - if self.__connected: - raise MsdConnectedError() - self.__gpio.switch_to_server() - get_logger(0).info("MSD switched to Server") - else: - if not self.__connected: - raise MsdDisconnectedError() - self.__gpio.switch_to_local() - try: - await self.__load_device_info() - except Exception: - if self.__connected: - self.__gpio.switch_to_server() - raise - get_logger(0).info("MSD switched to KVM: %s", self.__device_info) - self.__connected = connected - - @contextlib.asynccontextmanager - async def read_image(self, name: str) -> AsyncGenerator[BaseMsdReader, None]: - async with self.__working(): - if self is not None: # XXX: Vulture and pylint hack - raise MsdMultiNotSupported() - yield BaseMsdReader() - - @contextlib.asynccontextmanager - async def write_image(self, name: str, size: int, remove_incomplete: (bool | None)) -> AsyncGenerator[MsdFileWriter, None]: - async with self.__working(): - if remove_incomplete is not None: - raise MsdMultiNotSupported() - async with self.__region: - try: - assert self.__device_info - if self.__connected: - raise MsdConnectedError() - - self.__device_writer = await MsdFileWriter( - notifier=self.__notifier, - path=self.__device_info.path, - file_size=size, - sync_size=self.__sync_chunk_size, - chunk_size=self.__upload_chunk_size, - ).open() - - await self.__write_image_info(False) - self.__notifier.notify() - yield self.__device_writer - await self.__write_image_info(True) - finally: - try: - await aiotools.shield_fg(self.__close_device_writer()) - finally: - await aiotools.shield_fg(self.__load_device_info()) - - @aiotools.atomic_fg - async def remove(self, name: str) -> None: - async with self.__working(): - raise MsdMultiNotSupported() - - # ===== - - @contextlib.asynccontextmanager - async def __working(self) -> AsyncGenerator[None, None]: - if not self.__device_info: - raise MsdOfflineError() - yield - - # ===== - - async def __write_image_info(self, complete: bool) -> None: - assert self.__device_writer - assert self.__device_info - if not (await self.__device_info.write_image_info(self.__device_writer, complete)): - get_logger().error("Can't write image info because device is full") - - async def __close_device_writer(self) -> None: - if self.__device_writer: - await self.__device_writer.close() # type: ignore - self.__device_writer = None - - async def __load_device_info(self) -> None: - retries = self.__init_retries - while True: - await asyncio.sleep(self.__init_delay) - try: - self.__device_info = await DeviceInfo.read(self.__device_path) - break - except Exception: - if retries == 0: - self.__device_info = None - raise MsdError("Can't load device info") - get_logger().exception("Can't load device info; retries=%d", retries) - retries -= 1 diff --git a/kvmd/plugins/msd/relay/drive.py b/kvmd/plugins/msd/relay/drive.py deleted file mode 100644 index 51a85392..00000000 --- a/kvmd/plugins/msd/relay/drive.py +++ /dev/null @@ -1,142 +0,0 @@ -# ========================================================================== # -# # -# KVMD - The main PiKVM daemon. # -# # -# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU 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 General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see <https://www.gnu.org/licenses/>. # -# # -# ========================================================================== # - - -import os -import stat -import fcntl -import struct -import dataclasses - -from typing import IO - -from .... import aiotools -from .... import aiofs - -from .. import MsdFileWriter - - -# ===== -_IMAGE_INFO_SIZE = 4096 -_IMAGE_INFO_MAGIC_SIZE = 16 -_IMAGE_INFO_NAME_SIZE = 256 -_IMAGE_INFO_PADS_SIZE = _IMAGE_INFO_SIZE - _IMAGE_INFO_NAME_SIZE - 1 - 8 - _IMAGE_INFO_MAGIC_SIZE * 8 -_IMAGE_INFO_FORMAT = ">%dL%dc?Q%dx%dL" % ( - _IMAGE_INFO_MAGIC_SIZE, - _IMAGE_INFO_NAME_SIZE, - _IMAGE_INFO_PADS_SIZE, - _IMAGE_INFO_MAGIC_SIZE, -) -_IMAGE_INFO_MAGIC = [0x1ACE1ACE] * _IMAGE_INFO_MAGIC_SIZE - - -# ===== [email protected](frozen=True) -class ImageInfo: - name: str - size: int - complete: bool - - @classmethod - def from_bytes(cls, data: bytes) -> ("ImageInfo" | None): - try: - parsed = list(struct.unpack(_IMAGE_INFO_FORMAT, data)) - except struct.error: - pass - else: - magic_begin = parsed[:_IMAGE_INFO_MAGIC_SIZE] - magic_end = parsed[-_IMAGE_INFO_MAGIC_SIZE:] - if magic_begin == magic_end == _IMAGE_INFO_MAGIC: - image_name_bytes = b"".join(parsed[ - _IMAGE_INFO_MAGIC_SIZE # noqa: E203 - : - _IMAGE_INFO_MAGIC_SIZE + _IMAGE_INFO_NAME_SIZE - ]) - return ImageInfo( - name=image_name_bytes.decode("utf-8", errors="ignore").strip("\x00").strip(), - size=parsed[_IMAGE_INFO_MAGIC_SIZE + _IMAGE_INFO_NAME_SIZE + 1], - complete=parsed[_IMAGE_INFO_MAGIC_SIZE + _IMAGE_INFO_NAME_SIZE], - ) - return None - - def to_bytes(self) -> bytes: - return struct.pack( - _IMAGE_INFO_FORMAT, - *_IMAGE_INFO_MAGIC, - *memoryview(( # type: ignore - self.name.encode("utf-8") - + b"\x00" * _IMAGE_INFO_NAME_SIZE - )[:_IMAGE_INFO_NAME_SIZE]).cast("c"), - self.complete, - self.size, - *_IMAGE_INFO_MAGIC, - ) - - [email protected](frozen=True) -class DeviceInfo: - path: str - size: int - free: int - image: (ImageInfo | None) - - @classmethod - async def read(cls, device_path: str) -> "DeviceInfo": - return (await aiotools.run_async(cls.__inner_read, device_path)) - - @classmethod - def __inner_read(cls, device_path: str) -> "DeviceInfo": - if not stat.S_ISBLK(os.stat(device_path).st_mode): - raise RuntimeError(f"Not a block device: {device_path}") - - with open(device_path, "rb") as device_file: - # size = BLKGETSIZE * BLKSSZGET - size = _ioctl_uint32(device_file, 0x1260) * _ioctl_uint32(device_file, 0x1268) - device_file.seek(size - _IMAGE_INFO_SIZE) - image_info = ImageInfo.from_bytes(device_file.read()) - - return DeviceInfo( - path=device_path, - size=size, - free=(size - image_info.size if image_info else size), - image=image_info, - ) - - async def write_image_info(self, device_writer: MsdFileWriter, complete: bool) -> bool: - device_file = device_writer.get_file() - state = device_writer.get_state() - image_info = ImageInfo(state["name"], state["written"], complete) - - if self.size - image_info.size > _IMAGE_INFO_SIZE: - await device_file.seek(self.size - _IMAGE_INFO_SIZE) # type: ignore - await device_file.write(image_info.to_bytes()) # type: ignore - await aiofs.afile_sync(device_file) - await device_file.seek(0) # type: ignore - return True - return False # Device is full - - -def _ioctl_uint32(device_file: IO, request: int) -> int: - buf = b"\0" * 4 - buf = fcntl.ioctl(device_file.fileno(), request, buf) # type: ignore - result = struct.unpack("I", buf)[0] - assert result > 0, (device_file, request, buf) - return result diff --git a/kvmd/plugins/msd/relay/gpio.py b/kvmd/plugins/msd/relay/gpio.py deleted file mode 100644 index cec31f1a..00000000 --- a/kvmd/plugins/msd/relay/gpio.py +++ /dev/null @@ -1,79 +0,0 @@ -# ========================================================================== # -# # -# KVMD - The main PiKVM daemon. # -# # -# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU 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 General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see <https://www.gnu.org/licenses/>. # -# # -# ========================================================================== # - - -import gpiod - -from .... import aiogp - - -# ===== -class Gpio: # pylint: disable=too-many-instance-attributes - def __init__( - self, - device_path: str, - target_pin: int, - reset_pin: int, - reset_inverted: bool, - reset_delay: float, - ) -> None: - - self.__device_path = device_path - self.__target_pin = target_pin - self.__reset_pin = reset_pin - self.__reset_inverted = reset_inverted - self.__reset_delay = reset_delay - - self.__chip: (gpiod.Chip | None) = None - self.__target_line: (gpiod.Line | None) = None - self.__reset_line: (gpiod.Line | None) = None - - def open(self) -> None: - assert self.__chip is None - assert self.__target_line is None - assert self.__reset_line is None - - self.__chip = gpiod.Chip(self.__device_path) - - self.__target_line = self.__chip.get_line(self.__target_pin) - self.__target_line.request("kvmd::msd::target", gpiod.LINE_REQ_DIR_OUT, default_vals=[0]) - - self.__reset_line = self.__chip.get_line(self.__reset_pin) - self.__reset_line.request("kvmd::msd::reset", gpiod.LINE_REQ_DIR_OUT, default_vals=[int(self.__reset_inverted)]) - - def close(self) -> None: - if self.__chip: - try: - self.__chip.close() - except Exception: - pass - - def switch_to_local(self) -> None: - assert self.__target_line - self.__target_line.set_value(0) - - def switch_to_server(self) -> None: - assert self.__target_line - self.__target_line.set_value(1) - - async def reset(self) -> None: - assert self.__reset_line - await aiogp.pulse(self.__reset_line, self.__reset_delay, 0, self.__reset_inverted) |