Remove embedded MQTT broker (#37032)

This commit is contained in:
Erik Montnemery 2020-06-23 18:51:50 +02:00 committed by GitHub
parent a004e6aa68
commit 1b3e5460a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 3 additions and 265 deletions

View file

@ -42,7 +42,7 @@ from homeassistant.util.logging import catch_log_exception
# Loading the config flow file will register the flow
from . import config_flow # noqa: F401 pylint: disable=unused-import
from . import debug_info, discovery, server
from . import debug_info, discovery
from .const import (
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_TOPIC,
@ -80,8 +80,6 @@ DATA_MQTT_CONFIG = "mqtt_config"
SERVICE_PUBLISH = "publish"
SERVICE_DUMP = "dump"
CONF_EMBEDDED = "embedded"
CONF_DISCOVERY_PREFIX = "discovery_prefix"
CONF_KEEPALIVE = "keepalive"
CONF_CERTIFICATE = "certificate"
@ -169,7 +167,7 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(
vol.Coerce(int), vol.Range(min=15)
),
vol.Optional(CONF_BROKER): cv.string,
vol.Required(CONF_BROKER): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
@ -187,9 +185,6 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
),
vol.Optional(CONF_EMBEDDED): vol.All(
server.HBMQTT_CONFIG_SCHEMA, embedded_broker_deprecated
),
vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): cv.boolean,
@ -418,23 +413,6 @@ def subscribe(
return remove
async def _async_setup_server(hass: HomeAssistantType, config: ConfigType):
"""Try to start embedded MQTT broker.
This method is a coroutine.
"""
conf: ConfigType = config.get(DOMAIN, {})
success, broker_config = await server.async_start(
hass, conf.get(CONF_PASSWORD), conf.get(CONF_EMBEDDED)
)
if not success:
return None
return broker_config
async def _async_setup_discovery(
hass: HomeAssistantType, conf: ConfigType, config_entry
) -> bool:
@ -464,28 +442,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
conf = dict(conf)
if CONF_EMBEDDED in conf or CONF_BROKER not in conf:
broker_config = await _async_setup_server(hass, config)
if broker_config is None:
_LOGGER.error("Unable to start embedded MQTT broker")
return False
conf.update(
{
CONF_BROKER: broker_config[0],
CONF_PORT: broker_config[1],
CONF_USERNAME: broker_config[2],
CONF_PASSWORD: broker_config[3],
CONF_CERTIFICATE: broker_config[4],
CONF_PROTOCOL: broker_config[5],
CONF_CLIENT_KEY: None,
CONF_CLIENT_CERT: None,
CONF_TLS_INSECURE: None,
}
)
hass.data[DATA_MQTT_CONFIG] = conf
# Only import if we haven't before.

View file

@ -3,7 +3,7 @@
"name": "MQTT",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/mqtt",
"requirements": ["hbmqtt==0.9.5", "paho-mqtt==1.5.0"],
"requirements": ["paho-mqtt==1.5.0"],
"dependencies": ["http"],
"codeowners": ["@home-assistant/core", "@emontnemery"]
}

View file

@ -1,99 +0,0 @@
"""Support for a local MQTT broker."""
import logging
import tempfile
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
from .const import PROTOCOL_311
_LOGGER = logging.getLogger(__name__)
# None allows custom config to be created through generate_config
HBMQTT_CONFIG_SCHEMA = vol.Any(
None,
vol.Schema(
{
vol.Optional("auth"): vol.Schema(
{vol.Optional("password-file"): cv.isfile}, extra=vol.ALLOW_EXTRA
),
vol.Optional("listeners"): vol.Schema(
{vol.Required("default"): vol.Schema(dict), str: vol.Schema(dict)}
),
},
extra=vol.ALLOW_EXTRA,
),
)
async def async_start(hass, password, server_config):
"""Initialize MQTT Server.
This method is a coroutine.
"""
# pylint: disable=import-outside-toplevel
from hbmqtt.broker import Broker, BrokerException
passwd = tempfile.NamedTemporaryFile()
gen_server_config, client_config = generate_config(hass, passwd, password)
try:
if server_config is None:
server_config = gen_server_config
broker = Broker(server_config, hass.loop)
await broker.start()
except BrokerException:
_LOGGER.exception("Error initializing MQTT server")
return False, None
finally:
passwd.close()
async def async_shutdown_mqtt_server(event):
"""Shut down the MQTT server."""
await broker.shutdown()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown_mqtt_server)
return True, client_config
def generate_config(hass, passwd, password):
"""Generate a configuration based on current Home Assistant instance."""
# pylint: disable=import-outside-toplevel
from passlib.apps import custom_app_context
config = {
"listeners": {
"default": {
"max-connections": 50000,
"bind": "0.0.0.0:1883",
"type": "tcp",
},
"ws-1": {"bind": "0.0.0.0:8080", "type": "ws"},
},
"auth": {"allow-anonymous": password is None},
"plugins": ["auth_anonymous"],
"topic-check": {"enabled": True, "plugins": ["topic_taboo"]},
}
if password:
username = "homeassistant"
# Encrypt with what hbmqtt uses to verify
passwd.write(
f"homeassistant:{custom_app_context.encrypt(password)}\n".encode("utf-8")
)
passwd.flush()
config["auth"]["password-file"] = passwd.name
config["plugins"].append("auth_file")
else:
username = None
client_config = ("localhost", 1883, username, password, None, PROTOCOL_311)
return config, client_config

