diff --git a/homeassistant/components/glances/__init__.py b/homeassistant/components/glances/__init__.py index 44460ed1928b..ebeff5aa20e0 100644 --- a/homeassistant/components/glances/__init__.py +++ b/homeassistant/components/glances/__init__.py @@ -26,8 +26,9 @@ from homeassistant.exceptions import ( ConfigEntryNotReady, HomeAssistantError, ) -from homeassistant.helpers.httpx_client import get_async_client +from homeassistant.helpers.httpx_client import create_async_httpx_client +from .const import DEFAULT_TIMEOUT from .coordinator import GlancesConfigEntry, GlancesDataUpdateCoordinator PLATFORMS = [Platform.SENSOR] @@ -65,7 +66,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: GlancesConfigEntry) -> async def get_api(hass: HomeAssistant, entry_data: dict[str, Any]) -> Glances: """Return the api from glances_api.""" - httpx_client = get_async_client(hass, verify_ssl=entry_data[CONF_VERIFY_SSL]) + # The shared httpx client cannot be used because its 5-second timeout + # is too short for slow Glances hosts. + httpx_client = create_async_httpx_client( + hass, verify_ssl=entry_data[CONF_VERIFY_SSL], timeout=DEFAULT_TIMEOUT + ) for version in (4, 3): api = Glances( host=entry_data[CONF_HOST], diff --git a/homeassistant/components/glances/const.py b/homeassistant/components/glances/const.py index 6831ccb9e3b6..63e0bcb8c0fb 100644 --- a/homeassistant/components/glances/const.py +++ b/homeassistant/components/glances/const.py @@ -9,5 +9,6 @@ CONF_VERSION = "version" DEFAULT_HOST = "localhost" DEFAULT_PORT = 61208 DEFAULT_SCAN_INTERVAL = timedelta(seconds=60) +DEFAULT_TIMEOUT = 30 CPU_ICON = f"mdi:cpu-{64 if sys.maxsize > 2**32 else 32}-bit" diff --git a/homeassistant/components/glances/coordinator.py b/homeassistant/components/glances/coordinator.py index d95ac1310bb8..d2d5383c3f25 100644 --- a/homeassistant/components/glances/coordinator.py +++ b/homeassistant/components/glances/coordinator.py @@ -47,7 +47,7 @@ class GlancesDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): except exceptions.GlancesApiAuthorizationError as err: raise ConfigEntryAuthFailed from err except exceptions.GlancesApiError as err: - raise UpdateFailed from err + raise UpdateFailed(str(err)) from err # Update computed values uptime: datetime | None = None up_duration: timedelta | None = None diff --git a/tests/components/glances/test_init.py b/tests/components/glances/test_init.py index 16d4d9d371bb..517904055d77 100644 --- a/tests/components/glances/test_init.py +++ b/tests/components/glances/test_init.py @@ -1,7 +1,8 @@ """Tests for Glances integration.""" -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch +from freezegun.api import FrozenDateTimeFactory from glances_api.exceptions import ( GlancesApiAuthorizationError, GlancesApiConnectionError, @@ -9,13 +10,18 @@ from glances_api.exceptions import ( ) import pytest -from homeassistant.components.glances.const import DOMAIN +from homeassistant.components.glances.const import ( + DEFAULT_SCAN_INTERVAL, + DEFAULT_TIMEOUT, + DOMAIN, +) from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from . import MOCK_USER_INPUT -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed async def test_successful_config_entry(hass: HomeAssistant) -> None: @@ -53,6 +59,49 @@ async def test_setup_error( assert entry.state is entry_state +async def test_entity_unavailable_on_update_error( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_api: MagicMock, +) -> None: + """Test that entities become unavailable when a data update fails.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert entry.state is ConfigEntryState.LOADED + assert hass.states.get("sensor.0_0_0_0_ssl_disk_used").state != STATE_UNAVAILABLE + + mock_api.return_value.get_ha_sensor_data.side_effect = GlancesApiConnectionError( + "Connection to http://localhost:61209/api/4/all failed" + ) + freezer.tick(DEFAULT_SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get("sensor.0_0_0_0_ssl_disk_used").state == STATE_UNAVAILABLE + + +async def test_dedicated_httpx_client_uses_timeout( + hass: HomeAssistant, +) -> None: + """The integration's dedicated httpx client uses DEFAULT_TIMEOUT.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.glances.create_async_httpx_client" + ) as mock_create: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert entry.state is ConfigEntryState.LOADED + + kwargs = mock_create.call_args.kwargs + assert kwargs["timeout"] == DEFAULT_TIMEOUT + assert kwargs["verify_ssl"] == MOCK_USER_INPUT["verify_ssl"] + + async def test_unload_entry(hass: HomeAssistant) -> None: """Test removing Glances.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)