Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle additional bluetooth start exceptions #76096

Merged
merged 6 commits into from Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 32 additions & 1 deletion homeassistant/components/bluetooth/__init__.py
Expand Up @@ -12,6 +12,7 @@

import async_timeout
from bleak import BleakError
from dbus_next import InvalidMessageError

from homeassistant import config_entries
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
Expand All @@ -26,6 +27,7 @@
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.loader import async_get_bluetooth
from homeassistant.util.package import is_docker_env

from . import models
from .const import CONF_ADAPTER, DEFAULT_ADAPTERS, DOMAIN
Expand Down Expand Up @@ -341,13 +343,42 @@ async def async_start(
try:
async with async_timeout.timeout(START_TIMEOUT):
await self.scanner.start() # type: ignore[no-untyped-call]
except InvalidMessageError as ex:
self._cancel_device_detected()
_LOGGER.debug("Invalid DBus message received: %s", ex, exc_info=True)
raise ConfigEntryNotReady(
f"Invalid DBus message received: {ex}; try restarting `dbus`"
) from ex
except BrokenPipeError as ex:
self._cancel_device_detected()
_LOGGER.debug("DBus connection broken: %s", ex, exc_info=True)
if is_docker_env():
raise ConfigEntryNotReady(
f"DBus connection broken: {ex}; try restarting `bluetooth`, `dbus`, and finally the docker container"
) from ex
raise ConfigEntryNotReady(
f"DBus connection broken: {ex}; try restarting `bluetooth` and `dbus`"
) from ex
except FileNotFoundError as ex:
self._cancel_device_detected()
_LOGGER.debug(
"FileNotFoundError while starting bluetooth: %s", ex, exc_info=True
)
if is_docker_env():
raise ConfigEntryNotReady(
f"DBus service not found; docker config may be missing `-v /run/dbus:/run/dbus:ro`: {ex}"
) from ex
raise ConfigEntryNotReady(
f"DBus service not found; make sure the DBus socket is available to Home Assistant: {ex}"
) from ex
except asyncio.TimeoutError as ex:
self._cancel_device_detected()
raise ConfigEntryNotReady(
f"Timed out starting Bluetooth after {START_TIMEOUT} seconds"
) from ex
except (FileNotFoundError, BleakError) as ex:
except BleakError as ex:
self._cancel_device_detected()
_LOGGER.debug("BleakError while starting bluetooth: %s", ex, exc_info=True)
raise ConfigEntryNotReady(f"Failed to start Bluetooth: {ex}") from ex
self.async_setup_unavailable_tracking()
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
Expand Down
110 changes: 110 additions & 0 deletions tests/components/bluetooth/test_init.py
Expand Up @@ -5,6 +5,7 @@

from bleak import BleakError
from bleak.backends.scanner import AdvertisementData, BLEDevice
from dbus_next import InvalidMessageError
import pytest

from homeassistant.components import bluetooth
Expand Down Expand Up @@ -1409,3 +1410,112 @@ async def test_changing_the_adapter_at_runtime(hass):

hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()


async def test_dbus_socket_missing_in_container(hass, caplog):
"""Test we handle dbus being missing in the container."""

with patch(
"homeassistant.components.bluetooth.is_docker_env", return_value=True
), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch(
"homeassistant.components.bluetooth.HaBleakScanner.start",
side_effect=FileNotFoundError,
):
assert await async_setup_component(
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()

hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert "/run/dbus" in caplog.text
assert "docker" in caplog.text


async def test_dbus_socket_missing(hass, caplog):
"""Test we handle dbus being missing."""

with patch(
"homeassistant.components.bluetooth.is_docker_env", return_value=False
), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch(
"homeassistant.components.bluetooth.HaBleakScanner.start",
side_effect=FileNotFoundError,
):
assert await async_setup_component(
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()

hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert "DBus" in caplog.text
assert "docker" not in caplog.text


async def test_dbus_broken_pipe_in_container(hass, caplog):
"""Test we handle dbus broken pipe in the container."""

with patch(
"homeassistant.components.bluetooth.is_docker_env", return_value=True
), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch(
"homeassistant.components.bluetooth.HaBleakScanner.start",
side_effect=BrokenPipeError,
):
assert await async_setup_component(
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()

hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert "dbus" in caplog.text
assert "restarting" in caplog.text
assert "container" in caplog.text


async def test_dbus_broken_pipe(hass, caplog):
"""Test we handle dbus broken pipe."""

with patch(
"homeassistant.components.bluetooth.is_docker_env", return_value=False
), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch(
"homeassistant.components.bluetooth.HaBleakScanner.start",
side_effect=BrokenPipeError,
):
assert await async_setup_component(
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()

hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert "DBus" in caplog.text
assert "restarting" in caplog.text
assert "container" not in caplog.text


async def test_invalid_dbus_message(hass, caplog):
"""Test we handle invalid dbus message."""

with patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch(
"homeassistant.components.bluetooth.HaBleakScanner.start",
side_effect=InvalidMessageError,
):
assert await async_setup_component(
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()

hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert "dbus" in caplog.text