Compare commits

...

1 Commits

Author SHA1 Message Date
ludeeus
374cb0e69d Add host and add-on resource usage to support package download 2026-01-16 07:45:13 +00:00
6 changed files with 699 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ import attr
from hass_nabucasa import AlreadyConnectedError, Cloud, auth from hass_nabucasa import AlreadyConnectedError, Cloud, auth
from hass_nabucasa.const import STATE_DISCONNECTED from hass_nabucasa.const import STATE_DISCONNECTED
from hass_nabucasa.voice_data import TTS_VOICES from hass_nabucasa.voice_data import TTS_VOICES
import psutil_home_assistant as ha_psutil
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
@@ -27,6 +28,7 @@ from homeassistant.components.alexa import (
errors as alexa_errors, errors as alexa_errors,
) )
from homeassistant.components.google_assistant import helpers as google_helpers from homeassistant.components.google_assistant import helpers as google_helpers
from homeassistant.components.hassio import get_addons_stats, get_supervisor_info
from homeassistant.components.homeassistant import exposed_entities from homeassistant.components.homeassistant import exposed_entities
from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin
from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.data_validator import RequestDataValidator
@@ -37,6 +39,7 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.hassio import is_hassio
from homeassistant.loader import ( from homeassistant.loader import (
async_get_custom_components, async_get_custom_components,
async_get_loaded_integration, async_get_loaded_integration,
@@ -571,6 +574,11 @@ class DownloadSupportPackageView(HomeAssistantView):
"</details>\n\n" "</details>\n\n"
) )
markdown += await self._get_host_resources_markdown(hass)
if is_hassio(hass):
markdown += await self._get_addon_resources_markdown(hass)
log_handler = hass.data[DATA_CLOUD_LOG_HANDLER] log_handler = hass.data[DATA_CLOUD_LOG_HANDLER]
logs = "\n".join(await log_handler.get_logs(hass)) logs = "\n".join(await log_handler.get_logs(hass))
markdown += ( markdown += (
@@ -584,6 +592,103 @@ class DownloadSupportPackageView(HomeAssistantView):
return markdown return markdown
async def _get_host_resources_markdown(self, hass: HomeAssistant) -> str:
"""Get host resource usage markdown using psutil."""
def _collect_system_stats() -> dict[str, Any]:
"""Collect system stats."""
psutil_wrapper = ha_psutil.PsutilWrapper()
psutil_mod = psutil_wrapper.psutil
cpu_percent = psutil_mod.cpu_percent(interval=0.1)
memory = psutil_mod.virtual_memory()
disk = psutil_mod.disk_usage("/")
return {
"cpu_percent": cpu_percent,
"memory_total": memory.total,
"memory_used": memory.used,
"memory_available": memory.available,
"memory_percent": memory.percent,
"disk_total": disk.total,
"disk_used": disk.used,
"disk_free": disk.free,
"disk_percent": disk.percent,
}
markdown = ""
try:
stats = await hass.async_add_executor_job(_collect_system_stats)
markdown += "## Host resource usage\n\n"
markdown += "Resource | Value\n"
markdown += "--- | ---\n"
markdown += f"CPU usage | {stats['cpu_percent']}%\n"
memory_total_gb = round(stats["memory_total"] / (1024**3), 2)
memory_used_gb = round(stats["memory_used"] / (1024**3), 2)
memory_available_gb = round(stats["memory_available"] / (1024**3), 2)
markdown += f"Memory total | {memory_total_gb} GB\n"
markdown += (
f"Memory used | {memory_used_gb} GB ({stats['memory_percent']}%)\n"
)
markdown += f"Memory available | {memory_available_gb} GB\n"
disk_total_gb = round(stats["disk_total"] / (1024**3), 2)
disk_used_gb = round(stats["disk_used"] / (1024**3), 2)
disk_free_gb = round(stats["disk_free"] / (1024**3), 2)
markdown += f"Disk total | {disk_total_gb} GB\n"
markdown += f"Disk used | {disk_used_gb} GB ({stats['disk_percent']}%)\n"
markdown += f"Disk free | {disk_free_gb} GB\n"
markdown += "\n"
except Exception: # noqa: BLE001
# Broad exception catch for robustness in support package generation
markdown += "## Host resource usage\n\n"
markdown += "Unable to collect host resource information\n\n"
return markdown
async def _get_addon_resources_markdown(self, hass: HomeAssistant) -> str:
"""Get add-on resource usage markdown for hassio."""
markdown = ""
try:
supervisor_info = get_supervisor_info(hass) or {}
addons_stats = get_addons_stats(hass)
addons = supervisor_info.get("addons", [])
if addons:
markdown += "## Add-on resource usage\n\n"
markdown += "<details><summary>Add-on resources</summary>\n\n"
markdown += "Add-on | Version | State | CPU | Memory\n"
markdown += "--- | --- | --- | --- | ---\n"
for addon in addons:
slug = addon.get("slug", "unknown")
name = addon.get("name", slug)
version = addon.get("version", "unknown")
state = addon.get("state", "unknown")
addon_stats = addons_stats.get(slug, {})
cpu = addon_stats.get("cpu_percent")
memory = addon_stats.get("memory_percent")
cpu_str = f"{cpu}%" if cpu is not None else "N/A"
memory_str = f"{memory}%" if memory is not None else "N/A"
markdown += (
f"{name} | {version} | {state} | {cpu_str} | {memory_str}\n"
)
markdown += "\n</details>\n\n"
except Exception: # noqa: BLE001
# Broad exception catch for robustness in support package generation
markdown += "## Add-on resource usage\n\n"
markdown += "Unable to collect add-on resource information\n\n"
return markdown
async def get(self, request: web.Request) -> web.Response: async def get(self, request: web.Request) -> web.Response:
"""Download support package file.""" """Download support package file."""

View File

@@ -5,7 +5,8 @@
"alexa", "alexa",
"assist_pipeline", "assist_pipeline",
"backup", "backup",
"google_assistant" "google_assistant",
"hassio"
], ],
"codeowners": ["@home-assistant/cloud"], "codeowners": ["@home-assistant/cloud"],
"dependencies": ["auth", "http", "repairs", "webhook", "web_rtc"], "dependencies": ["auth", "http", "repairs", "webhook", "web_rtc"],
@@ -13,6 +14,6 @@
"integration_type": "system", "integration_type": "system",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["acme", "hass_nabucasa", "snitun"], "loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==1.9.0"], "requirements": ["hass-nabucasa==1.9.0", "psutil-home-assistant==0.0.1"],
"single_config_entry": true "single_config_entry": true
} }

