Add system health section for the Supervisor (#43074)
parent
37bcfd1d2f
commit
403514ccb3
|
@ -42,9 +42,11 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
DATA_INFO = "hassio_info"
|
||||
DATA_HOST_INFO = "hassio_host_info"
|
||||
DATA_CORE_INFO = "hassio_core_info"
|
||||
DATA_HOST_INFO = "hassio_host_info"
|
||||
DATA_INFO = "hassio_info"
|
||||
DATA_OS_INFO = "hassio_os_info"
|
||||
DATA_SUPERVISOR_INFO = "hassio_supervisor_info"
|
||||
HASSIO_UPDATE_INTERVAL = timedelta(minutes=55)
|
||||
|
||||
SERVICE_ADDON_START = "addon_start"
|
||||
|
@ -218,6 +220,26 @@ def get_host_info(hass):
|
|||
return hass.data.get(DATA_HOST_INFO)
|
||||
|
||||
|
||||
@callback
|
||||
@bind_hass
|
||||
def get_supervisor_info(hass):
|
||||
"""Return Supervisor information.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
return hass.data.get(DATA_SUPERVISOR_INFO)
|
||||
|
||||
|
||||
@callback
|
||||
@bind_hass
|
||||
def get_os_info(hass):
|
||||
"""Return OS information.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
return hass.data.get(DATA_OS_INFO)
|
||||
|
||||
|
||||
@callback
|
||||
@bind_hass
|
||||
def get_core_info(hass):
|
||||
|
@ -358,6 +380,8 @@ async def async_setup(hass, config):
|
|||
hass.data[DATA_INFO] = await hassio.get_info()
|
||||
hass.data[DATA_HOST_INFO] = await hassio.get_host_info()
|
||||
hass.data[DATA_CORE_INFO] = await hassio.get_core_info()
|
||||
hass.data[DATA_SUPERVISOR_INFO] = await hassio.get_supervisor_info()
|
||||
hass.data[DATA_OS_INFO] = await hassio.get_os_info()
|
||||
except HassioAPIError as err:
|
||||
_LOGGER.warning("Can't read last version: %s", err)
|
||||
|
||||
|
|
|
@ -82,6 +82,14 @@ class HassIO:
|
|||
"""
|
||||
return self.send_command("/host/info", method="get")
|
||||
|
||||
@api_data
|
||||
def get_os_info(self):
|
||||
"""Return data for the OS.
|
||||
|
||||
This method return a coroutine.
|
||||
"""
|
||||
return self.send_command("/os/info", method="get")
|
||||
|
||||
@api_data
|
||||
def get_core_info(self):
|
||||
"""Return data for Home Asssistant Core.
|
||||
|
@ -90,6 +98,14 @@ class HassIO:
|
|||
"""
|
||||
return self.send_command("/core/info", method="get")
|
||||
|
||||
@api_data
|
||||
def get_supervisor_info(self):
|
||||
"""Return data for the Supervisor.
|
||||
|
||||
This method returns a coroutine.
|
||||
"""
|
||||
return self.send_command("/supervisor/info", method="get")
|
||||
|
||||
@api_data
|
||||
def get_addon_info(self, addon):
|
||||
"""Return data for a Add-on.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"domain": "hassio",
|
||||
"name": "Hass.io",
|
||||
"name": "Home Assistant Supervisor",
|
||||
"documentation": "https://www.home-assistant.io/hassio",
|
||||
"dependencies": ["http"],
|
||||
"after_dependencies": ["panel_custom"],
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"system_health": {
|
||||
"info": {
|
||||
"board": "Board",
|
||||
"disk_total": "Disk Total",
|
||||
"disk_used": "Disk Used",
|
||||
"docker_version": "Docker Version",
|
||||
"healthy": "Healthy",
|
||||
"host_os": "Host Operating System",
|
||||
"installed_addons": "Installed Add-ons",
|
||||
"supervisor_api": "Supervisor API",
|
||||
"supervisor_version": "Supervisor Version",
|
||||
"supported": "Supported",
|
||||
"update_channel": "Update Channel",
|
||||
"version_api": "Version API"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
"""Provide info to system health."""
|
||||
import os
|
||||
|
||||
from homeassistant.components import system_health
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
SUPERVISOR_PING = f"http://{os.environ['HASSIO']}/supervisor/ping"
|
||||
OBSERVER_URL = f"http://{os.environ['HASSIO']}:4357"
|
||||
|
||||
|
||||
@callback
|
||||
def async_register(
|
||||
hass: HomeAssistant, register: system_health.SystemHealthRegistration
|
||||
) -> None:
|
||||
"""Register system health callbacks."""
|
||||
register.async_register_info(system_health_info, "/hassio")
|
||||
|
||||
|
||||
async def system_health_info(hass: HomeAssistant):
|
||||
"""Get info for the info page."""
|
||||
info = hass.components.hassio.get_info()
|
||||
host_info = hass.components.hassio.get_host_info()
|
||||
supervisor_info = hass.components.hassio.get_supervisor_info()
|
||||
|
||||
if supervisor_info.get("healthy"):
|
||||
healthy = True
|
||||
else:
|
||||
healthy = {
|
||||
"type": "failed",
|
||||
"error": "Unhealthy",
|
||||
"more_info": "/hassio/system",
|
||||
}
|
||||
|
||||
if supervisor_info.get("supported"):
|
||||
supported = True
|
||||
else:
|
||||
supported = {
|
||||
"type": "failed",
|
||||
"error": "Unsupported",
|
||||
"more_info": "/hassio/system",
|
||||
}
|
||||
|
||||
information = {
|
||||
"host_os": host_info.get("operating_system"),
|
||||
"update_channel": info.get("channel"),
|
||||
"supervisor_version": info.get("supervisor"),
|
||||
"docker_version": info.get("docker"),
|
||||
"disk_total": f"{host_info.get('disk_total')} GB",
|
||||
"disk_used": f"{host_info.get('disk_used')} GB",
|
||||
"healthy": healthy,
|
||||
"supported": supported,
|
||||
}
|
||||
|
||||
if info.get("hassos") is not None:
|
||||
os_info = hass.components.hassio.get_os_info()
|
||||
information["board"] = os_info.get("board")
|
||||
|
||||
information["supervisor_api"] = system_health.async_check_can_reach_url(
|
||||
hass, SUPERVISOR_PING, OBSERVER_URL
|
||||
)
|
||||
information["version_api"] = system_health.async_check_can_reach_url(
|
||||
hass,
|
||||
f"https://version.home-assistant.io/{info.get('channel')}.json",
|
||||
"/hassio/system",
|
||||
)
|
||||
|
||||
information["installed_addons"] = ", ".join(
|
||||
f"{addon['name']} ({addon['version']})"
|
||||
for addon in supervisor_info.get("addons", [])
|
||||
)
|
||||
|
||||
return information
|
|
@ -1,3 +1,18 @@
|
|||
{
|
||||
"title": "Hass.io"
|
||||
}
|
||||
"system_health": {
|
||||
"info": {
|
||||
"board": "Board",
|
||||
"disk_total": "Disk Total",
|
||||
"disk_used": "Disk Used",
|
||||
"docker_version": "Docker Version",
|
||||
"healthy": "Healthy",
|
||||
"host_os": "Host Operating System",
|
||||
"installed_addons": "Installed Add-ons",
|
||||
"supervisor_api": "Supervisor API",
|
||||
"supervisor_version": "Supervisor Version",
|
||||
"supported": "Supported",
|
||||
"update_channel": "Update Channel",
|
||||
"version_api": "Version API"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
{
|
||||
"system_health": {
|
||||
"info": {
|
||||
"installation_type": "Installation Type",
|
||||
"version": "Version",
|
||||
"dev": "Development",
|
||||
"virtualenv": "Virtual Environment",
|
||||
"python_version": "Python Version",
|
||||
"docker": "Docker",
|
||||
"arch": "CPU Architecture",
|
||||
"timezone": "Timezone",
|
||||
"os_name": "Operating System Name",
|
||||
"dev": "Development",
|
||||
"docker": "Docker",
|
||||
"installation_type": "Installation Type",
|
||||
"os_name": "Operating System Family",
|
||||
"os_version": "Operating System Version",
|
||||
"supervisor": "Supervisor",
|
||||
"host_os": "Home Assistant OS",
|
||||
"chassis": "Chassis",
|
||||
"docker_version": "Docker"
|
||||
"python_version": "Python Version",
|
||||
"timezone": "Timezone",
|
||||
"version": "Version",
|
||||
"virtualenv": "Virtual Environment"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,5 +15,17 @@ def async_register(
|
|||
async def system_health_info(hass):
|
||||
"""Get info for the info page."""
|
||||
info = await system_info.async_get_system_info(hass)
|
||||
info.pop("hassio")
|
||||
return info
|
||||
|
||||
return {
|
||||
"version": info.get("version"),
|
||||
"installation_type": info.get("installation_type"),
|
||||
"dev": info.get("dev"),
|
||||
"hassio": info.get("hassio"),
|
||||
"docker": info.get("docker"),
|
||||
"virtualenv": info.get("virtualenv"),
|
||||
"python_version": info.get("python_version"),
|
||||
"os_name": info.get("os_name"),
|
||||
"os_version": info.get("os_version"),
|
||||
"arch": info.get("arch"),
|
||||
"timezone": info.get("timezone"),
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
{
|
||||
"system_health": {
|
||||
"info": {
|
||||
"arch": "CPU Architecture",
|
||||
"chassis": "Chassis",
|
||||
"dev": "Development",
|
||||
"docker": "Docker",
|
||||
"docker_version": "Docker",
|
||||
"host_os": "Home Assistant OS",
|
||||
"installation_type": "Installation Type",
|
||||
"os_name": "Operating System Name",
|
||||
"os_version": "Operating System Version",
|
||||
"python_version": "Python Version",
|
||||
"supervisor": "Supervisor",
|
||||
"timezone": "Timezone",
|
||||
"version": "Version",
|
||||
"virtualenv": "Virtual Environment"
|
||||
}
|
||||
"system_health": {
|
||||
"info": {
|
||||
"arch": "CPU Architecture",
|
||||
"dev": "Development",
|
||||
"docker": "Docker",
|
||||
"installation_type": "Installation Type",
|
||||
"os_name": "Operating System Family",
|
||||
"os_version": "Operating System Version",
|
||||
"python_version": "Python Version",
|
||||
"timezone": "Timezone",
|
||||
"version": "Version",
|
||||
"virtualenv": "Virtual Environment"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Home Assistant"
|
||||
}
|
||||
|
|
|
@ -80,6 +80,39 @@ async def test_api_host_info(hassio_handler, aioclient_mock):
|
|||
assert data["operating_system"] == "Debian GNU/Linux 10 (buster)"
|
||||
|
||||
|
||||
async def test_api_supervisor_info(hassio_handler, aioclient_mock):
|
||||
"""Test setup with API Supervisor info."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/supervisor/info",
|
||||
json={
|
||||
"result": "ok",
|
||||
"data": {"supported": True, "version": "2020.11.1", "channel": "stable"},
|
||||
},
|
||||
)
|
||||
|
||||
data = await hassio_handler.get_supervisor_info()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert data["supported"]
|
||||
assert data["version"] == "2020.11.1"
|
||||
assert data["channel"] == "stable"
|
||||
|
||||
|
||||
async def test_api_os_info(hassio_handler, aioclient_mock):
|
||||
"""Test setup with API OS info."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/os/info",
|
||||
json={
|
||||
"result": "ok",
|
||||
"data": {"board": "odroid-n2", "version": "2020.11.1"},
|
||||
},
|
||||
)
|
||||
|
||||
data = await hassio_handler.get_os_info()
|
||||
assert aioclient_mock.call_count == 1
|
||||
assert data["board"] == "odroid-n2"
|
||||
assert data["version"] == "2020.11.1"
|
||||
|
||||
|
||||
async def test_api_host_info_error(hassio_handler, aioclient_mock):
|
||||
"""Test setup with API Home Assistant info error."""
|
||||
aioclient_mock.get(
|
||||
|
|
|
@ -44,6 +44,14 @@ def mock_all(aioclient_mock):
|
|||
"http://127.0.0.1/core/info",
|
||||
json={"result": "ok", "data": {"version_latest": "1.0.0"}},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/os/info",
|
||||
json={"result": "ok", "data": {"version_latest": "1.0.0"}},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/supervisor/info",
|
||||
json={"result": "ok", "data": {"version_latest": "1.0.0"}},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
|
||||
)
|
||||
|
@ -55,7 +63,7 @@ async def test_setup_api_ping(hass, aioclient_mock):
|
|||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0"
|
||||
assert hass.components.hassio.is_hassio()
|
||||
|
||||
|
@ -94,7 +102,7 @@ async def test_setup_api_push_api_data(hass, aioclient_mock):
|
|||
)
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert not aioclient_mock.mock_calls[1][2]["ssl"]
|
||||
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
|
||||
assert aioclient_mock.mock_calls[1][2]["watchdog"]
|
||||
|
@ -110,7 +118,7 @@ async def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
|
|||
)
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert not aioclient_mock.mock_calls[1][2]["ssl"]
|
||||
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
|
||||
assert not aioclient_mock.mock_calls[1][2]["watchdog"]
|
||||
|
@ -122,7 +130,7 @@ async def test_setup_api_push_api_data_default(hass, aioclient_mock, hass_storag
|
|||
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert not aioclient_mock.mock_calls[1][2]["ssl"]
|
||||
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
|
||||
refresh_token = aioclient_mock.mock_calls[1][2]["refresh_token"]
|
||||
|
@ -169,7 +177,7 @@ async def test_setup_api_existing_hassio_user(hass, aioclient_mock, hass_storage
|
|||
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert not aioclient_mock.mock_calls[1][2]["ssl"]
|
||||
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
|
||||
assert aioclient_mock.mock_calls[1][2]["refresh_token"] == token.token
|
||||
|
@ -183,7 +191,7 @@ async def test_setup_core_push_timezone(hass, aioclient_mock):
|
|||
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert aioclient_mock.mock_calls[2][2]["timezone"] == "testzone"
|
||||
|
||||
with patch("homeassistant.util.dt.set_default_time_zone"):
|
||||
|
@ -200,7 +208,7 @@ async def test_setup_hassio_no_additional_data(hass, aioclient_mock):
|
|||
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
||||
assert result
|
||||
|
||||
assert aioclient_mock.call_count == 7
|
||||
assert aioclient_mock.call_count == 9
|
||||
assert aioclient_mock.mock_calls[-1][3]["X-Hassio-Key"] == "123456"
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
"""Test hassio system health."""
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from aiohttp import ClientError
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .test_init import MOCK_ENVIRON
|
||||
|
||||
from tests.async_mock import patch
|
||||
from tests.common import get_system_health_info
|
||||
|
||||
|
||||
async def test_hassio_system_health(hass, aioclient_mock):
|
||||
"""Test hassio system health."""
|
||||
aioclient_mock.get("http://127.0.0.1/info", json={"result": "ok", "data": {}})
|
||||
aioclient_mock.get("http://127.0.0.1/host/info", json={"result": "ok", "data": {}})
|
||||
aioclient_mock.get("http://127.0.0.1/os/info", json={"result": "ok", "data": {}})
|
||||
aioclient_mock.get("http://127.0.0.1/supervisor/ping", text="")
|
||||
aioclient_mock.get("https://version.home-assistant.io/stable.json", text="")
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/supervisor/info", json={"result": "ok", "data": {}}
|
||||
)
|
||||
|
||||
hass.config.components.add("hassio")
|
||||
with patch.dict(os.environ, MOCK_ENVIRON):
|
||||
assert await async_setup_component(hass, "system_health", {})
|
||||
|
||||
hass.data["hassio_info"] = {
|
||||
"channel": "stable",
|
||||
"supervisor": "2020.11.1",
|
||||
"docker": "19.0.3",
|
||||
"hassos": True,
|
||||
}
|
||||
hass.data["hassio_host_info"] = {
|
||||
"operating_system": "Home Assistant OS 5.9",
|
||||
"disk_total": "32.0",
|
||||
"disk_used": "30.0",
|
||||
}
|
||||
hass.data["hassio_os_info"] = {"board": "odroid-n2"}
|
||||
hass.data["hassio_supervisor_info"] = {
|
||||
"healthy": True,
|
||||
"supported": True,
|
||||
"addons": [{"name": "Awesome Addon", "version": "1.0.0"}],
|
||||
}
|
||||
|
||||
info = await get_system_health_info(hass, "hassio")
|
||||
|
||||
for key, val in info.items():
|
||||
if asyncio.iscoroutine(val):
|
||||
info[key] = await val
|
||||
|
||||
assert info == {
|
||||
"board": "odroid-n2",
|
||||
"disk_total": "32.0 GB",
|
||||
"disk_used": "30.0 GB",
|
||||
"docker_version": "19.0.3",
|
||||
"healthy": True,
|
||||
"host_os": "Home Assistant OS 5.9",
|
||||
"installed_addons": "Awesome Addon (1.0.0)",
|
||||
"supervisor_api": "ok",
|
||||
"supervisor_version": "2020.11.1",
|
||||
"supported": True,
|
||||
"update_channel": "stable",
|
||||
"version_api": "ok",
|
||||
}
|
||||
|
||||
|
||||
async def test_hassio_system_health_with_issues(hass, aioclient_mock):
|
||||
"""Test hassio system health."""
|
||||
aioclient_mock.get("http://127.0.0.1/info", json={"result": "ok", "data": {}})
|
||||
aioclient_mock.get("http://127.0.0.1/host/info", json={"result": "ok", "data": {}})
|
||||
aioclient_mock.get("http://127.0.0.1/os/info", json={"result": "ok", "data": {}})
|
||||
aioclient_mock.get("http://127.0.0.1/supervisor/ping", text="")
|
||||
aioclient_mock.get("https://version.home-assistant.io/stable.json", exc=ClientError)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/supervisor/info", json={"result": "ok", "data": {}}
|
||||
)
|
||||
|
||||
hass.config.components.add("hassio")
|
||||
with patch.dict(os.environ, MOCK_ENVIRON):
|
||||
assert await async_setup_component(hass, "system_health", {})
|
||||
|
||||
hass.data["hassio_info"] = {"channel": "stable"}
|
||||
hass.data["hassio_host_info"] = {}
|
||||
hass.data["hassio_os_info"] = {}
|
||||
hass.data["hassio_supervisor_info"] = {
|
||||
"healthy": False,
|
||||
"supported": False,
|
||||
}
|
||||
|
||||
info = await get_system_health_info(hass, "hassio")
|
||||
|
||||
for key, val in info.items():
|
||||
if asyncio.iscoroutine(val):
|
||||
info[key] = await val
|
||||
|
||||
assert info["healthy"] == {
|
||||
"error": "Unhealthy",
|
||||
"more_info": "/hassio/system",
|
||||
"type": "failed",
|
||||
}
|
||||
assert info["supported"] == {
|
||||
"error": "Unsupported",
|
||||
"more_info": "/hassio/system",
|
||||
"type": "failed",
|
||||
}
|
||||
assert info["version_api"] == {
|
||||
"error": "unreachable",
|
||||
"more_info": "/hassio/system",
|
||||
"type": "failed",
|
||||
}
|
|
@ -72,6 +72,12 @@ async def mock_supervisor_fixture(hass, aioclient_mock):
|
|||
), patch(
|
||||
"homeassistant.components.hassio.HassIO.get_host_info",
|
||||
return_value={},
|
||||
), patch(
|
||||
"homeassistant.components.hassio.HassIO.get_supervisor_info",
|
||||
return_value={},
|
||||
), patch(
|
||||
"homeassistant.components.hassio.HassIO.get_os_info",
|
||||
return_value={},
|
||||
), patch(
|
||||
"homeassistant.components.hassio.HassIO.get_ingress_panels",
|
||||
return_value={"panels": {}},
|
||||
|
|
Loading…
Reference in New Issue