mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Issue warning if glances server version is 2 (#105887)
* Issue warning if glances server version is 2 * Auto detect api version * Apply suggestions * Add HA version deprecation * Apply suggestions from code review * update config flow tests * Fix breaks in ha version --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
890615bb92
commit
2331f89936
@ -1,13 +1,34 @@
|
|||||||
"""The Glances component."""
|
"""The Glances component."""
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from glances_api import Glances
|
from glances_api import Glances
|
||||||
|
from glances_api.exceptions import (
|
||||||
|
GlancesApiAuthorizationError,
|
||||||
|
GlancesApiError,
|
||||||
|
GlancesApiNoDataAvailable,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL, Platform
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import (
|
||||||
|
ConfigEntryAuthFailed,
|
||||||
|
ConfigEntryError,
|
||||||
|
ConfigEntryNotReady,
|
||||||
|
HomeAssistantError,
|
||||||
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.httpx_client import get_async_client
|
from homeassistant.helpers.httpx_client import get_async_client
|
||||||
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import GlancesDataUpdateCoordinator
|
from .coordinator import GlancesDataUpdateCoordinator
|
||||||
@ -16,10 +37,19 @@ PLATFORMS = [Platform.SENSOR]
|
|||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Set up Glances from config entry."""
|
"""Set up Glances from config entry."""
|
||||||
api = get_api(hass, dict(config_entry.data))
|
try:
|
||||||
|
api = await get_api(hass, dict(config_entry.data))
|
||||||
|
except GlancesApiAuthorizationError as err:
|
||||||
|
raise ConfigEntryAuthFailed from err
|
||||||
|
except GlancesApiError as err:
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
|
except ServerVersionMismatch as err:
|
||||||
|
raise ConfigEntryError(err) from err
|
||||||
coordinator = GlancesDataUpdateCoordinator(hass, config_entry, api)
|
coordinator = GlancesDataUpdateCoordinator(hass, config_entry, api)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
@ -39,8 +69,38 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
def get_api(hass: HomeAssistant, entry_data: dict[str, Any]) -> Glances:
|
async def get_api(hass: HomeAssistant, entry_data: dict[str, Any]) -> Glances:
|
||||||
"""Return the api from glances_api."""
|
"""Return the api from glances_api."""
|
||||||
entry_data.pop(CONF_NAME, None)
|
|
||||||
httpx_client = get_async_client(hass, verify_ssl=entry_data[CONF_VERIFY_SSL])
|
httpx_client = get_async_client(hass, verify_ssl=entry_data[CONF_VERIFY_SSL])
|
||||||
return Glances(httpx_client=httpx_client, **entry_data)
|
for version in (3, 2):
|
||||||
|
api = Glances(
|
||||||
|
host=entry_data[CONF_HOST],
|
||||||
|
port=entry_data[CONF_PORT],
|
||||||
|
version=version,
|
||||||
|
ssl=entry_data[CONF_SSL],
|
||||||
|
username=entry_data.get(CONF_USERNAME),
|
||||||
|
password=entry_data.get(CONF_PASSWORD),
|
||||||
|
httpx_client=httpx_client,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await api.get_ha_sensor_data()
|
||||||
|
except GlancesApiNoDataAvailable as err:
|
||||||
|
_LOGGER.debug("Failed to connect to Glances API v%s: %s", version, err)
|
||||||
|
continue
|
||||||
|
if version == 2:
|
||||||
|
async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"deprecated_version",
|
||||||
|
breaks_in_ha_version="2024.8.0",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
translation_key="deprecated_version",
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Connected to Glances API v%s", version)
|
||||||
|
return api
|
||||||
|
raise ServerVersionMismatch("Could not connect to Glances API version 2 or 3")
|
||||||
|
|
||||||
|
|
||||||
|
class ServerVersionMismatch(HomeAssistantError):
|
||||||
|
"""Raise exception if we fail to connect to Glances API."""
|
||||||
|
@ -21,15 +21,8 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
from . import get_api
|
from . import ServerVersionMismatch, get_api
|
||||||
from .const import (
|
from .const import DEFAULT_HOST, DEFAULT_PORT, DOMAIN
|
||||||
CONF_VERSION,
|
|
||||||
DEFAULT_HOST,
|
|
||||||
DEFAULT_PORT,
|
|
||||||
DEFAULT_VERSION,
|
|
||||||
DOMAIN,
|
|
||||||
SUPPORTED_VERSIONS,
|
|
||||||
)
|
|
||||||
|
|
||||||
DATA_SCHEMA = vol.Schema(
|
DATA_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -37,7 +30,6 @@ DATA_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_USERNAME): str,
|
vol.Optional(CONF_USERNAME): str,
|
||||||
vol.Optional(CONF_PASSWORD): str,
|
vol.Optional(CONF_PASSWORD): str,
|
||||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||||
vol.Required(CONF_VERSION, default=DEFAULT_VERSION): vol.In(SUPPORTED_VERSIONS),
|
|
||||||
vol.Optional(CONF_SSL, default=False): bool,
|
vol.Optional(CONF_SSL, default=False): bool,
|
||||||
vol.Optional(CONF_VERIFY_SSL, default=False): bool,
|
vol.Optional(CONF_VERIFY_SSL, default=False): bool,
|
||||||
}
|
}
|
||||||
@ -65,9 +57,8 @@ class GlancesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
assert self._reauth_entry
|
assert self._reauth_entry
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
user_input = {**self._reauth_entry.data, **user_input}
|
user_input = {**self._reauth_entry.data, **user_input}
|
||||||
api = get_api(self.hass, user_input)
|
|
||||||
try:
|
try:
|
||||||
await api.get_ha_sensor_data()
|
await get_api(self.hass, user_input)
|
||||||
except GlancesApiAuthorizationError:
|
except GlancesApiAuthorizationError:
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except GlancesApiConnectionError:
|
except GlancesApiConnectionError:
|
||||||
@ -101,12 +92,11 @@ class GlancesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._async_abort_entries_match(
|
self._async_abort_entries_match(
|
||||||
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
|
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
|
||||||
)
|
)
|
||||||
api = get_api(self.hass, user_input)
|
|
||||||
try:
|
try:
|
||||||
await api.get_ha_sensor_data()
|
await get_api(self.hass, user_input)
|
||||||
except GlancesApiAuthorizationError:
|
except GlancesApiAuthorizationError:
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except GlancesApiConnectionError:
|
except (GlancesApiConnectionError, ServerVersionMismatch):
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
else:
|
else:
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
|
@ -8,9 +8,6 @@ CONF_VERSION = "version"
|
|||||||
|
|
||||||
DEFAULT_HOST = "localhost"
|
DEFAULT_HOST = "localhost"
|
||||||
DEFAULT_PORT = 61208
|
DEFAULT_PORT = 61208
|
||||||
DEFAULT_VERSION = 3
|
|
||||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
SUPPORTED_VERSIONS = [2, 3]
|
|
||||||
|
|
||||||
CPU_ICON = f"mdi:cpu-{64 if sys.maxsize > 2**32 else 32}-bit"
|
CPU_ICON = f"mdi:cpu-{64 if sys.maxsize > 2**32 else 32}-bit"
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]",
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
"port": "[%key:common::config_flow::data::port%]",
|
"port": "[%key:common::config_flow::data::port%]",
|
||||||
"version": "Glances API Version (2 or 3)",
|
|
||||||
"ssl": "[%key:common::config_flow::data::ssl%]",
|
"ssl": "[%key:common::config_flow::data::ssl%]",
|
||||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||||
},
|
},
|
||||||
@ -30,5 +29,11 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"issues": {
|
||||||
|
"deprecated_version": {
|
||||||
|
"title": "Glances servers with version 2 is deprecated",
|
||||||
|
"description": "Glances servers with version 2 is deprecated and will not be supported in future versions of HA. It is recommended to update your server to Glances version 3 then reload the integration."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ MOCK_USER_INPUT: dict[str, Any] = {
|
|||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"username": "username",
|
"username": "username",
|
||||||
"password": "password",
|
"password": "password",
|
||||||
"version": 3,
|
|
||||||
"port": 61208,
|
"port": 61208,
|
||||||
"ssl": False,
|
"ssl": False,
|
||||||
"verify_ssl": True,
|
"verify_ssl": True,
|
||||||
|
@ -4,6 +4,7 @@ from unittest.mock import MagicMock
|
|||||||
from glances_api.exceptions import (
|
from glances_api.exceptions import (
|
||||||
GlancesApiAuthorizationError,
|
GlancesApiAuthorizationError,
|
||||||
GlancesApiConnectionError,
|
GlancesApiConnectionError,
|
||||||
|
GlancesApiNoDataAvailable,
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
|||||||
[
|
[
|
||||||
(GlancesApiAuthorizationError, "invalid_auth"),
|
(GlancesApiAuthorizationError, "invalid_auth"),
|
||||||
(GlancesApiConnectionError, "cannot_connect"),
|
(GlancesApiConnectionError, "cannot_connect"),
|
||||||
|
(GlancesApiNoDataAvailable, "cannot_connect"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_form_fails(
|
async def test_form_fails(
|
||||||
@ -54,7 +56,7 @@ async def test_form_fails(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test flow fails when api exception is raised."""
|
"""Test flow fails when api exception is raised."""
|
||||||
|
|
||||||
mock_api.return_value.get_ha_sensor_data.side_effect = [error, HA_SENSOR_DATA]
|
mock_api.return_value.get_ha_sensor_data.side_effect = error
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
glances.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
glances.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
@ -65,12 +67,6 @@ async def test_form_fails(
|
|||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["errors"] == {"base": message}
|
assert result["errors"] == {"base": message}
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
|
||||||
result["flow_id"], user_input=MOCK_USER_INPUT
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
|
||||||
|
|
||||||
|
|
||||||
async def test_form_already_configured(hass: HomeAssistant) -> None:
|
async def test_form_already_configured(hass: HomeAssistant) -> None:
|
||||||
"""Test host is already configured."""
|
"""Test host is already configured."""
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
"""Tests for Glances integration."""
|
"""Tests for Glances integration."""
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
from glances_api.exceptions import (
|
from glances_api.exceptions import (
|
||||||
GlancesApiAuthorizationError,
|
GlancesApiAuthorizationError,
|
||||||
GlancesApiConnectionError,
|
GlancesApiConnectionError,
|
||||||
|
GlancesApiNoDataAvailable,
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.glances.const import DOMAIN
|
from homeassistant.components.glances.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import issue_registry as ir
|
||||||
|
|
||||||
from . import MOCK_USER_INPUT
|
from . import HA_SENSOR_DATA, MOCK_USER_INPUT
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -27,11 +29,34 @@ async def test_successful_config_entry(hass: HomeAssistant) -> None:
|
|||||||
assert entry.state == ConfigEntryState.LOADED
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entry_deprecated_version(
|
||||||
|
hass: HomeAssistant, issue_registry: ir.IssueRegistry, mock_api: AsyncMock
|
||||||
|
) -> None:
|
||||||
|
"""Test creating an issue if glances server is version 2."""
|
||||||
|
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mock_api.return_value.get_ha_sensor_data.side_effect = [
|
||||||
|
GlancesApiNoDataAvailable("endpoint: 'all' is not valid"),
|
||||||
|
HA_SENSOR_DATA,
|
||||||
|
HA_SENSOR_DATA,
|
||||||
|
]
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
|
||||||
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
issue = issue_registry.async_get_issue(DOMAIN, "deprecated_version")
|
||||||
|
assert issue is not None
|
||||||
|
assert issue.severity == ir.IssueSeverity.WARNING
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("error", "entry_state"),
|
("error", "entry_state"),
|
||||||
[
|
[
|
||||||
(GlancesApiAuthorizationError, ConfigEntryState.SETUP_ERROR),
|
(GlancesApiAuthorizationError, ConfigEntryState.SETUP_ERROR),
|
||||||
(GlancesApiConnectionError, ConfigEntryState.SETUP_RETRY),
|
(GlancesApiConnectionError, ConfigEntryState.SETUP_RETRY),
|
||||||
|
(GlancesApiNoDataAvailable, ConfigEntryState.SETUP_ERROR),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_setup_error(
|
async def test_setup_error(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user