1
requirements_all.txt generated
View File

@@ -1776,6 +1776,7 @@ prowlpy==1.1.1
# homeassistant.components.proxmoxve # homeassistant.components.proxmoxve
proxmoxer==2.0.1 proxmoxer==2.0.1
# homeassistant.components.cloud
# homeassistant.components.hardware # homeassistant.components.hardware
# homeassistant.components.recorder # homeassistant.components.recorder
# homeassistant.components.systemmonitor # homeassistant.components.systemmonitor

View File

@@ -1522,6 +1522,7 @@ prometheus-client==0.21.0
# homeassistant.components.prowl # homeassistant.components.prowl
prowlpy==1.1.1 prowlpy==1.1.1
# homeassistant.components.cloud
# homeassistant.components.hardware # homeassistant.components.hardware
# homeassistant.components.recorder # homeassistant.components.recorder
# homeassistant.components.systemmonitor # homeassistant.components.systemmonitor

View File

@@ -87,6 +87,18 @@
</details> </details>
## Host resource usage
Resource | Value
--- | ---
CPU usage | 25.5%
Memory total | 16.0 GB
Memory used | 8.0 GB (50.0%)
Memory available | 8.0 GB
Disk total | 500.0 GB
Disk used | 200.0 GB (40.0%)
Disk free | 300.0 GB
## Full logs ## Full logs
<details><summary>Logs</summary> <details><summary>Logs</summary>
@@ -181,6 +193,18 @@
</details> </details>
## Host resource usage
Resource | Value
--- | ---
CPU usage | 25.5%
Memory total | 16.0 GB
Memory used | 8.0 GB (50.0%)
Memory available | 8.0 GB
Disk total | 500.0 GB
Disk used | 200.0 GB (40.0%)
Disk free | 300.0 GB
## Full logs ## Full logs
<details><summary>Logs</summary> <details><summary>Logs</summary>
@@ -196,6 +220,252 @@
''' '''
# --- # ---
# name: test_download_support_package_hassio
'''
## System Information
version | core-2025.2.0
--- | ---
installation_type | Home Assistant OS
dev | False
hassio | True
docker | True
container_arch | aarch64
user | root
virtualenv | False
python_version | 3.13.1
os_name | Linux
os_version | 6.12.9
arch | aarch64
timezone | US/Pacific
config_dir | config
## Active Integrations
Built-in integrations: 23
Custom integrations: 1
<details><summary>Built-in integrations</summary>
Domain | Name
--- | ---
ai_task | AI Task
auth | Auth
binary_sensor | Binary Sensor
cloud | Home Assistant Cloud
cloud.ai_task | Unknown
cloud.binary_sensor | Unknown
cloud.conversation | Unknown
cloud.stt | Unknown
cloud.tts | Unknown
conversation | Conversation
ffmpeg | FFmpeg
hassio | hassio
homeassistant | Home Assistant Core Integration
http | HTTP
intent | Intent
media_source | Media Source
mock_no_info_integration | mock_no_info_integration
repairs | Repairs
stt | Speech-to-text (STT)
system_health | System Health
tts | Text-to-speech (TTS)
web_rtc | WebRTC
webhook | Webhook
</details>
<details><summary>Custom integrations</summary>
Domain | Name | Version | Documentation
--- | --- | --- | ---
test | Test Components | 1.2.3 | http://example.com
</details>
<details><summary>hassio</summary>
host_os | Home Assistant OS 14.0
--- | ---
update_channel | stable
supervisor_version | supervisor-2025.01.0
agent_version | 1.6.0
docker_version | 27.4.1
disk_total | 128.5 GB
disk_used | 45.2 GB
healthy | True
supported | True
host_connectivity | True
supervisor_connectivity | True
board | green
supervisor_api | ok
version_api | ok
installed_addons | Mosquitto broker (6.4.1), Samba share (12.3.2), Visual Studio Code (5.21.2)
</details>
<details><summary>mock_no_info_integration</summary>
No information available
</details>
<details><summary>cloud</summary>
logged_in | True
--- | ---
subscription_expiration | 2025-01-17T11:19:31+00:00
relayer_connected | True
relayer_region | xx-earth-616
remote_enabled | True
remote_connected | False
alexa_enabled | True
google_enabled | False
cloud_ice_servers_enabled | True
remote_server | us-west-1
certificate_status | ready
instance_id | 12345678901234567890
can_reach_cert_server | Exception: Unexpected exception
can_reach_cloud_auth | Failed: unreachable
can_reach_cloud | ok
</details>
## Host resource usage
Resource | Value
--- | ---
CPU usage | 25.5%
Memory total | 16.0 GB
Memory used | 8.0 GB (50.0%)
Memory available | 8.0 GB
Disk total | 500.0 GB
Disk used | 200.0 GB (40.0%)
Disk free | 300.0 GB
## Add-on resource usage
<details><summary>Add-on resources</summary>
Add-on | Version | State | CPU | Memory
--- | --- | --- | --- | ---
Mosquitto broker | 6.4.1 | started | 0.5% | 1.2%
Samba share | 12.3.2 | started | 0.1% | 0.8%
Visual Studio Code | 5.21.2 | stopped | N/A | N/A
</details>
## Full logs
<details><summary>Logs</summary>
```logs
2025-02-10 12:00:00.000 INFO (MainThread) [hass_nabucasa.iot] Hass nabucasa log
2025-02-10 12:00:00.000 WARNING (MainThread) [snitun.utils.aiohttp_client] Snitun log
2025-02-10 12:00:00.000 ERROR (MainThread) [homeassistant.components.cloud.client] Cloud log
```
</details>
'''
# ---
# name: test_download_support_package_host_resources
'''
## System Information
version | core-2025.2.0
--- | ---
installation_type | Home Assistant Container
dev | False
hassio | False
docker | True
container_arch | x86_64
user | root
virtualenv | False
python_version | 3.13.1
os_name | Linux
os_version | 6.12.9
arch | x86_64
timezone | US/Pacific
config_dir | config
## Active Integrations
Built-in integrations: 21
Custom integrations: 0
<details><summary>Built-in integrations</summary>
Domain | Name
--- | ---
ai_task | AI Task
auth | Auth
binary_sensor | Binary Sensor
cloud | Home Assistant Cloud
cloud.ai_task | Unknown
cloud.binary_sensor | Unknown
cloud.conversation | Unknown
cloud.stt | Unknown
cloud.tts | Unknown
conversation | Conversation
ffmpeg | FFmpeg
homeassistant | Home Assistant Core Integration
http | HTTP
intent | Intent
media_source | Media Source
repairs | Repairs
stt | Speech-to-text (STT)
system_health | System Health
tts | Text-to-speech (TTS)
web_rtc | WebRTC
webhook | Webhook
</details>
<details><summary>cloud</summary>
logged_in | True
--- | ---
subscription_expiration | 2025-01-17T11:19:31+00:00
relayer_connected | True
relayer_region | xx-earth-616
remote_enabled | True
remote_connected | False
alexa_enabled | True
google_enabled | False
cloud_ice_servers_enabled | True
remote_server | us-west-1
certificate_status | ready
instance_id | 12345678901234567890
can_reach_cert_server | Exception: Unexpected exception
can_reach_cloud_auth | Failed: unreachable
can_reach_cloud | ok
</details>
## Host resource usage
Resource | Value
--- | ---
CPU usage | 25.5%
Memory total | 16.0 GB
Memory used | 8.0 GB (50.0%)
Memory available | 8.0 GB
Disk total | 500.0 GB
Disk used | 200.0 GB (40.0%)
Disk free | 300.0 GB
## Full logs
<details><summary>Logs</summary>
```logs
2025-02-10 12:00:00.000 INFO (MainThread) [hass_nabucasa.iot] Hass nabucasa log
```
</details>
'''
# ---
# name: test_download_support_package_integration_load_error # name: test_download_support_package_integration_load_error
''' '''
## System Information ## System Information
@@ -246,6 +516,18 @@
</details> </details>
## Host resource usage
Resource | Value
--- | ---
CPU usage | 25.5%
Memory total | 16.0 GB
Memory used | 8.0 GB (50.0%)
Memory available | 8.0 GB
Disk total | 500.0 GB
Disk used | 200.0 GB (40.0%)
Disk free | 300.0 GB
## Full logs ## Full logs
<details><summary>Logs</summary> <details><summary>Logs</summary>

