Check config before restarting#5609
Conversation
|
@andrey-git, thanks for your PR! By analyzing the history of the files in this pull request, we identified @balloob, @kellerza and @pvizeli to be potential reviewers. |
| res['secret_cache'] = dict(yaml.__SECRET_CACHE) | ||
| except Exception as err: # pylint: disable=broad-except | ||
| print(color('red', 'Fatal error while loading config:'), str(err)) | ||
| res['except_global'].append(err) |
There was a problem hiding this comment.
There will always only be one of these, so instead of adding a new key, simply add it to "General Errors" res['except'].setdefault(ERROR_STR, []).append(err)
homeassistant/core.py
Outdated
| @callback | ||
| def _async_restart_handler(self, *args): | ||
| """Restart Home Assistant.""" | ||
| if subprocess.call([sys.argv[0], '--script', 'check_config']): |
There was a problem hiding this comment.
Is this not a blocking call? Should be scheduled on the event loop
|
I'm not sure if that the right solution, but the core is async and need https://docs.python.org/3.4/library/asyncio-subprocess.html |
| def _async_restart_handler(self, *args): | ||
| """Restart Home Assistant.""" | ||
| if subprocess.call([sys.argv[0], '--script', 'check_config']): | ||
| _LOGGER.error("check_config failed. Not restarting.") |
There was a problem hiding this comment.
You will need to create a persistent notification to notify the user or they won't know.
There was a problem hiding this comment.
This is a great idea, with the same notification name, you can do a "checking config" message and change it into a "failed" config message if it fails.
Checking does take a while on slower rPi's!
There was a problem hiding this comment.
Added notification and switched to async
|
One of my ideas is still to rewrite the config loader into a seperate function. The current check_config script is a monumental hack (was a good learning experience). The idea would be to have one function to provide a validated config, after voluptuous with all the correct line numbers references, include packages and stretch goal maybe load_order (although this migh not make sense) This function can then be the input for bootstrap, check_config or such validation requirements as we have here. The current way we do this is deeply ingrained into bootstrap and suffers from incorrect yaml references (log_exception shortcomings:, voluptuous strips line info (so needs to be re-added), named component keys "switch named1:" ) For a start no changes required to bootstrap, but in the long run I belive we can greatly simply bootstrap if we can trust the config coming in Would this be worth the effort? |
|
Added notification and switched to async |
| yield from self._notify('Config validated') | ||
| yield from self.async_stop() | ||
|
|
||
| @callback |
There was a problem hiding this comment.
Cant you simplify this (get rid of _async_check_config_and_restart function), by changing this @callback into a @asyncio.coroutine?
There was a problem hiding this comment.
Tried that. add_signal_handler refuses to accept a coroutine.
homeassistant/core.py
Outdated
| """Restart Home Assistant.""" | ||
| self.exit_code = RESTART_EXIT_CODE | ||
| self.loop.create_task(self.async_stop()) | ||
| if self.services.has_service('persistent_notification', 'create'): |
homeassistant/core.py
Outdated
| print(content) | ||
| # Put error cleaned from color codes in the error log so it | ||
| # will be visible at the UI. | ||
| _LOGGER.error(re.sub(r'\033\[[^m]*m', '', content)) |
There was a problem hiding this comment.
Won't this error now be duplicated in stdout?
There was a problem hiding this comment.
Yes, but I think this is the best outcome, as stdout will have the colored printout which is easiest to read and the UI will have the error log.
There was a problem hiding this comment.
We should only log it with _LOGGER.error and not with print.
There was a problem hiding this comment.
Agreed, the print shows the colour, but in the context of a server, you rarely see stdout. It's mostly helpful in a dev environment.
|
Hi @andrey-git, It seems you haven't yet signed a CLA. Please do so here. Once you do that we will be able to review and accept this pull request. Thanks! |
| 'check_config', | ||
| stdout=asyncio.subprocess.PIPE) | ||
| # Wait for the subprocess exit | ||
| result = yield from proc.wait() |
There was a problem hiding this comment.
you should use communicate to protect from subprocess deadlock.
|
@pvizeli the non-hack way is obviously better, but that is for the future. For now I think that this is a fine intermediate solution. (and since all the hacky stuff is hidden in a command line script, it's fine) |
homeassistant/core.py
Outdated
| self.loop.create_task(self.async_stop()) | ||
|
|
||
| @asyncio.coroutine | ||
| def _notify(self, message): |
There was a problem hiding this comment.
There is no need for this function. Every component already exports functions for their services: https://github.com/andrey-git/home-assistant/blob/7b5b1fb7511bbc42c943d27eb0563a1e17ac4d24/homeassistant/components/persistent_notification.py#L47-L58
There was a problem hiding this comment.
I think core shouldn't depend on a component. Even a "core" components such as persistent notification.
homeassistant/core.py
Outdated
| """Restart Home Assistant.""" | ||
| self.exit_code = RESTART_EXIT_CODE | ||
| self.loop.create_task(self.async_stop()) | ||
| self.loop.create_task(self._notify('Checking config...')) |
There was a problem hiding this comment.
Please remove this notification. We should only report on result.
homeassistant/core.py
Outdated
| # Put error cleaned from color codes in the error log so it | ||
| # will be visible at the UI. | ||
| _LOGGER.error(re.sub(r'\033\[[^m]*m', '', content)) | ||
| yield from self._notify('Restart failed') |
There was a problem hiding this comment.
Please remove this function and inline the calls inside this method. Also use notification_id so that you will overwrite previous restart hass notifications instead of crowding the UI.
There was a problem hiding this comment.
The _notification_id is captured in the _notify methods, but also likethe idea more that it rather calls the complete method
There was a problem hiding this comment.
This code makes two notification (restarting, and error). Why duplicate code for the title and the id?
There was a problem hiding this comment.
Oh, I see that you want to remove the "checking config". In this case the inline is indeed in order.
There was a problem hiding this comment.
You'll need to move the import of persistent notification also in this method, or else we'll get circular imports.
There was a problem hiding this comment.
Don't you think depending here on component/ is bad?
I think core should just call the service, so it will "work" even if the persistent_notification component is missing.
homeassistant/core.py
Outdated
| yield from self._notify('Restart failed') | ||
| return | ||
| self.exit_code = RESTART_EXIT_CODE | ||
| # This notification is unlikely to arrive before HA is stopped. |
There was a problem hiding this comment.
Indeed unlikely, let's remove it.
|
Since it's core functionality, we will need some tests. You can stub out the call to |
|
Tests added, comments addressed, except for importing persistent_notification in core which I think shouldn't happen. |
|
Other question. Why not move to |
|
@pvizeli |
|
Hack is the wrong word, I don't want to disrate that. I think the real workflow is not solve with that. After I upload a congig I will check that with script and it would be nice if I can use it with a service. I never restart my hass after every config update. I think a user should have this possibility and if a user don't use it... The other is that the check config overwritte the logs and make the restart complexer as it needed and make it more trouble maker. |
|
And in the future we have a online config editor and that need it as service or we need to restart everthing after save :) |
| self.hass.start() | ||
| with patch.object(self.hass, 'async_stop') as mock_stop: | ||
| self.hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART) | ||
| mock_stop.assert_not_called() |
There was a problem hiding this comment.
Tip for the future: don't use assert_not_called because if you would make a typo like calling mock_stop.assert_not_called_obviously_wrong() it will pass because it will return a mock object. Better to test explicitly:
assert mock_stop.called|
So I agree with @pvizeli that calling a subprocess etc should not be handled in the core but I think that moving our start/stop/check to So let's merge this and then put on our to do to move all three services. |
Description:
Check config by running
--script check_configbefore restarting. Cancel restart on failure.If the code does not interact with devices:
toxrun successfully. Your PR cannot be merged unless tests pass