diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 3f1f8617689..81d2dd8ea03 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -23,7 +23,8 @@ from homeassistant.const import ( CONF_PROTOCOL, CONF_USERNAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import Event, ServiceCall, callback -from homeassistant.exceptions import HomeAssistantError, Unauthorized +from homeassistant.exceptions import ( + HomeAssistantError, Unauthorized, ConfigEntryNotReady) from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ( @@ -104,6 +105,10 @@ ATTR_DISCOVERY_HASH = 'discovery_hash' MAX_RECONNECT_WAIT = 300 # seconds +CONNECTION_SUCCESS = 'connection_success' +CONNECTION_FAILED = 'connection_failed' +CONNECTION_FAILED_RECOVERABLE = 'connection_failed_recoverable' + def valid_topic(value: Any) -> str: """Validate that this is a valid topic name/filter.""" @@ -569,11 +574,14 @@ async def async_setup_entry(hass, entry): tls_version=tls_version, ) - success = await hass.data[DATA_MQTT].async_connect() # type: bool + result = await hass.data[DATA_MQTT].async_connect() # type: str - if not success: + if result == CONNECTION_FAILED: return False + if result == CONNECTION_FAILED_RECOVERABLE: + raise ConfigEntryNotReady + async def async_stop_mqtt(event: Event): """Stop MQTT component.""" await hass.data[DATA_MQTT].async_disconnect() @@ -685,7 +693,7 @@ class MQTT: await self.hass.async_add_job( self._mqttc.publish, topic, payload, qos, retain) - async def async_connect(self) -> bool: + async def async_connect(self) -> str: """Connect to the host. Does process messages yet. This method is a coroutine. @@ -696,15 +704,15 @@ class MQTT: self._mqttc.connect, self.broker, self.port, self.keepalive) except OSError as err: _LOGGER.error("Failed to connect due to exception: %s", err) - return False + return CONNECTION_FAILED_RECOVERABLE if result != 0: import paho.mqtt.client as mqtt _LOGGER.error("Failed to connect: %s", mqtt.error_string(result)) - return False + return CONNECTION_FAILED self._mqttc.loop_start() - return True + return CONNECTION_SUCCESS @callback def async_disconnect(self): diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 5c441a68bea..144ee9c43d8 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -12,6 +12,7 @@ from homeassistant.const import ( ATTR_DOMAIN, ATTR_SERVICE, EVENT_CALL_SERVICE, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import callback from homeassistant.setup import async_setup_component +from homeassistant.exceptions import ConfigEntryNotReady from tests.common import ( MockConfigEntry, async_fire_mqtt_message, async_mock_mqtt_component, @@ -621,6 +622,19 @@ async def test_setup_fails_if_no_connect_broker(hass): assert not await mqtt.async_setup_entry(hass, entry) +async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass): + """Test for setup failure if connection to broker is missing.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={ + mqtt.CONF_BROKER: 'test-broker' + }) + + with mock.patch('paho.mqtt.client.Client') as mock_client: + mock_client().connect = mock.Mock( + side_effect=OSError("Connection error")) + with pytest.raises(ConfigEntryNotReady): + await mqtt.async_setup_entry(hass, entry) + + async def test_setup_uses_certificate_on_certificate_set_to_auto( hass, mock_MQTT): """Test setup uses bundled certs when certificate is set to auto."""