mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 02:24:51 -05:00
Restart HA on saving new http conf and also return it on http/config (#173103)
This commit is contained in:
@@ -5,6 +5,10 @@ from typing import Any
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.homeassistant import (
|
||||
DOMAIN as HASS_DOMAIN,
|
||||
SERVICE_HOMEASSISTANT_RESTART,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
@@ -28,9 +32,16 @@ async def websocket_get_config(
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Return the active HTTP configuration (the confirmed-working ``stable`` slot)."""
|
||||
"""Return the HTTP configuration.
|
||||
|
||||
``stable`` is the confirmed-working config
|
||||
``pending`` is an unconfirmed config awaiting promotion, or ``None``.
|
||||
"""
|
||||
store = await async_get_and_load_store(hass)
|
||||
connection.send_result(msg["id"], store.stable)
|
||||
connection.send_result(
|
||||
msg["id"],
|
||||
{"stable": store.stable, "pending": store.pending},
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@@ -46,15 +57,20 @@ async def websocket_set_config(
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Store a new pending HTTP configuration.
|
||||
"""Store a new pending HTTP configuration and restart to apply it.
|
||||
|
||||
The new config is not applied until Home Assistant is restarted
|
||||
and the user promotes it via ``http/config/promote``. Until then
|
||||
the existing ``stable`` config remains the recovery fallback.
|
||||
Restart whenever the pending slot changes, so the runtime config is
|
||||
refreshed. The result reports whether a restart was triggered via
|
||||
``{"restart": bool}``.
|
||||
"""
|
||||
store = await async_get_and_load_store(hass)
|
||||
previous_pending = store.pending
|
||||
await store.async_set_pending(msg[ATTR_CONFIG])
|
||||
connection.send_result(msg["id"])
|
||||
restart = store.pending != previous_pending
|
||||
connection.send_result(msg["id"], {"restart": restart})
|
||||
|
||||
if restart:
|
||||
await hass.services.async_call(HASS_DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.helpers.network import NoURLAvailableError
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.ssl import server_context_intermediate, server_context_modern
|
||||
|
||||
from tests.common import async_call_logger_set_level
|
||||
from tests.common import async_call_logger_set_level, async_mock_service
|
||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||
|
||||
|
||||
@@ -1205,12 +1205,15 @@ async def test_websocket_http_config(
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
# On a fresh setup the stable slot is seeded with the schema defaults.
|
||||
# Staging a new config triggers a restart so the pending config is applied.
|
||||
restart_calls = async_mock_service(hass, "homeassistant", "restart")
|
||||
|
||||
# On a fresh setup the stable slot is seeded with the schema defaults and
|
||||
# there is no pending config.
|
||||
await ws_client.send_json_auto_id({"type": "http/config"})
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"]["server_port"] == 8123
|
||||
assert response["result"]["ip_ban_enabled"] is True
|
||||
assert response["result"] == {"stable": _DEFAULT_CONFIG, "pending": None}
|
||||
|
||||
new_config = {
|
||||
"server_port": 9123,
|
||||
@@ -1227,15 +1230,19 @@ async def test_websocket_http_config(
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
# Configure is an ack; verify pending state via storage.
|
||||
assert response["result"] == {"restart": True}
|
||||
pending = hass_storage["http"]["data"]["pending"]
|
||||
assert pending["server_port"] == 9123
|
||||
assert pending["trusted_proxies"] == ["127.0.0.0/8"]
|
||||
# Stable is unchanged until the user promotes.
|
||||
await hass.async_block_till_done()
|
||||
assert len(restart_calls) == 1
|
||||
|
||||
# Stable is unchanged until the user promotes, but the pending config is
|
||||
# now returned alongside it.
|
||||
await ws_client.send_json_auto_id({"type": "http/config"})
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"]["server_port"] == 8123
|
||||
assert response["result"] == {"stable": _DEFAULT_CONFIG, "pending": new_config}
|
||||
|
||||
# Promote: pending becomes stable, pending is cleared.
|
||||
await ws_client.send_json_auto_id({"type": "http/config/promote"})
|
||||
@@ -1247,7 +1254,7 @@ async def test_websocket_http_config(
|
||||
await ws_client.send_json_auto_id({"type": "http/config"})
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"]["server_port"] == 9123
|
||||
assert response["result"] == {"stable": new_config, "pending": None}
|
||||
|
||||
# Promoting again with no pending is rejected.
|
||||
await ws_client.send_json_auto_id({"type": "http/config/promote"})
|
||||
@@ -1255,19 +1262,35 @@ async def test_websocket_http_config(
|
||||
assert not response["success"]
|
||||
assert response["error"]["code"] == "not_allowed"
|
||||
|
||||
# Clearing pending leaves stable untouched.
|
||||
# Staging a different config again changes the pending slot -> restart.
|
||||
await ws_client.send_json_auto_id(
|
||||
{"type": "http/config/configure", "config": {"server_port": 7000}}
|
||||
)
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {"restart": True}
|
||||
assert hass_storage["http"]["data"]["pending"]["server_port"] == 7000
|
||||
await hass.async_block_till_done()
|
||||
assert len(restart_calls) == 2
|
||||
|
||||
# Clearing a previously staged config also changes the active config back
|
||||
# to stable, so it must trigger a restart too.
|
||||
await ws_client.send_json_auto_id({"type": "http/config/configure", "config": None})
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {"restart": True}
|
||||
assert hass_storage["http"]["data"]["pending"] is None
|
||||
assert hass_storage["http"]["data"]["stable"]["server_port"] == 9123
|
||||
await hass.async_block_till_done()
|
||||
assert len(restart_calls) == 3
|
||||
|
||||
# Clearing again when there is no pending config is a no-op -> no restart.
|
||||
await ws_client.send_json_auto_id({"type": "http/config/configure", "config": None})
|
||||
response = await ws_client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == {"restart": False}
|
||||
await hass.async_block_till_done()
|
||||
assert len(restart_calls) == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
Reference in New Issue
Block a user