Fail recorder setup with unsupported dialect or version (#70888)
This commit is contained in:
parent
f3c582815c
commit
037f6947d8
4 changed files with 47 additions and 34 deletions
|
@ -56,6 +56,7 @@ from .models import (
|
||||||
StatisticData,
|
StatisticData,
|
||||||
StatisticMetaData,
|
StatisticMetaData,
|
||||||
StatisticsRuns,
|
StatisticsRuns,
|
||||||
|
UnsupportedDialect,
|
||||||
process_timestamp,
|
process_timestamp,
|
||||||
)
|
)
|
||||||
from .pool import POOL_SIZE, MutexPool, RecorderPool
|
from .pool import POOL_SIZE, MutexPool, RecorderPool
|
||||||
|
@ -606,6 +607,8 @@ class Recorder(threading.Thread):
|
||||||
try:
|
try:
|
||||||
self._setup_connection()
|
self._setup_connection()
|
||||||
return migration.get_schema_version(self.get_session)
|
return migration.get_schema_version(self.get_session)
|
||||||
|
except UnsupportedDialect:
|
||||||
|
break
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"Error during connection setup: %s (retrying in %s seconds)",
|
"Error during connection setup: %s (retrying in %s seconds)",
|
||||||
|
|
|
@ -123,6 +123,10 @@ EVENT_ORIGIN_ORDER = [EventOrigin.local, EventOrigin.remote]
|
||||||
EVENT_ORIGIN_TO_IDX = {origin: idx for idx, origin in enumerate(EVENT_ORIGIN_ORDER)}
|
EVENT_ORIGIN_TO_IDX = {origin: idx for idx, origin in enumerate(EVENT_ORIGIN_ORDER)}
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedDialect(Exception):
|
||||||
|
"""The dialect or its version is not supported."""
|
||||||
|
|
||||||
|
|
||||||
class Events(Base): # type: ignore[misc,valid-type]
|
class Events(Base): # type: ignore[misc,valid-type]
|
||||||
"""Event history data."""
|
"""Event history data."""
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ from .models import (
|
||||||
TABLE_SCHEMA_CHANGES,
|
TABLE_SCHEMA_CHANGES,
|
||||||
TABLES_TO_CHECK,
|
TABLES_TO_CHECK,
|
||||||
RecorderRuns,
|
RecorderRuns,
|
||||||
|
UnsupportedDialect,
|
||||||
process_timestamp,
|
process_timestamp,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -328,29 +329,31 @@ def query_on_connection(dbapi_connection: Any, statement: str) -> Any:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _warn_unsupported_dialect(dialect_name: str) -> None:
|
def _fail_unsupported_dialect(dialect_name: str) -> None:
|
||||||
"""Warn about unsupported database version."""
|
"""Warn about unsupported database version."""
|
||||||
_LOGGER.warning(
|
_LOGGER.error(
|
||||||
"Database %s is not supported; Home Assistant supports %s. "
|
"Database %s is not supported; Home Assistant supports %s. "
|
||||||
"Starting with Home Assistant 2022.2 this will prevent the recorder from "
|
"Starting with Home Assistant 2022.6 this prevents the recorder from "
|
||||||
"starting. Please migrate your database to a supported software before then",
|
"starting. Please migrate your database to a supported software",
|
||||||
dialect_name,
|
dialect_name,
|
||||||
"MariaDB ≥ 10.3, MySQL ≥ 8.0, PostgreSQL ≥ 12, SQLite ≥ 3.31.0",
|
"MariaDB ≥ 10.3, MySQL ≥ 8.0, PostgreSQL ≥ 12, SQLite ≥ 3.31.0",
|
||||||
)
|
)
|
||||||
|
raise UnsupportedDialect
|
||||||
|
|
||||||
|
|
||||||
def _warn_unsupported_version(
|
def _fail_unsupported_version(
|
||||||
server_version: str, dialect_name: str, minimum_version: str
|
server_version: str, dialect_name: str, minimum_version: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Warn about unsupported database version."""
|
"""Warn about unsupported database version."""
|
||||||
_LOGGER.warning(
|
_LOGGER.error(
|
||||||
"Version %s of %s is not supported; minimum supported version is %s. "
|
"Version %s of %s is not supported; minimum supported version is %s. "
|
||||||
"Starting with Home Assistant 2022.2 this will prevent the recorder from "
|
"Starting with Home Assistant 2022.6 this prevents the recorder from "
|
||||||
"starting. Please upgrade your database software before then",
|
"starting. Please upgrade your database software",
|
||||||
server_version,
|
server_version,
|
||||||
dialect_name,
|
dialect_name,
|
||||||
minimum_version,
|
minimum_version,
|
||||||
)
|
)
|
||||||
|
raise UnsupportedDialect
|
||||||
|
|
||||||
|
|
||||||
def _extract_version_from_server_response(
|
def _extract_version_from_server_response(
|
||||||
|
@ -398,9 +401,6 @@ def setup_connection_for_dialect(
|
||||||
first_connection: bool,
|
first_connection: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Execute statements needed for dialect connection."""
|
"""Execute statements needed for dialect connection."""
|
||||||
# Returns False if the the connection needs to be setup
|
|
||||||
# on the next connection, returns True if the connection
|
|
||||||
# never needs to be setup again.
|
|
||||||
if dialect_name == SupportedDialect.SQLITE:
|
if dialect_name == SupportedDialect.SQLITE:
|
||||||
if first_connection:
|
if first_connection:
|
||||||
old_isolation = dbapi_connection.isolation_level
|
old_isolation = dbapi_connection.isolation_level
|
||||||
|
@ -419,7 +419,7 @@ def setup_connection_for_dialect(
|
||||||
False
|
False
|
||||||
)
|
)
|
||||||
if not version or version < MIN_VERSION_SQLITE:
|
if not version or version < MIN_VERSION_SQLITE:
|
||||||
_warn_unsupported_version(
|
_fail_unsupported_version(
|
||||||
version or version_string, "SQLite", MIN_VERSION_SQLITE
|
version or version_string, "SQLite", MIN_VERSION_SQLITE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -453,7 +453,7 @@ def setup_connection_for_dialect(
|
||||||
False
|
False
|
||||||
)
|
)
|
||||||
if not version or version < MIN_VERSION_MARIA_DB:
|
if not version or version < MIN_VERSION_MARIA_DB:
|
||||||
_warn_unsupported_version(
|
_fail_unsupported_version(
|
||||||
version or version_string, "MariaDB", MIN_VERSION_MARIA_DB
|
version or version_string, "MariaDB", MIN_VERSION_MARIA_DB
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -462,7 +462,7 @@ def setup_connection_for_dialect(
|
||||||
False
|
False
|
||||||
)
|
)
|
||||||
if not version or version < MIN_VERSION_MYSQL:
|
if not version or version < MIN_VERSION_MYSQL:
|
||||||
_warn_unsupported_version(
|
_fail_unsupported_version(
|
||||||
version or version_string, "MySQL", MIN_VERSION_MYSQL
|
version or version_string, "MySQL", MIN_VERSION_MYSQL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -473,12 +473,12 @@ def setup_connection_for_dialect(
|
||||||
version_string = result[0][0]
|
version_string = result[0][0]
|
||||||
version = _extract_version_from_server_response(version_string)
|
version = _extract_version_from_server_response(version_string)
|
||||||
if not version or version < MIN_VERSION_PGSQL:
|
if not version or version < MIN_VERSION_PGSQL:
|
||||||
_warn_unsupported_version(
|
_fail_unsupported_version(
|
||||||
version or version_string, "PostgreSQL", MIN_VERSION_PGSQL
|
version or version_string, "PostgreSQL", MIN_VERSION_PGSQL
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
_warn_unsupported_dialect(dialect_name)
|
_fail_unsupported_dialect(dialect_name)
|
||||||
|
|
||||||
|
|
||||||
def end_incomplete_runs(session: Session, start_time: datetime) -> None:
|
def end_incomplete_runs(session: Session, start_time: datetime) -> None:
|
||||||
|
|
|
@ -14,7 +14,7 @@ from sqlalchemy.sql.lambdas import StatementLambdaElement
|
||||||
from homeassistant.components import recorder
|
from homeassistant.components import recorder
|
||||||
from homeassistant.components.recorder import history, util
|
from homeassistant.components.recorder import history, util
|
||||||
from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX
|
from homeassistant.components.recorder.const import DATA_INSTANCE, SQLITE_URL_PREFIX
|
||||||
from homeassistant.components.recorder.models import RecorderRuns
|
from homeassistant.components.recorder.models import RecorderRuns, UnsupportedDialect
|
||||||
from homeassistant.components.recorder.util import (
|
from homeassistant.components.recorder.util import (
|
||||||
end_incomplete_runs,
|
end_incomplete_runs,
|
||||||
is_second_sunday,
|
is_second_sunday,
|
||||||
|
@ -168,10 +168,8 @@ async def test_last_run_was_recently_clean(
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mysql_version, db_supports_row_number",
|
"mysql_version, db_supports_row_number",
|
||||||
[
|
[
|
||||||
("10.2.0-MariaDB", True),
|
("10.3.0-MariaDB", True),
|
||||||
("10.1.0-MariaDB", False),
|
("8.0.0", True),
|
||||||
("5.8.0", True),
|
|
||||||
("5.7.0", False),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_setup_connection_for_dialect_mysql(mysql_version, db_supports_row_number):
|
def test_setup_connection_for_dialect_mysql(mysql_version, db_supports_row_number):
|
||||||
|
@ -207,8 +205,7 @@ def test_setup_connection_for_dialect_mysql(mysql_version, db_supports_row_numbe
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"sqlite_version, db_supports_row_number",
|
"sqlite_version, db_supports_row_number",
|
||||||
[
|
[
|
||||||
("3.25.0", True),
|
("3.31.0", True),
|
||||||
("3.24.0", False),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_setup_connection_for_dialect_sqlite(sqlite_version, db_supports_row_number):
|
def test_setup_connection_for_dialect_sqlite(sqlite_version, db_supports_row_number):
|
||||||
|
@ -255,8 +252,7 @@ def test_setup_connection_for_dialect_sqlite(sqlite_version, db_supports_row_num
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"sqlite_version, db_supports_row_number",
|
"sqlite_version, db_supports_row_number",
|
||||||
[
|
[
|
||||||
("3.25.0", True),
|
("3.31.0", True),
|
||||||
("3.24.0", False),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_setup_connection_for_dialect_sqlite_zero_commit_interval(
|
def test_setup_connection_for_dialect_sqlite_zero_commit_interval(
|
||||||
|
@ -319,7 +315,7 @@ def test_setup_connection_for_dialect_sqlite_zero_commit_interval(
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_warn_outdated_mysql(caplog, mysql_version, message):
|
def test_fail_outdated_mysql(caplog, mysql_version, message):
|
||||||
"""Test setting up the connection for an outdated mysql version."""
|
"""Test setting up the connection for an outdated mysql version."""
|
||||||
instance_mock = MagicMock(_db_supports_row_number=True)
|
instance_mock = MagicMock(_db_supports_row_number=True)
|
||||||
execute_args = []
|
execute_args = []
|
||||||
|
@ -340,7 +336,10 @@ def test_warn_outdated_mysql(caplog, mysql_version, message):
|
||||||
|
|
||||||
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
|
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
|
||||||
|
|
||||||
util.setup_connection_for_dialect(instance_mock, "mysql", dbapi_connection, True)
|
with pytest.raises(UnsupportedDialect):
|
||||||
|
util.setup_connection_for_dialect(
|
||||||
|
instance_mock, "mysql", dbapi_connection, True
|
||||||
|
)
|
||||||
|
|
||||||
assert message in caplog.text
|
assert message in caplog.text
|
||||||
|
|
||||||
|
@ -395,7 +394,7 @@ def test_supported_mysql(caplog, mysql_version):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_warn_outdated_pgsql(caplog, pgsql_version, message):
|
def test_fail_outdated_pgsql(caplog, pgsql_version, message):
|
||||||
"""Test setting up the connection for an outdated PostgreSQL version."""
|
"""Test setting up the connection for an outdated PostgreSQL version."""
|
||||||
instance_mock = MagicMock(_db_supports_row_number=True)
|
instance_mock = MagicMock(_db_supports_row_number=True)
|
||||||
execute_args = []
|
execute_args = []
|
||||||
|
@ -416,9 +415,10 @@ def test_warn_outdated_pgsql(caplog, pgsql_version, message):
|
||||||
|
|
||||||
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
|
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
|
||||||
|
|
||||||
util.setup_connection_for_dialect(
|
with pytest.raises(UnsupportedDialect):
|
||||||
instance_mock, "postgresql", dbapi_connection, True
|
util.setup_connection_for_dialect(
|
||||||
)
|
instance_mock, "postgresql", dbapi_connection, True
|
||||||
|
)
|
||||||
|
|
||||||
assert message in caplog.text
|
assert message in caplog.text
|
||||||
|
|
||||||
|
@ -472,7 +472,7 @@ def test_supported_pgsql(caplog, pgsql_version):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_warn_outdated_sqlite(caplog, sqlite_version, message):
|
def test_fail_outdated_sqlite(caplog, sqlite_version, message):
|
||||||
"""Test setting up the connection for an outdated sqlite version."""
|
"""Test setting up the connection for an outdated sqlite version."""
|
||||||
instance_mock = MagicMock(_db_supports_row_number=True)
|
instance_mock = MagicMock(_db_supports_row_number=True)
|
||||||
execute_args = []
|
execute_args = []
|
||||||
|
@ -493,7 +493,10 @@ def test_warn_outdated_sqlite(caplog, sqlite_version, message):
|
||||||
|
|
||||||
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
|
dbapi_connection = MagicMock(cursor=_make_cursor_mock)
|
||||||
|
|
||||||
util.setup_connection_for_dialect(instance_mock, "sqlite", dbapi_connection, True)
|
with pytest.raises(UnsupportedDialect):
|
||||||
|
util.setup_connection_for_dialect(
|
||||||
|
instance_mock, "sqlite", dbapi_connection, True
|
||||||
|
)
|
||||||
|
|
||||||
assert message in caplog.text
|
assert message in caplog.text
|
||||||
|
|
||||||
|
@ -544,7 +547,10 @@ def test_warn_unsupported_dialect(caplog, dialect, message):
|
||||||
instance_mock = MagicMock()
|
instance_mock = MagicMock()
|
||||||
dbapi_connection = MagicMock()
|
dbapi_connection = MagicMock()
|
||||||
|
|
||||||
util.setup_connection_for_dialect(instance_mock, dialect, dbapi_connection, True)
|
with pytest.raises(UnsupportedDialect):
|
||||||
|
util.setup_connection_for_dialect(
|
||||||
|
instance_mock, dialect, dbapi_connection, True
|
||||||
|
)
|
||||||
|
|
||||||
assert message in caplog.text
|
assert message in caplog.text
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue