Skip to content

Check config before restarting#5609

Merged
balloob merged 8 commits intohome-assistant:devfrom
andrey-git:restart
Feb 7, 2017
Merged

Check config before restarting#5609
balloob merged 8 commits intohome-assistant:devfrom
andrey-git:restart

Conversation

@andrey-git
Copy link
Copy Markdown
Contributor

Description:

Check config by running --script check_config before restarting. Cancel restart on failure.
If the code does not interact with devices:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • Tests have been added to verify that the new code works.

@mention-bot
Copy link
Copy Markdown

@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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@callback
def _async_restart_handler(self, *args):
"""Restart Home Assistant."""
if subprocess.call([sys.argv[0], '--script', 'check_config']):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not a blocking call? Should be scheduled on the event loop

@pvizeli
Copy link
Copy Markdown
Member

pvizeli commented Jan 28, 2017

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.")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to create a persistent notification to notify the user or they won't know.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added notification and switched to async

@fabaff fabaff changed the title Check config before restarting. Check config before restarting Jan 29, 2017
@kellerza
Copy link
Copy Markdown
Member

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?

@andrey-git
Copy link
Copy Markdown
Contributor Author

Added notification and switched to async

yield from self._notify('Config validated')
yield from self.async_stop()

@callback
Copy link
Copy Markdown
Member

@kellerza kellerza Jan 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cant you simplify this (get rid of _async_check_config_and_restart function), by changing this @callback into a @asyncio.coroutine?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that. add_signal_handler refuses to accept a coroutine.

"""Restart Home Assistant."""
self.exit_code = RESTART_EXIT_CODE
self.loop.create_task(self.async_stop())
if self.services.has_service('persistent_notification', 'create'):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is always setup

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

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))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this error now be duplicated in stdout?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

@balloob balloob Feb 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only log it with _LOGGER.error and not with print.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@homeassistant
Copy link
Copy Markdown
Contributor

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!

Copy link
Copy Markdown
Member

@pvizeli pvizeli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea to call self inside self or using subprocess stuff inside core. It's like a hack.

I think @kellerza have right to move the validate layer into next level and split it from setup. I think that is the harder way but not a hack like the check_config (sorry @kellerza)

'check_config',
stdout=asyncio.subprocess.PIPE)
# Wait for the subprocess exit
result = yield from proc.wait()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should use communicate to protect from subprocess deadlock.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

@andrey-git
Copy link
Copy Markdown
Contributor Author

@pvizeli I agree that the solution @kellerza proposed is much better.
However that would be a large change that I'm not comfortable making.

This change still brings value to users.

@andrey-git
Copy link
Copy Markdown
Contributor Author

@kellerza , @pvizeli Is this ready to merge?

@balloob
Copy link
Copy Markdown
Member

balloob commented Feb 2, 2017

@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)

self.loop.create_task(self.async_stop())

@asyncio.coroutine
def _notify(self, message):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think core shouldn't depend on a component. Even a "core" components such as persistent notification.

"""Restart Home Assistant."""
self.exit_code = RESTART_EXIT_CODE
self.loop.create_task(self.async_stop())
self.loop.create_task(self._notify('Checking config...'))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this notification. We should only report on result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

# 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')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _notification_id is captured in the _notify methods, but also likethe idea more that it rather calls the complete method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code makes two notification (restarting, and error). Why duplicate code for the title and the id?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see that you want to remove the "checking config". In this case the inline is indeed in order.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to move the import of persistent notification also in this method, or else we'll get circular imports.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

yield from self._notify('Restart failed')
return
self.exit_code = RESTART_EXIT_CODE
# This notification is unlikely to arrive before HA is stopped.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed unlikely, let's remove it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@balloob
Copy link
Copy Markdown
Member

balloob commented Feb 2, 2017

Since it's core functionality, we will need some tests. You can stub out the call to asyncio.subprocess to simulate getting a certain result and see if a restart would go through.

@andrey-git
Copy link
Copy Markdown
Contributor Author

Tests added, comments addressed, except for importing persistent_notification in core which I think shouldn't happen.

@pvizeli
Copy link
Copy Markdown
Member

pvizeli commented Feb 3, 2017

Other question. Why not move to components/__init__ and create a service for check config. So we have no hack inside core @balloob. @andrey-git your code is good that is not my point.

@danielperna84
Copy link
Copy Markdown
Contributor

@pvizeli
Good point. Although I'm biased because I would need that for my configurator.

@pvizeli
Copy link
Copy Markdown
Member

pvizeli commented Feb 6, 2017

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.

@pvizeli
Copy link
Copy Markdown
Member

pvizeli commented Feb 6, 2017

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()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@balloob
Copy link
Copy Markdown
Member

balloob commented Feb 7, 2017

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 components/__init__.py is out of scope for this PR.

So let's merge this and then put on our to do to move all three services.

@balloob balloob merged commit f774538 into home-assistant:dev Feb 7, 2017
@home-assistant home-assistant locked and limited conversation to collaborators May 19, 2017
@andrey-git andrey-git deleted the restart branch May 10, 2018 18:14
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants