mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-15 21:26:29 +00:00
Add blockbuster option to API (#5746)
* Add blockbuster option to API * cache not lru_cache
This commit is contained in:
parent
ec721c41c1
commit
80f7f07341
@ -11,10 +11,12 @@ import zlib_fast
|
||||
# Enable fast zlib before importing supervisor
|
||||
zlib_fast.enable()
|
||||
|
||||
from supervisor import bootstrap # pylint: disable=wrong-import-position # noqa: E402
|
||||
from supervisor.utils.logging import ( # pylint: disable=wrong-import-position # noqa: E402
|
||||
activate_log_queue_handler,
|
||||
)
|
||||
# pylint: disable=wrong-import-position
|
||||
from supervisor import bootstrap # noqa: E402
|
||||
from supervisor.utils.blockbuster import activate_blockbuster # noqa: E402
|
||||
from supervisor.utils.logging import activate_log_queue_handler # noqa: E402
|
||||
|
||||
# pylint: enable=wrong-import-position
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -52,6 +54,8 @@ if __name__ == "__main__":
|
||||
_LOGGER.info("Initializing Supervisor setup")
|
||||
coresys = loop.run_until_complete(bootstrap.initialize_coresys())
|
||||
loop.set_debug(coresys.config.debug)
|
||||
if coresys.config.detect_blocking_io:
|
||||
activate_blockbuster()
|
||||
loop.run_until_complete(coresys.core.connect())
|
||||
|
||||
loop.run_until_complete(bootstrap.supervisor_debugger(coresys))
|
||||
|
@ -80,3 +80,11 @@ class BootSlot(StrEnum):
|
||||
|
||||
A = "A"
|
||||
B = "B"
|
||||
|
||||
|
||||
class DetectBlockingIO(StrEnum):
|
||||
"""Enable/Disable detection for blocking I/O in event loop."""
|
||||
|
||||
OFF = "off"
|
||||
ON = "on"
|
||||
ON_AT_STARTUP = "on_at_startup"
|
||||
|
@ -20,6 +20,7 @@ from ..const import (
|
||||
ATTR_CPU_PERCENT,
|
||||
ATTR_DEBUG,
|
||||
ATTR_DEBUG_BLOCK,
|
||||
ATTR_DETECT_BLOCKING_IO,
|
||||
ATTR_DIAGNOSTICS,
|
||||
ATTR_FORCE_SECURITY,
|
||||
ATTR_HEALTHY,
|
||||
@ -47,10 +48,15 @@ from ..const import (
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import APIError
|
||||
from ..store.validate import repositories
|
||||
from ..utils.blockbuster import (
|
||||
activate_blockbuster,
|
||||
blockbuster_enabled,
|
||||
deactivate_blockbuster,
|
||||
)
|
||||
from ..utils.sentry import close_sentry, init_sentry
|
||||
from ..utils.validate import validate_timezone
|
||||
from ..validate import version_tag, wait_boot
|
||||
from .const import CONTENT_TYPE_TEXT
|
||||
from .const import CONTENT_TYPE_TEXT, DetectBlockingIO
|
||||
from .utils import api_process, api_process_raw, api_validate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@ -69,6 +75,7 @@ SCHEMA_OPTIONS = vol.Schema(
|
||||
vol.Optional(ATTR_CONTENT_TRUST): vol.Boolean(),
|
||||
vol.Optional(ATTR_FORCE_SECURITY): vol.Boolean(),
|
||||
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
||||
vol.Optional(ATTR_DETECT_BLOCKING_IO): vol.Coerce(DetectBlockingIO),
|
||||
}
|
||||
)
|
||||
|
||||
@ -101,6 +108,7 @@ class APISupervisor(CoreSysAttributes):
|
||||
ATTR_DEBUG_BLOCK: self.sys_config.debug_block,
|
||||
ATTR_DIAGNOSTICS: self.sys_config.diagnostics,
|
||||
ATTR_AUTO_UPDATE: self.sys_updater.auto_update,
|
||||
ATTR_DETECT_BLOCKING_IO: blockbuster_enabled(),
|
||||
# Depricated
|
||||
ATTR_WAIT_BOOT: self.sys_config.wait_boot,
|
||||
ATTR_ADDONS: [
|
||||
@ -160,6 +168,17 @@ class APISupervisor(CoreSysAttributes):
|
||||
if ATTR_AUTO_UPDATE in body:
|
||||
self.sys_updater.auto_update = body[ATTR_AUTO_UPDATE]
|
||||
|
||||
if detect_blocking_io := body.get(ATTR_DETECT_BLOCKING_IO):
|
||||
if detect_blocking_io == DetectBlockingIO.ON_AT_STARTUP:
|
||||
self.sys_config.detect_blocking_io = True
|
||||
detect_blocking_io = DetectBlockingIO.ON
|
||||
|
||||
if detect_blocking_io == DetectBlockingIO.ON:
|
||||
activate_blockbuster()
|
||||
elif detect_blocking_io == DetectBlockingIO.OFF:
|
||||
self.sys_config.detect_blocking_io = False
|
||||
deactivate_blockbuster()
|
||||
|
||||
# Deprecated
|
||||
if ATTR_WAIT_BOOT in body:
|
||||
self.sys_config.wait_boot = body[ATTR_WAIT_BOOT]
|
||||
|
@ -12,6 +12,7 @@ from .const import (
|
||||
ATTR_ADDONS_CUSTOM_LIST,
|
||||
ATTR_DEBUG,
|
||||
ATTR_DEBUG_BLOCK,
|
||||
ATTR_DETECT_BLOCKING_IO,
|
||||
ATTR_DIAGNOSTICS,
|
||||
ATTR_IMAGE,
|
||||
ATTR_LAST_BOOT,
|
||||
@ -142,6 +143,16 @@ class CoreConfig(FileConfiguration):
|
||||
"""Set debug wait mode."""
|
||||
self._data[ATTR_DEBUG_BLOCK] = value
|
||||
|
||||
@property
|
||||
def detect_blocking_io(self) -> bool:
|
||||
"""Return True if blocking I/O in event loop detection enabled at startup."""
|
||||
return self._data[ATTR_DETECT_BLOCKING_IO]
|
||||
|
||||
@detect_blocking_io.setter
|
||||
def detect_blocking_io(self, value: bool) -> None:
|
||||
"""Enable/Disable blocking I/O in event loop detection at startup."""
|
||||
self._data[ATTR_DETECT_BLOCKING_IO] = value
|
||||
|
||||
@property
|
||||
def diagnostics(self) -> bool | None:
|
||||
"""Return bool if diagnostics is set otherwise None."""
|
||||
|
@ -152,6 +152,7 @@ ATTR_DEFAULT = "default"
|
||||
ATTR_DEPLOYMENT = "deployment"
|
||||
ATTR_DESCRIPTON = "description"
|
||||
ATTR_DETACHED = "detached"
|
||||
ATTR_DETECT_BLOCKING_IO = "detect_blocking_io"
|
||||
ATTR_DEVICES = "devices"
|
||||
ATTR_DEVICETREE = "devicetree"
|
||||
ATTR_DIAGNOSTICS = "diagnostics"
|
||||
|
35
supervisor/utils/blockbuster.py
Normal file
35
supervisor/utils/blockbuster.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""Activate and deactivate blockbuster for finding blocking I/O."""
|
||||
|
||||
from functools import cache
|
||||
import logging
|
||||
|
||||
from blockbuster import BlockBuster
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@cache
|
||||
def _get_blockbuster() -> BlockBuster:
|
||||
"""Get blockbuster instance."""
|
||||
return BlockBuster()
|
||||
|
||||
|
||||
def blockbuster_enabled() -> bool:
|
||||
"""Return true if blockbuster detection is enabled."""
|
||||
blockbuster = _get_blockbuster()
|
||||
# We activate all or none so just check the first one
|
||||
for _, fn in blockbuster.functions.items():
|
||||
return fn.activated
|
||||
return False
|
||||
|
||||
|
||||
def activate_blockbuster() -> None:
|
||||
"""Activate blockbuster detection."""
|
||||
_LOGGER.info("Activating BlockBuster blocking I/O detection")
|
||||
_get_blockbuster().activate()
|
||||
|
||||
|
||||
def deactivate_blockbuster() -> None:
|
||||
"""Deactivate blockbuster detection."""
|
||||
_LOGGER.info("Deactivating BlockBuster blocking I/O detection")
|
||||
_get_blockbuster().deactivate()
|
@ -15,6 +15,7 @@ from .const import (
|
||||
ATTR_CONTENT_TRUST,
|
||||
ATTR_DEBUG,
|
||||
ATTR_DEBUG_BLOCK,
|
||||
ATTR_DETECT_BLOCKING_IO,
|
||||
ATTR_DIAGNOSTICS,
|
||||
ATTR_DISPLAYNAME,
|
||||
ATTR_DNS,
|
||||
@ -162,6 +163,7 @@ SCHEMA_SUPERVISOR_CONFIG = vol.Schema(
|
||||
vol.Optional(ATTR_DEBUG, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_DEBUG_BLOCK, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_DIAGNOSTICS, default=None): vol.Maybe(vol.Boolean()),
|
||||
vol.Optional(ATTR_DETECT_BLOCKING_IO, default=False): vol.Boolean(),
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
|
@ -1,9 +1,11 @@
|
||||
"""Test Supervisor API."""
|
||||
|
||||
# pylint: disable=protected-access
|
||||
import time
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from blockbuster import BlockingError
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
@ -247,3 +249,46 @@ async def test_api_supervisor_options_timezone(
|
||||
assert resp.status == 200
|
||||
|
||||
assert coresys.timezone == "Europe/Zurich"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("blockbuster", "option_value", "config_value"),
|
||||
[("no_blockbuster", "on", False), ("no_blockbuster", "on_at_startup", True)],
|
||||
indirect=["blockbuster"],
|
||||
)
|
||||
async def test_api_supervisor_options_blocking_io(
|
||||
api_client: TestClient, coresys: CoreSys, option_value: str, config_value: bool
|
||||
):
|
||||
"""Test setting supervisor detect blocking io option."""
|
||||
# This should not fail with a blocking error yet
|
||||
time.sleep(0)
|
||||
|
||||
resp = await api_client.post(
|
||||
"/supervisor/options", json={"detect_blocking_io": option_value}
|
||||
)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await api_client.get("/supervisor/info")
|
||||
assert resp.status == 200
|
||||
body = await resp.json()
|
||||
assert body["data"]["detect_blocking_io"] is True
|
||||
|
||||
# This remains false because we only turned it on for current run of supervisor, not permanently
|
||||
assert coresys.config.detect_blocking_io is config_value
|
||||
|
||||
with pytest.raises(BlockingError):
|
||||
time.sleep(0)
|
||||
|
||||
resp = await api_client.post(
|
||||
"/supervisor/options", json={"detect_blocking_io": "off"}
|
||||
)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await api_client.get("/supervisor/info")
|
||||
assert resp.status == 200
|
||||
body = await resp.json()
|
||||
assert body["data"]["detect_blocking_io"] is False
|
||||
assert coresys.config.detect_blocking_io is False
|
||||
|
||||
# This should not raise blocking error anymore
|
||||
time.sleep(0)
|
||||
|
@ -65,8 +65,12 @@ from .dbus_service_mocks.network_manager import NetworkManager as NetworkManager
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def blockbuster() -> BlockBuster:
|
||||
def blockbuster(request: pytest.FixtureRequest) -> BlockBuster | None:
|
||||
"""Raise for blocking I/O in event loop."""
|
||||
if getattr(request, "param", "") == "no_blockbuster":
|
||||
yield None
|
||||
return
|
||||
|
||||
# Only scanning supervisor code for now as that's our primary interest
|
||||
# This will still raise for tests that call utilities in supervisor code that block
|
||||
# But it will ignore calls to libraries and such that do blocking I/O directly from tests
|
||||
|
Loading…
x
Reference in New Issue
Block a user