summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDevaev Maxim <[email protected]>2019-09-25 03:15:20 +0300
committerDevaev Maxim <[email protected]>2019-09-25 03:15:20 +0300
commit5c4e8f7962fe89ee0dcdea9ba859f7045fa2d6d8 (patch)
tree785b2454f56480464d18c71c2221b262bb92eabb
parent5d437c58e342bcb9847e419e46c751b3ed70e747 (diff)
extended msd api for future otg
-rw-r--r--kvmd/apps/__init__.py1
-rw-r--r--kvmd/apps/kvmd/server.py23
-rw-r--r--kvmd/plugins/msd/__init__.py16
-rw-r--r--kvmd/plugins/msd/disabled.py12
-rw-r--r--kvmd/plugins/msd/relay.py66
-rw-r--r--kvmd/validators/kvm.py15
-rw-r--r--testenv/tests/validators/test_kvm.py25
7 files changed, 116 insertions, 42 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py
index 048c2b9d..8014d8f6 100644
--- a/kvmd/apps/__init__.py
+++ b/kvmd/apps/__init__.py
@@ -183,6 +183,7 @@ def _get_config_scheme() -> Dict:
"unix_rm": Option(False, type=valid_bool),
"unix_mode": Option(0, type=valid_unix_mode),
"heartbeat": Option(3.0, type=valid_float_f01),
+ "sync_chunk_size": Option(65536, type=(lambda arg: valid_number(arg, min=1024))),
"access_log_format": Option("[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---"
" referer='%{Referer}i'; user_agent='%{User-Agent}i'"),
},
diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py
index 311cb6fd..4f197641 100644
--- a/kvmd/apps/kvmd/server.py
+++ b/kvmd/apps/kvmd/server.py
@@ -67,6 +67,7 @@ from ...validators.kvm import valid_atx_button
from ...validators.kvm import valid_log_seek
from ...validators.kvm import valid_stream_quality
from ...validators.kvm import valid_stream_fps
+from ...validators.kvm import valid_msd_image_name
from ...validators.kvm import valid_hid_key
from ...validators.kvm import valid_hid_mouse_move
from ...validators.kvm import valid_hid_mouse_button
@@ -250,6 +251,7 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__streamer = streamer
self.__heartbeat: Optional[float] = None # Assigned in run() for consistance
+ self.__sync_chunk_size: Optional[int] = None # Ditto
self.__sockets: Set[aiohttp.web.WebSocketResponse] = set()
self.__sockets_lock = asyncio.Lock()
@@ -266,6 +268,7 @@ class Server: # pylint: disable=too-many-instance-attributes
unix_rm: bool,
unix_mode: int,
heartbeat: float,
+ sync_chunk_size: int,
access_log_format: str,
) -> None:
@@ -274,6 +277,7 @@ class Server: # pylint: disable=too-many-instance-attributes
setproctitle.setproctitle("[main] " + setproctitle.getproctitle())
self.__heartbeat = heartbeat
+ self.__sync_chunk_size = sync_chunk_size
assert port or unix_path
if unix_path:
@@ -474,31 +478,40 @@ class Server: # pylint: disable=too-many-instance-attributes
async def __msd_disconnect_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
return _json(await self.__msd.disconnect())
+ @_exposed("POST", "/msd/select")
+ async def __msd_select_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
+ return _json(await self.__msd.select(valid_msd_image_name(request.query.get("image_name"))))
+
+ @_exposed("POST", "/msd/remove")
+ async def __msd_remove_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
+ return _json(await self.__msd.remove(valid_msd_image_name(request.query.get("image_name"))))
+
@_exposed("POST", "/msd/write")
async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
+ assert self.__sync_chunk_size is not None
logger = get_logger(0)
reader = await request.multipart()
+ image_name = ""
written = 0
try:
async with self.__msd:
name_field = await _get_multipart_field(reader, "image_name")
- image_name = (await name_field.read()).decode("utf-8")[:256]
+ image_name = valid_msd_image_name((await name_field.read()).decode("utf-8"))
data_field = await _get_multipart_field(reader, "image_data")
logger.info("Writing image %r to MSD ...", image_name)
await self.__msd.write_image_info(image_name, False)
- chunk_size = self.__msd.get_chunk_size()
while True:
- chunk = await data_field.read_chunk(chunk_size)
+ chunk = await data_field.read_chunk(self.__sync_chunk_size)
if not chunk:
break
written = await self.__msd.write_image_chunk(chunk)
await self.__msd.write_image_info(image_name, True)
finally:
if written != 0:
- logger.info("Written %d bytes to MSD", written)
- return _json({"written": written})
+ logger.info("Written image %r with size=%d bytes to MSD", image_name, written)
+ return _json({"image": {"name": image_name, "size": written}})
@_exposed("POST", "/msd/reset")
async def __msd_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
diff --git a/kvmd/plugins/msd/__init__.py b/kvmd/plugins/msd/__init__.py
index d5aa3064..bff60232 100644
--- a/kvmd/plugins/msd/__init__.py
+++ b/kvmd/plugins/msd/__init__.py
@@ -64,6 +64,11 @@ class MsdIsBusyError(MsdOperationError):
super().__init__("Performing another MSD operation, please try again later")
+class MsdMultiNotSupported(MsdOperationError):
+ def __init__(self) -> None:
+ super().__init__("This MSD does not support storing multiple images")
+
+
# =====
class BaseMsd(BasePlugin):
def get_state(self) -> Dict:
@@ -73,22 +78,27 @@ class BaseMsd(BasePlugin):
yield {}
raise NotImplementedError
+ async def reset(self) -> None:
+ raise NotImplementedError
+
async def cleanup(self) -> None:
pass
+ # =====
+
async def connect(self) -> Dict:
raise NotImplementedError
async def disconnect(self) -> Dict:
raise NotImplementedError
- async def reset(self) -> None:
+ async def select(self, name: str) -> Dict:
raise NotImplementedError
- async def __aenter__(self) -> "BaseMsd":
+ async def remove(self, name: str) -> Dict:
raise NotImplementedError
- def get_chunk_size(self) -> int:
+ async def __aenter__(self) -> "BaseMsd":
raise NotImplementedError
async def write_image_info(self, name: str, complete: bool) -> None:
diff --git a/kvmd/plugins/msd/disabled.py b/kvmd/plugins/msd/disabled.py
index 56ed10db..5eb3083b 100644
--- a/kvmd/plugins/msd/disabled.py
+++ b/kvmd/plugins/msd/disabled.py
@@ -42,6 +42,7 @@ class Plugin(BaseMsd):
def get_state(self) -> Dict:
return {
"enabled": False,
+ "multi": False,
"online": False,
"busy": False,
"uploading": False,
@@ -56,19 +57,24 @@ class Plugin(BaseMsd):
yield self.get_state()
await asyncio.sleep(60)
+ async def reset(self) -> None:
+ raise MsdDisabledError()
+
+ # =====
+
async def connect(self) -> Dict:
raise MsdDisabledError()
async def disconnect(self) -> Dict:
raise MsdDisabledError()
- async def reset(self) -> None:
+ async def select(self, name: str) -> Dict:
raise MsdDisabledError()
- async def __aenter__(self) -> BaseMsd:
+ async def remove(self, name: str) -> Dict:
raise MsdDisabledError()
- def get_chunk_size(self) -> int:
+ async def __aenter__(self) -> BaseMsd:
raise MsdDisabledError()
async def write_image_info(self, name: str, complete: bool) -> None:
diff --git a/kvmd/plugins/msd/relay.py b/kvmd/plugins/msd/relay.py
index 39818e95..c6b755fb 100644
--- a/kvmd/plugins/msd/relay.py
+++ b/kvmd/plugins/msd/relay.py
@@ -48,7 +48,6 @@ from ... import gpio
from ...yamlconf import Option
-from ...validators.basic import valid_number
from ...validators.basic import valid_int_f1
from ...validators.basic import valid_float_f01
@@ -62,6 +61,7 @@ from . import MsdAlreadyConnectedError
from . import MsdAlreadyDisconnectedError
from . import MsdConnectedError
from . import MsdIsBusyError
+from . import MsdMultiNotSupported
from . import BaseMsd
@@ -170,7 +170,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
init_delay: float,
init_retries: int,
reset_delay: float,
- chunk_size: int,
) -> None:
self.__target_pin = gpio.set_output(target_pin)
@@ -180,7 +179,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__init_delay = init_delay
self.__init_retries = init_retries
self.__reset_delay = reset_delay
- self.__chunk_size = chunk_size
self.__region = aioregion.AioExclusiveRegion(MsdIsBusyError)
@@ -209,8 +207,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
"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),
-
- "chunk_size": Option(65536, type=(lambda arg: valid_number(arg, min=1024))),
}
def get_state(self) -> Dict:
@@ -225,6 +221,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
current = dataclasses.asdict(self._device_info.image)
return {
"enabled": True,
+ "multi": False,
"online": bool(self._device_info),
"busy": self.__region.is_busy(),
"uploading": bool(self.__device_file),
@@ -239,11 +236,38 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
yield (await self.__state_queue.get())
@aiotools.atomic
+ async def reset(self) -> None:
+ with aiotools.unregion_only_on_exception(self.__region):
+ await self.__inner_reset()
+
+ @aiotools.tasked
+ @aiotools.muted("Can't reset MSD or operation was not completed")
+ async def __inner_reset(self) -> None:
+ try:
+ gpio.write(self.__reset_pin, True)
+ await asyncio.sleep(self.__reset_delay)
+ gpio.write(self.__reset_pin, False)
+
+ gpio.write(self.__target_pin, False)
+ self.__on_kvm = True
+
+ await self.__load_device_info()
+ get_logger(0).info("MSD reset has been successful")
+ finally:
+ try:
+ gpio.write(self.__reset_pin, False)
+ finally:
+ self.__region.exit()
+ await self.__state_queue.put(self.get_state())
+
+ @aiotools.atomic
async def cleanup(self) -> None:
await self.__close_device_file()
gpio.write(self.__target_pin, False)
gpio.write(self.__reset_pin, False)
+ # =====
+
@_msd_working
@aiotools.atomic
async def connect(self) -> Dict:
@@ -292,30 +316,13 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
if notify:
await self.__state_queue.put(state or self.get_state())
- @aiotools.atomic
- async def reset(self) -> None:
- with aiotools.unregion_only_on_exception(self.__region):
- await self.__inner_reset()
-
- @aiotools.tasked
- @aiotools.muted("Can't reset MSD or operation was not completed")
- async def __inner_reset(self) -> None:
- try:
- gpio.write(self.__reset_pin, True)
- await asyncio.sleep(self.__reset_delay)
- gpio.write(self.__reset_pin, False)
-
- gpio.write(self.__target_pin, False)
- self.__on_kvm = True
+ @_msd_working
+ async def select(self, name: str) -> Dict:
+ raise MsdMultiNotSupported()
- await self.__load_device_info()
- get_logger(0).info("MSD reset has been successful")
- finally:
- try:
- gpio.write(self.__reset_pin, False)
- finally:
- self.__region.exit()
- await self.__state_queue.put(self.get_state())
+ @_msd_working
+ async def remove(self, name: str) -> Dict:
+ raise MsdMultiNotSupported()
@_msd_working
@aiotools.atomic
@@ -334,9 +341,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
finally:
await self.__state_queue.put(self.get_state())
- def get_chunk_size(self) -> int:
- return self.__chunk_size
-
@aiotools.atomic
async def write_image_info(self, name: str, complete: bool) -> None:
assert self.__device_file
diff --git a/kvmd/validators/kvm.py b/kvmd/validators/kvm.py
index e99929af..29b45500 100644
--- a/kvmd/validators/kvm.py
+++ b/kvmd/validators/kvm.py
@@ -20,10 +20,13 @@
# ========================================================================== #
+import re
+
from typing import Any
from .. import keymap
+from . import check_not_none_string
from . import check_string_in_list
from .basic import valid_number
@@ -38,6 +41,18 @@ def valid_atx_button(arg: Any) -> str:
return check_string_in_list(arg, "ATX button", ["power", "power_long", "reset"])
+def valid_msd_image_name(arg: Any) -> str:
+ if len(str(arg).strip()) == 0:
+ arg = None
+ arg = check_not_none_string(arg, "MSD image name", strip=True)
+ arg = re.sub(r"[^\w\.+@()\[\]-]", "_", arg)
+ if arg == ".":
+ arg = "_"
+ if arg == "..":
+ arg = "__"
+ return arg[:255]
+
+
def valid_log_seek(arg: Any) -> int:
return int(valid_number(arg, min=0, name="log seek"))
diff --git a/testenv/tests/validators/test_kvm.py b/testenv/tests/validators/test_kvm.py
index fb223d6a..eeedb4d0 100644
--- a/testenv/tests/validators/test_kvm.py
+++ b/testenv/tests/validators/test_kvm.py
@@ -32,6 +32,7 @@ from kvmd.validators.kvm import valid_atx_button
from kvmd.validators.kvm import valid_log_seek
from kvmd.validators.kvm import valid_stream_quality
from kvmd.validators.kvm import valid_stream_fps
+from kvmd.validators.kvm import valid_msd_image_name
from kvmd.validators.kvm import valid_hid_key
from kvmd.validators.kvm import valid_hid_mouse_move
from kvmd.validators.kvm import valid_hid_mouse_button
@@ -105,6 +106,30 @@ def test_fail__valid_stream_fps(arg: Any) -> None:
# =====
[email protected]("arg, retval", [
+ ("archlinux-2018.07.01-i686.iso", "archlinux-2018.07.01-i686.iso"),
+ ("archlinux-2018.07.01-x86_64.iso", "archlinux-2018.07.01-x86_64.iso"),
+ ("dsl-4.11.rc1.iso", "dsl-4.11.rc1.iso"),
+ ("systemrescuecd-x86-5.3.1.iso", "systemrescuecd-x86-5.3.1.iso"),
+ ("ubuntu-16.04.5-desktop-i386.iso", "ubuntu-16.04.5-desktop-i386.iso"),
+ (".", "_"),
+ ("..", "__"),
+ ("/..", "_.."),
+ ("/root/..", "_root_.."),
+ (" тест(){}[ \t].iso\t", "тест()__[__].iso"),
+ ("?" * 1000, "_" * 255),
+])
+def test_ok__valid_msd_image_name(arg: Any, retval: str) -> None:
+ assert valid_msd_image_name(arg) == retval
+
+
[email protected]("arg", ["", None])
+def test_fail__valid_msd_image_name(arg: Any) -> None:
+ with pytest.raises(ValidatorError):
+ print(valid_msd_image_name(arg))
+
+
+# =====
def test_ok__valid_hid_key() -> None:
for key in KEYMAP:
print(valid_hid_key(key))