View file

@ -713,9 +713,6 @@ hangups==0.4.9
# homeassistant.components.cloud
hass-nabucasa==0.34.7
# homeassistant.components.mqtt
hbmqtt==0.9.5
# homeassistant.components.jewish_calendar
hdate==0.9.5

View file

@ -324,9 +324,6 @@ hangups==0.4.9
# homeassistant.components.cloud
hass-nabucasa==0.34.7
# homeassistant.components.mqtt
hbmqtt==0.9.5
# homeassistant.components.jewish_calendar
hdate==0.9.5

View file

@ -670,32 +670,6 @@ async def test_restore_all_active_subscriptions_on_reconnect(
assert mqtt_client_mock.subscribe.mock_calls == expected
@pytest.fixture
def mqtt_server_start_mock(hass):
"""Mock embedded server start."""
client_config = ("localhost", 1883, "user", "pass", None, "3.1.1")
with patch(
"homeassistant.components.mqtt.server.async_start",
return_value=(True, client_config),
) as _start:
yield _start
@pytest.mark.parametrize("mqtt_config", [{}])
async def test_setup_embedded_starts_with_no_config(
hass, mqtt_server_start_mock, mqtt_mock
):
"""Test setting up embedded server with no config."""
assert mqtt_server_start_mock.call_count == 1
@pytest.mark.parametrize("mqtt_config", [{"embedded": None}])
async def test_setup_embedded_with_embedded(hass, mqtt_server_start_mock, mqtt_mock):
"""Test setting up embedded server with empty embedded config."""
assert mqtt_server_start_mock.call_count == 1
async def test_setup_logs_error_if_no_connect_broker(hass, caplog):
"""Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})

View file

@ -1,87 +0,0 @@
"""The tests for the MQTT component embedded server."""
from unittest.mock import MagicMock, Mock
import pytest
import homeassistant.components.mqtt as mqtt
from homeassistant.const import CONF_PASSWORD
from homeassistant.setup import setup_component
from tests.async_mock import AsyncMock, patch
from tests.common import get_test_home_assistant, mock_coro
@pytest.fixture(autouse=True)
def inject_fixture(hass_storage):
"""Inject pytest fixtures."""
class TestMQTT:
"""Test the MQTT component."""
def setup_method(self, method):
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
def teardown_method(self, method):
"""Stop everything that was started."""
self.hass.stop()
@patch("passlib.apps.custom_app_context", Mock(return_value=""))
@patch("tempfile.NamedTemporaryFile", Mock(return_value=MagicMock()))
@patch("hbmqtt.broker.Broker", Mock(return_value=MagicMock(start=AsyncMock())))
@patch("hbmqtt.broker.Broker.start", AsyncMock(return_value=None))
@patch("homeassistant.components.mqtt.MQTT")
def test_creating_config_with_pass_and_no_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started with password.
Since 0.77, MQTT server has to set up its own password.
"""
mock_mqtt().async_connect = AsyncMock(return_value=True)
self.hass.bus.listen_once = MagicMock()
password = "mqtt_secret"
assert setup_component(
self.hass, mqtt.DOMAIN, {mqtt.DOMAIN: {CONF_PASSWORD: password}}
)
self.hass.block_till_done()
assert mock_mqtt.called
assert mock_mqtt.mock_calls[1][1][2]["username"] == "homeassistant"
assert mock_mqtt.mock_calls[1][1][2]["password"] == password
@patch("passlib.apps.custom_app_context", Mock(return_value=""))
@patch("tempfile.NamedTemporaryFile", Mock(return_value=MagicMock()))
@patch("hbmqtt.broker.Broker", Mock(return_value=MagicMock(start=AsyncMock())))
@patch("hbmqtt.broker.Broker.start", AsyncMock(return_value=None))
@patch("homeassistant.components.mqtt.MQTT")
def test_creating_config_with_pass_and_http_pass(self, mock_mqtt):
"""Test if the MQTT server gets started with password.
Since 0.77, MQTT server has to set up its own password.
"""
mock_mqtt().async_connect = AsyncMock(return_value=True)
self.hass.bus.listen_once = MagicMock()
password = "mqtt_secret"
self.hass.config.api = MagicMock(api_password="api_password")
assert setup_component(
self.hass, mqtt.DOMAIN, {mqtt.DOMAIN: {CONF_PASSWORD: password}}
)
self.hass.block_till_done()
assert mock_mqtt.called
assert mock_mqtt.mock_calls[1][1][2]["username"] == "homeassistant"
assert mock_mqtt.mock_calls[1][1][2]["password"] == password
@patch("tempfile.NamedTemporaryFile", Mock(return_value=MagicMock()))
@patch("hbmqtt.broker.Broker.start", return_value=mock_coro())
def test_broker_config_fails(self, mock_run):
"""Test if the MQTT component fails if server fails."""
from hbmqtt.broker import BrokerException
mock_run.side_effect = BrokerException
self.hass.config.api = MagicMock(api_password=None)
assert not setup_component(
self.hass, mqtt.DOMAIN, {mqtt.DOMAIN: {mqtt.CONF_EMBEDDED: {}}}
)