Validate go2rtc server version (#129810)
parent
af58b0c3b7
commit
8889464e04
|
@ -5,7 +5,7 @@ import shutil
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
||||||
from go2rtc_client import Go2RtcRestClient
|
from go2rtc_client import Go2RtcRestClient
|
||||||
from go2rtc_client.exceptions import Go2RtcClientError
|
from go2rtc_client.exceptions import Go2RtcClientError, Go2RtcVersionError
|
||||||
from go2rtc_client.ws import (
|
from go2rtc_client.ws import (
|
||||||
Go2RtcWsClient,
|
Go2RtcWsClient,
|
||||||
ReceiveMessages,
|
ReceiveMessages,
|
||||||
|
@ -114,7 +114,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
server = Server(
|
server = Server(
|
||||||
hass, binary, enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False)
|
hass, binary, enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False)
|
||||||
)
|
)
|
||||||
await server.start()
|
try:
|
||||||
|
await server.start()
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
_LOGGER.warning("Could not start go2rtc server", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
async def on_stop(event: Event) -> None:
|
async def on_stop(event: Event) -> None:
|
||||||
await server.stop()
|
await server.stop()
|
||||||
|
@ -143,7 +147,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
# Validate the server URL
|
# Validate the server URL
|
||||||
try:
|
try:
|
||||||
client = Go2RtcRestClient(async_get_clientsession(hass), url)
|
client = Go2RtcRestClient(async_get_clientsession(hass), url)
|
||||||
await client.streams.list()
|
await client.validate_server_version()
|
||||||
except Go2RtcClientError as err:
|
except Go2RtcClientError as err:
|
||||||
if isinstance(err.__cause__, _RETRYABLE_ERRORS):
|
if isinstance(err.__cause__, _RETRYABLE_ERRORS):
|
||||||
raise ConfigEntryNotReady(
|
raise ConfigEntryNotReady(
|
||||||
|
@ -151,6 +155,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
) from err
|
) from err
|
||||||
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
|
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
|
||||||
return False
|
return False
|
||||||
|
except Go2RtcVersionError as err:
|
||||||
|
raise ConfigEntryNotReady(
|
||||||
|
f"The go2rtc server version is not supported, {err}"
|
||||||
|
) from err
|
||||||
except Exception as err: # noqa: BLE001
|
except Exception as err: # noqa: BLE001
|
||||||
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
|
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -112,6 +112,10 @@ class Server:
|
||||||
await self._stop()
|
await self._stop()
|
||||||
raise Go2RTCServerStartError from err
|
raise Go2RTCServerStartError from err
|
||||||
|
|
||||||
|
# Check the server version
|
||||||
|
client = Go2RtcRestClient(async_get_clientsession(self._hass), DEFAULT_URL)
|
||||||
|
await client.validate_server_version()
|
||||||
|
|
||||||
async def _log_output(self, process: asyncio.subprocess.Process) -> None:
|
async def _log_output(self, process: asyncio.subprocess.Process) -> None:
|
||||||
"""Log the output of the process."""
|
"""Log the output of the process."""
|
||||||
assert process.stdout is not None
|
assert process.stdout is not None
|
||||||
|
@ -174,7 +178,7 @@ class Server:
|
||||||
_LOGGER.debug("Monitoring go2rtc API")
|
_LOGGER.debug("Monitoring go2rtc API")
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
await client.streams.list()
|
await client.validate_server_version()
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
_LOGGER.debug("go2rtc API did not reply", exc_info=True)
|
_LOGGER.debug("go2rtc API did not reply", exc_info=True)
|
||||||
|
|
|
@ -23,6 +23,7 @@ def rest_client() -> Generator[AsyncMock]:
|
||||||
client = mock_client.return_value
|
client = mock_client.return_value
|
||||||
client.streams = streams = Mock(spec_set=_StreamClient)
|
client.streams = streams = Mock(spec_set=_StreamClient)
|
||||||
streams.list.return_value = {}
|
streams.list.return_value = {}
|
||||||
|
client.validate_server_version = AsyncMock()
|
||||||
client.webrtc = Mock(spec_set=_WebRTCClient)
|
client.webrtc = Mock(spec_set=_WebRTCClient)
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
||||||
from go2rtc_client import Stream
|
from go2rtc_client import Stream
|
||||||
from go2rtc_client.exceptions import Go2RtcClientError
|
from go2rtc_client.exceptions import Go2RtcClientError, Go2RtcVersionError
|
||||||
from go2rtc_client.models import Producer
|
from go2rtc_client.models import Producer
|
||||||
from go2rtc_client.ws import (
|
from go2rtc_client.ws import (
|
||||||
ReceiveMessages,
|
ReceiveMessages,
|
||||||
|
@ -494,6 +494,8 @@ ERR_CONNECT = "Could not connect to go2rtc instance"
|
||||||
ERR_CONNECT_RETRY = (
|
ERR_CONNECT_RETRY = (
|
||||||
"Could not connect to go2rtc instance on http://localhost:1984/; Retrying"
|
"Could not connect to go2rtc instance on http://localhost:1984/; Retrying"
|
||||||
)
|
)
|
||||||
|
ERR_START_SERVER = "Could not start go2rtc server"
|
||||||
|
ERR_UNSUPPORTED_VERSION = "The go2rtc server version is not supported"
|
||||||
_INVALID_CONFIG = "Invalid config for 'go2rtc': "
|
_INVALID_CONFIG = "Invalid config for 'go2rtc': "
|
||||||
ERR_INVALID_URL = _INVALID_CONFIG + "invalid url"
|
ERR_INVALID_URL = _INVALID_CONFIG + "invalid url"
|
||||||
ERR_EXCLUSIVE = _INVALID_CONFIG + DEBUG_UI_URL_MESSAGE
|
ERR_EXCLUSIVE = _INVALID_CONFIG + DEBUG_UI_URL_MESSAGE
|
||||||
|
@ -526,8 +528,10 @@ async def test_non_user_setup_with_error(
|
||||||
("config", "go2rtc_binary", "is_docker_env", "expected_log_message"),
|
("config", "go2rtc_binary", "is_docker_env", "expected_log_message"),
|
||||||
[
|
[
|
||||||
({DEFAULT_CONFIG_DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
|
({DEFAULT_CONFIG_DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
|
||||||
|
({DEFAULT_CONFIG_DOMAIN: {}}, "/usr/bin/go2rtc", True, ERR_START_SERVER),
|
||||||
({DOMAIN: {}}, None, False, ERR_URL_REQUIRED),
|
({DOMAIN: {}}, None, False, ERR_URL_REQUIRED),
|
||||||
({DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
|
({DOMAIN: {}}, None, True, ERR_BINARY_NOT_FOUND),
|
||||||
|
({DOMAIN: {}}, "/usr/bin/go2rtc", True, ERR_START_SERVER),
|
||||||
({DOMAIN: {CONF_URL: "invalid"}}, None, True, ERR_INVALID_URL),
|
({DOMAIN: {CONF_URL: "invalid"}}, None, True, ERR_INVALID_URL),
|
||||||
(
|
(
|
||||||
{DOMAIN: {CONF_URL: "http://localhost:1984", CONF_DEBUG_UI: True}},
|
{DOMAIN: {CONF_URL: "http://localhost:1984", CONF_DEBUG_UI: True}},
|
||||||
|
@ -559,8 +563,6 @@ async def test_setup_with_setup_error(
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("config", "go2rtc_binary", "is_docker_env", "expected_log_message"),
|
("config", "go2rtc_binary", "is_docker_env", "expected_log_message"),
|
||||||
[
|
[
|
||||||
({DEFAULT_CONFIG_DOMAIN: {}}, "/usr/bin/go2rtc", True, ERR_CONNECT),
|
|
||||||
({DOMAIN: {}}, "/usr/bin/go2rtc", True, ERR_CONNECT),
|
|
||||||
({DOMAIN: {CONF_URL: "http://localhost:1984/"}}, None, True, ERR_CONNECT),
|
({DOMAIN: {CONF_URL: "http://localhost:1984/"}}, None, True, ERR_CONNECT),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -584,7 +586,7 @@ async def test_setup_with_setup_entry_error(
|
||||||
assert expected_log_message in caplog.text
|
assert expected_log_message in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("config", [{DOMAIN: {}}, {DEFAULT_CONFIG_DOMAIN: {}}])
|
@pytest.mark.parametrize("config", [{DOMAIN: {CONF_URL: "http://localhost:1984/"}}])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("cause", "expected_config_entry_state", "expected_log_message"),
|
("cause", "expected_config_entry_state", "expected_log_message"),
|
||||||
[
|
[
|
||||||
|
@ -598,7 +600,7 @@ async def test_setup_with_setup_entry_error(
|
||||||
@pytest.mark.usefixtures(
|
@pytest.mark.usefixtures(
|
||||||
"mock_get_binary", "mock_go2rtc_entry", "mock_is_docker_env", "server"
|
"mock_get_binary", "mock_go2rtc_entry", "mock_is_docker_env", "server"
|
||||||
)
|
)
|
||||||
async def test_setup_with_retryable_setup_entry_error(
|
async def test_setup_with_retryable_setup_entry_error_custom_server(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
rest_client: AsyncMock,
|
rest_client: AsyncMock,
|
||||||
|
@ -610,7 +612,78 @@ async def test_setup_with_retryable_setup_entry_error(
|
||||||
"""Test setup integration entry fails."""
|
"""Test setup integration entry fails."""
|
||||||
go2rtc_error = Go2RtcClientError()
|
go2rtc_error = Go2RtcClientError()
|
||||||
go2rtc_error.__cause__ = cause
|
go2rtc_error.__cause__ = cause
|
||||||
rest_client.streams.list.side_effect = go2rtc_error
|
rest_client.validate_server_version.side_effect = go2rtc_error
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
assert len(config_entries) == 1
|
||||||
|
assert config_entries[0].state == expected_config_entry_state
|
||||||
|
assert expected_log_message in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("config", [{DOMAIN: {}}, {DEFAULT_CONFIG_DOMAIN: {}}])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("cause", "expected_config_entry_state", "expected_log_message"),
|
||||||
|
[
|
||||||
|
(ClientConnectionError(), ConfigEntryState.NOT_LOADED, ERR_START_SERVER),
|
||||||
|
(ServerConnectionError(), ConfigEntryState.NOT_LOADED, ERR_START_SERVER),
|
||||||
|
(None, ConfigEntryState.NOT_LOADED, ERR_START_SERVER),
|
||||||
|
(Exception(), ConfigEntryState.NOT_LOADED, ERR_START_SERVER),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
||||||
|
@pytest.mark.usefixtures(
|
||||||
|
"mock_get_binary", "mock_go2rtc_entry", "mock_is_docker_env", "server"
|
||||||
|
)
|
||||||
|
async def test_setup_with_retryable_setup_entry_error_default_server(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
rest_client: AsyncMock,
|
||||||
|
has_go2rtc_entry: bool,
|
||||||
|
config: ConfigType,
|
||||||
|
cause: Exception,
|
||||||
|
expected_config_entry_state: ConfigEntryState,
|
||||||
|
expected_log_message: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test setup integration entry fails."""
|
||||||
|
go2rtc_error = Go2RtcClientError()
|
||||||
|
go2rtc_error.__cause__ = cause
|
||||||
|
rest_client.validate_server_version.side_effect = go2rtc_error
|
||||||
|
assert not await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
assert len(config_entries) == has_go2rtc_entry
|
||||||
|
for config_entry in config_entries:
|
||||||
|
assert config_entry.state == expected_config_entry_state
|
||||||
|
assert expected_log_message in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("config", [{DOMAIN: {}}, {DEFAULT_CONFIG_DOMAIN: {}}])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("go2rtc_error", "expected_config_entry_state", "expected_log_message"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
Go2RtcVersionError("1.9.4", "1.9.5", "2.0.0"),
|
||||||
|
ConfigEntryState.SETUP_RETRY,
|
||||||
|
ERR_UNSUPPORTED_VERSION,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
||||||
|
@pytest.mark.usefixtures(
|
||||||
|
"mock_get_binary", "mock_go2rtc_entry", "mock_is_docker_env", "server"
|
||||||
|
)
|
||||||
|
async def test_setup_with_version_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
rest_client: AsyncMock,
|
||||||
|
config: ConfigType,
|
||||||
|
go2rtc_error: Exception,
|
||||||
|
expected_config_entry_state: ConfigEntryState,
|
||||||
|
expected_log_message: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test setup integration entry fails."""
|
||||||
|
rest_client.validate_server_version.side_effect = [None, go2rtc_error]
|
||||||
assert await async_setup_component(hass, DOMAIN, config)
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
|
|
@ -47,6 +47,7 @@ def mock_tempfile() -> Generator[Mock]:
|
||||||
)
|
)
|
||||||
async def test_server_run_success(
|
async def test_server_run_success(
|
||||||
mock_create_subprocess: AsyncMock,
|
mock_create_subprocess: AsyncMock,
|
||||||
|
rest_client: AsyncMock,
|
||||||
server_stdout: list[str],
|
server_stdout: list[str],
|
||||||
server: Server,
|
server: Server,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
@ -95,7 +96,7 @@ webrtc:
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_tempfile")
|
@pytest.mark.usefixtures("mock_tempfile")
|
||||||
async def test_server_timeout_on_stop(
|
async def test_server_timeout_on_stop(
|
||||||
mock_create_subprocess: MagicMock, server: Server
|
mock_create_subprocess: MagicMock, rest_client: AsyncMock, server: Server
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test server run where the process takes too long to terminate."""
|
"""Test server run where the process takes too long to terminate."""
|
||||||
# Start server thread
|
# Start server thread
|
||||||
|
|
Loading…
Reference in New Issue