View File

@@ -1,6 +1,6 @@
"""Tests for the HTTP API for the cloud component.""" """Tests for the HTTP API for the cloud component."""
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine, Generator
from copy import deepcopy from copy import deepcopy
import datetime import datetime
from http import HTTPStatus from http import HTTPStatus
@@ -114,6 +114,36 @@ PIPELINE_DATA_OTHER = {
SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/payments/subscription_info" SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/payments/subscription_info"
@pytest.fixture
def mock_psutil_wrapper() -> Generator[MagicMock]:
"""Fixture to mock psutil for support package tests."""
mock_memory = MagicMock()
mock_memory.total = 16 * 1024**3 # 16 GB
mock_memory.used = 8 * 1024**3 # 8 GB
mock_memory.available = 8 * 1024**3 # 8 GB
mock_memory.percent = 50.0
mock_disk = MagicMock()
mock_disk.total = 500 * 1024**3 # 500 GB
mock_disk.used = 200 * 1024**3 # 200 GB
mock_disk.free = 300 * 1024**3 # 300 GB
mock_disk.percent = 40.0
mock_psutil = MagicMock()
mock_psutil.cpu_percent = MagicMock(return_value=25.5)
mock_psutil.virtual_memory = MagicMock(return_value=mock_memory)
mock_psutil.disk_usage = MagicMock(return_value=mock_disk)
mock_wrapper = MagicMock()
mock_wrapper.psutil = mock_psutil
with patch(
"homeassistant.components.cloud.http_api.ha_psutil.PsutilWrapper",
return_value=mock_wrapper,
):
yield mock_wrapper
@pytest.fixture(name="setup_cloud") @pytest.fixture(name="setup_cloud")
async def setup_cloud_fixture(hass: HomeAssistant, cloud: MagicMock) -> None: async def setup_cloud_fixture(hass: HomeAssistant, cloud: MagicMock) -> None:
"""Fixture that sets up cloud.""" """Fixture that sets up cloud."""
@@ -1846,7 +1876,7 @@ async def test_logout_view_dispatch_event(
@patch("homeassistant.components.cloud.helpers.FixedSizeQueueLogHandler.MAX_RECORDS", 3) @patch("homeassistant.components.cloud.helpers.FixedSizeQueueLogHandler.MAX_RECORDS", 3)
@pytest.mark.usefixtures("enable_custom_integrations") @pytest.mark.usefixtures("enable_custom_integrations", "mock_psutil_wrapper")
async def test_download_support_package( async def test_download_support_package(
hass: HomeAssistant, hass: HomeAssistant,
cloud: MagicMock, cloud: MagicMock,
@@ -1959,7 +1989,7 @@ async def test_download_support_package(
assert await req.text() == snapshot assert await req.text() == snapshot
@pytest.mark.usefixtures("enable_custom_integrations") @pytest.mark.usefixtures("enable_custom_integrations", "mock_psutil_wrapper")
async def test_download_support_package_custom_components_error( async def test_download_support_package_custom_components_error(
hass: HomeAssistant, hass: HomeAssistant,
cloud: MagicMock, cloud: MagicMock,
@@ -1986,7 +2016,7 @@ async def test_download_support_package_custom_components_error(
async def mock_empty_info(hass: HomeAssistant) -> dict[str, Any]: async def mock_empty_info(hass: HomeAssistant) -> dict[str, Any]:
return {} return {}
register.async_register_info(mock_empty_info, "/config/mock_integration") register.async_register_info(mock_empty_info, "/mock_integration")
mock_platform( mock_platform(
hass, hass,
@@ -2071,7 +2101,7 @@ async def test_download_support_package_custom_components_error(
assert await req.text() == snapshot assert await req.text() == snapshot
@pytest.mark.usefixtures("enable_custom_integrations") @pytest.mark.usefixtures("enable_custom_integrations", "mock_psutil_wrapper")
async def test_download_support_package_integration_load_error( async def test_download_support_package_integration_load_error(
hass: HomeAssistant, hass: HomeAssistant,
cloud: MagicMock, cloud: MagicMock,
@@ -2098,7 +2128,7 @@ async def test_download_support_package_integration_load_error(
async def mock_empty_info(hass: HomeAssistant) -> dict[str, Any]: async def mock_empty_info(hass: HomeAssistant) -> dict[str, Any]:
return {} return {}
register.async_register_info(mock_empty_info, "/config/mock_integration") register.async_register_info(mock_empty_info, "/mock_integration")
mock_platform( mock_platform(
hass, hass,
@@ -2188,6 +2218,277 @@ async def test_download_support_package_integration_load_error(
assert await req.text() == snapshot assert await req.text() == snapshot
@patch("homeassistant.components.cloud.helpers.FixedSizeQueueLogHandler.MAX_RECORDS", 3)
@pytest.mark.usefixtures("enable_custom_integrations", "mock_psutil_wrapper")
async def test_download_support_package_hassio(
hass: HomeAssistant,
cloud: MagicMock,
set_cloud_prefs: Callable[[dict[str, Any]], Coroutine[Any, Any, None]],
hass_client: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test downloading a support package file with hassio resources."""
aioclient_mock.get("https://cloud.bla.com/status", text="")
aioclient_mock.get(
"https://cert-server/directory", exc=Exception("Unexpected exception")
)
aioclient_mock.get(
"https://cognito-idp.us-east-1.amazonaws.com/AAAA/.well-known/jwks.json",
exc=aiohttp.ClientError,
)
def async_register_hassio_platform(
hass: HomeAssistant,
register: system_health.SystemHealthRegistration,
) -> None:
async def mock_hassio_info(hass: HomeAssistant) -> dict[str, Any]:
return {
"host_os": "Home Assistant OS 14.0",
"update_channel": "stable",
"supervisor_version": "supervisor-2025.01.0",
"agent_version": "1.6.0",
"docker_version": "27.4.1",
"disk_total": "128.5 GB",
"disk_used": "45.2 GB",
"healthy": True,
"supported": True,
"host_connectivity": True,
"supervisor_connectivity": True,
"board": "green",
"supervisor_api": "ok",
"version_api": "ok",
"installed_addons": "Mosquitto broker (6.4.1), Samba share (12.3.2), Visual Studio Code (5.21.2)",
}
register.async_register_info(mock_hassio_info, "/hassio/system")
mock_platform(
hass,
"hassio.system_health",
MagicMock(async_register=async_register_hassio_platform),
)
hass.config.components.add("hassio")
def async_register_mock_platform(
hass: HomeAssistant,
register: system_health.SystemHealthRegistration,
) -> None:
async def mock_empty_info(hass: HomeAssistant) -> dict[str, Any]:
return {}
register.async_register_info(mock_empty_info, "/config/mock_integration")
mock_platform(
hass,
"mock_no_info_integration.system_health",
MagicMock(async_register=async_register_mock_platform),
)
hass.config.components.add("mock_no_info_integration")
hass.config.components.add("test")
assert await async_setup_component(hass, "system_health", {})
with patch("uuid.UUID.hex", new_callable=PropertyMock) as hexmock:
hexmock.return_value = "12345678901234567890"
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
"user_pool_id": "AAAA",
"region": "us-east-1",
"acme_server": "cert-server",
"relayer_server": "cloud.bla.com",
},
},
)
await hass.async_block_till_done()
await cloud.login("test-user", "test-pass")
cloud.remote.snitun_server = "us-west-1"
cloud.remote.certificate_status = CertificateStatus.READY
cloud.expiration_date = dt_util.parse_datetime("2025-01-17T11:19:31.0+00:00")
await cloud.client.async_system_message({"region": "xx-earth-616"})
await set_cloud_prefs(
{
"alexa_enabled": True,
"google_enabled": False,
"remote_enabled": True,
"cloud_ice_servers_enabled": True,
}
)
now = dt_util.utcnow()
tz = now.astimezone().tzinfo
freezer.move_to(datetime.datetime(2025, 2, 10, 12, 0, 0, tzinfo=tz))
logging.getLogger("hass_nabucasa.iot").info(
"This message will be dropped since this test patches MAX_RECORDS"
)
logging.getLogger("hass_nabucasa.iot").info("Hass nabucasa log")
logging.getLogger("snitun.utils.aiohttp_client").warning("Snitun log")
logging.getLogger("homeassistant.components.cloud.client").error("Cloud log")
freezer.move_to(now)
cloud_client = await hass_client()
with (
patch.object(hass.config, "config_dir", new="config"),
patch(
"homeassistant.components.homeassistant.system_health.system_info.async_get_system_info",
return_value={
"installation_type": "Home Assistant OS",
"version": "2025.2.0",
"dev": False,
"hassio": True,
"virtualenv": False,
"python_version": "3.13.1",
"docker": True,
"container_arch": "aarch64",
"arch": "aarch64",
"timezone": "US/Pacific",
"os_name": "Linux",
"os_version": "6.12.9",
"user": "root",
},
),
patch(
"homeassistant.components.cloud.http_api.get_supervisor_info",
return_value={
"addons": [
{
"slug": "core_mosquitto",
"name": "Mosquitto broker",
"version": "6.4.1",
"state": "started",
},
{
"slug": "core_samba",
"name": "Samba share",
"version": "12.3.2",
"state": "started",
},
{
"slug": "a0d7b954_vscode",
"name": "Visual Studio Code",
"version": "5.21.2",
"state": "stopped",
},
],
},
),
patch(
"homeassistant.components.cloud.http_api.get_addons_stats",
return_value={
"core_mosquitto": {
"cpu_percent": 0.5,
"memory_percent": 1.2,
},
"core_samba": {
"cpu_percent": 0.1,
"memory_percent": 0.8,
},
# No stats for vscode (stopped)
},
),
):
req = await cloud_client.get("/api/cloud/support_package")
assert req.status == HTTPStatus.OK
assert await req.text() == snapshot
@patch("homeassistant.components.cloud.helpers.FixedSizeQueueLogHandler.MAX_RECORDS", 3)
@pytest.mark.usefixtures("mock_psutil_wrapper")
async def test_download_support_package_host_resources(
hass: HomeAssistant,
cloud: MagicMock,
set_cloud_prefs: Callable[[dict[str, Any]], Coroutine[Any, Any, None]],
hass_client: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test downloading a support package file with psutil host resources (non-hassio)."""
aioclient_mock.get("https://cloud.bla.com/status", text="")
aioclient_mock.get(
"https://cert-server/directory", exc=Exception("Unexpected exception")
)
aioclient_mock.get(
"https://cognito-idp.us-east-1.amazonaws.com/AAAA/.well-known/jwks.json",
exc=aiohttp.ClientError,
)
assert await async_setup_component(hass, "system_health", {})
with patch("uuid.UUID.hex", new_callable=PropertyMock) as hexmock:
hexmock.return_value = "12345678901234567890"
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
"user_pool_id": "AAAA",
"region": "us-east-1",
"acme_server": "cert-server",
"relayer_server": "cloud.bla.com",
},
},
)
await hass.async_block_till_done()
await cloud.login("test-user", "test-pass")
cloud.remote.snitun_server = "us-west-1"
cloud.remote.certificate_status = CertificateStatus.READY
cloud.expiration_date = dt_util.parse_datetime("2025-01-17T11:19:31.0+00:00")
await cloud.client.async_system_message({"region": "xx-earth-616"})
await set_cloud_prefs(
{
"alexa_enabled": True,
"google_enabled": False,
"remote_enabled": True,
"cloud_ice_servers_enabled": True,
}
)
now = dt_util.utcnow()
tz = now.astimezone().tzinfo
freezer.move_to(datetime.datetime(2025, 2, 10, 12, 0, 0, tzinfo=tz))
logging.getLogger("hass_nabucasa.iot").info("Hass nabucasa log")
freezer.move_to(now)
cloud_client = await hass_client()
with (
patch.object(hass.config, "config_dir", new="config"),
patch(
"homeassistant.components.homeassistant.system_health.system_info.async_get_system_info",
return_value={
"installation_type": "Home Assistant Container",
"version": "2025.2.0",
"dev": False,
"hassio": False,
"virtualenv": False,
"python_version": "3.13.1",
"docker": True,
"container_arch": "x86_64",
"arch": "x86_64",
"timezone": "US/Pacific",
"os_name": "Linux",
"os_version": "6.12.9",
"user": "root",
},
),
):
req = await cloud_client.get("/api/cloud/support_package")
assert req.status == HTTPStatus.OK
assert await req.text() == snapshot
async def test_websocket_ice_servers( async def test_websocket_ice_servers(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,