Merge lp:~danilo/landscape-charm/actions into lp:landscape-charm
| Status: | Merged | ||||
|---|---|---|---|---|---|
| Approved by: | Данило Шеган on 2015-06-05 | ||||
| Approved revision: | 310 | ||||
| Merged at revision: | 306 | ||||
| Proposed branch: | lp:~danilo/landscape-charm/actions | ||||
| Merge into: | lp:landscape-charm | ||||
| Diff against target: |
350 lines (+159/-84) 9 files modified
lib/action.py (+50/-0) lib/hook.py (+0/-27) lib/migrate_schema.py (+2/-2) lib/pause.py (+2/-2) lib/resume.py (+2/-2) lib/tests/stubs.py (+8/-4) lib/tests/test_action.py (+93/-0) lib/tests/test_hook.py (+0/-45) lib/upgrade.py (+2/-2) |
||||
| To merge this branch: | bzr merge lp:~danilo/landscape-charm/actions | ||||
| Related bugs: |
|
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| 🤖 Landscape Builder | test results | Approve on 2015-06-05 | |
| Free Ekanayaka (community) | 2015-06-03 | Approve on 2015-06-05 | |
| Alberto Donato | 2015-06-03 | Approve on 2015-06-03 | |
|
Review via email:
|
|||
Commit Message
Introduce Action and MaintenanceAction classes to replace Hook-usage in actions
At this time, we need this separation to allow action-fail to properly set error messages (otherwise, hook error codes override it with "exited with code X".
This branch now drops MaintenanceHook which is replaced with MaintenanceAction now.
Description of the Change
Introduce Action and MaintenanceAction classes to replace Hook-usage in actions
All our existing actions are using Hook classes for their implementations. While this is not generally a problem other than naming problem, we do want to move away to having different behavior for actions.
Namely, they should not return error codes, because juju interprets their error code as the error message (see bug). Nor would that allow us to set the action return data with action-set.
This branch now drops MaintenanceHook which is replaced with MaintenanceAction now.
To test, use landscape.yaml from https:/
juju-deployer -vdW -w180 -c landscape.yaml landscape
and then play around with 'pause', 'resume', 'upgrade' and 'migrate-schema' actions. Check their status with 'juju action fetch $ID' to ensure error messages are properly set.
| Free Ekanayaka (free.ekanayaka) wrote : | # |
So technically the code triggered by actions can be considered "hook" code, in the sense that any charm script that gets invoked by juju is a "hook", but the reason it gets triggered may vary (a relation changed, and action was issued, the unit was stopped) and hence a the API available to the hook might change slightly (e.g. relation-set defaults to the relation ID being changed, or additional executables like action-set and action-fail are available).
With that in mind, and also considering the error handling issue in the comment, what about:
https:/
For the maintenance behavior I'd also add a class-level attribute (a bit for sake of composition-
| Free Ekanayaka (free.ekanayaka) wrote : | # |
> So technically the code triggered by actions can be considered "hook" code, in
> the sense that any charm script that gets invoked by juju is a "hook", but the
> reason it gets triggered may vary (a relation changed, and action was issued,
> the unit was stopped) and hence a the API available to the hook might change
> slightly (e.g. relation-set defaults to the relation ID being changed, or
> additional executables like action-set and action-fail are available).
>
> With that in mind, and also considering the error handling issue in the
> comment, what about:
>
> https:/
>
> For the maintenance behavior I'd also add a class-level attribute (a bit for
> sake of composition-
> behavior that you want to mix and match, and that's easier done with class
> attributes rather than with multiple inheritance).
FWIW this type of implementation (a single Hook abstraction with little behavioral variations when run in the context of an action) is the same as the one Juju in: juju/worker/
| Free Ekanayaka (free.ekanayaka) wrote : | # |
> So technically the code triggered by actions can be considered "hook" code, in
> the sense that any charm script that gets invoked by juju is a "hook", but the
> reason it gets triggered may vary (a relation changed, and action was issued,
> the unit was stopped) and hence a the API available to the hook might change
> slightly (e.g. relation-set defaults to the relation ID being changed, or
> additional executables like action-set and action-fail are available).
>
> With that in mind, and also considering the error handling issue in the
> comment, what about:
>
> https:/
>
> For the maintenance behavior I'd also add a class-level attribute (a bit for
> sake of composition-
> behavior that you want to mix and match, and that's easier done with class
> attributes rather than with multiple inheritance).
Re automatically detecting if we're in an action hook context, reading the Juju source code it seems we can use the JUJU_ACTION_NAME, JUJU_ACTION_UUID and JUJU_ACTION_TAG environment vars.
| Данило Шеган (danilo) wrote : | # |
Free, we basically disagree about what's "easier" in this context. I'd resolve the problem you mention by making Hook class suitable for reuse as a parent for Action class, and then Action class could still have all the action-specific functionality, but would still catch HookError.
Another option, that I like even more, is catching all the exceptions in run() and constructing a nice failure/log message out of those. I generally dislike having a particular exception as the API to signal failure, and would rather have failures automatically propagated and logged.
| Free Ekanayaka (free.ekanayaka) wrote : | # |
> Free, we basically disagree about what's "easier" in this context. I'd
> resolve the problem you mention by making Hook class suitable for reuse as a
> parent for Action class, and then Action class could still have all the
> action-specific functionality, but would still catch HookError.
This works for me. As said, I'd rather have just a Hook class and trigger the action-specific behavior automatically (we can know that by look at the env variables mentioned above)
> Another option, that I like even more, is catching all the exceptions in run()
> and constructing a nice failure/log message out of those.
Do you mean a catch-all try/except? E.g.
try:
self._run()
except Exception, error:
# do logging
> I generally dislike
> having a particular exception as the API to signal failure, and would rather
> have failures automatically propagated and logged.
I'm not convinced by this. The HookError is an internal way to propagate errors generated by *expected* conditions (and typically will not traceback and will provide to the user information that he can use to resolve the situation). Random errors should be probably considered just bugs and I think it's probably better to traceback (there's no way you can provide insight to the users about what to do).
So in short, HookError is for known failures mode, hard-crash for all the others.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean
that we'd have same classes doing different things based on their
environment: do you really foresee us having a hook code that should
behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in
multiple roles is more confusing, I am keeping Action* classes. I am
depending on Hook* classes, but couldn't come up with a better way to
extract common functionality without reworking everything, so left them
as they are.
I still have some reservations about using HookError directly: imho,
your example of apt module throwing HookError is a bad one: it should
never do that, but should instead throw errors specific to that module.
Callsite should catch that error, and if it's inside a hook/action, turn
it into a HookError/
branch, however.
You are right about not catching general exceptions and letting them
traceback, so I am not doing that.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean that we'd have same classes doing different things based on their environment: do you really foresee us having a hook code that should behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in multiple roles is more confusing, I am keeping Action* classes. I am depending on Hook* classes, but couldn't come up with a better way to extract common functionality without reworking everything, so left them as they are.
I still have some reservations about using HookError directly: imho, your example of apt module throwing HookError is a bad one: it should never do that, but should instead throw errors specific to that module. Callsite should catch that error, and if it's inside a hook/action, turn it into a HookError/
You are right about not catching general exceptions and letting them traceback, so I am not doing that.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean that we'd have same classes doing different things based on their environment: do you really foresee us having a hook code that should behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in multiple roles is more confusing, I am keeping Action* classes. I am depending on Hook* classes, but couldn't come up with a better way to extract common functionality without reworking everything, so left them as they are.
I still have some reservations about using HookError directly: imho, your example of apt module throwing HookError is a bad one: it should never do that, but should instead throw errors specific to that module. Callsite should catch that error, and if it's inside a hook/action, turn it into a HookError/
You are right about not catching general exceptions and letting them traceback, so I am not doing that.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean that we'd have same classes doing different things based on their environment: do you really foresee us having a hook code that should behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in multiple roles is more confusing, I am keeping Action* classes. I am depending on Hook* classes, but couldn't come up with a better way to extract common functionality without reworking everything, so left them as they are.
I still have some reservations about using HookError directly: imho, your example of apt module throwing HookError is a bad one: it should never do that, but should instead throw errors specific to that module. Callsite should catch that error, and if it's inside a hook/action, turn it into a HookError/
You are right about not catching general exceptions and letting them traceback, so I am not doing that.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean that we'd have same classes doing different things based on their environment: do you really foresee us having a hook code that should behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in multiple roles is more confusing, I am keeping Action* classes. I am depending on Hook* classes, but couldn't come up with a better way to extract common functionality without reworking everything, so left them as they are.
I still have some reservations about using HookError directly: imho, your example of apt module throwing HookError is a bad one: it should never do that, but should instead throw errors specific to that module. Callsite should catch that error, and if it's inside a hook/action, turn it into a HookError/
You are right about not catching general exceptions and letting them traceback, so I am not doing that.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean that we'd have same classes doing different things based on their environment: do you really foresee us having a hook code that should behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in multiple roles is more confusing, I am keeping Action* classes. I am depending on Hook* classes, but couldn't come up with a better way to extract common functionality without reworking everything, so left them as they are.
I still have some reservations about using HookError directly: imho, your example of apt module throwing HookError is a bad one: it should never do that, but should instead throw errors specific to that module. Callsite should catch that error, and if it's inside a hook/action, turn it into a HookError/
You are right about not catching general exceptions and letting them traceback, so I am not doing that.
| Данило Шеган (danilo) wrote : | # |
Thanks for commentary, Free. Basically, automatic behaviour would mean that we'd have same classes doing different things based on their environment: do you really foresee us having a hook code that should behave differently if it's ran as an action?
Since I assume no, and believe that having a single class act in multiple roles is more confusing, I am keeping Action* classes. I am depending on Hook* classes, but couldn't come up with a better way to extract common functionality without reworking everything, so left them as they are.
I still have some reservations about using HookError directly: imho, your example of apt module throwing HookError is a bad one: it should never do that, but should instead throw errors specific to that module. Callsite should catch that error, and if it's inside a hook/action, turn it into a HookError/
You are right about not catching general exceptions and letting them traceback, so I am not doing that.
Command: make ci-test
Result: Success
Revno: 302
Branch: lp:~danilo/landscape-charm/actions
Jenkins: https:/
| Данило Шеган (danilo) wrote : | # |
As discussed on IRC regarding structure of exceptions, we've agreed to meet mid-way: we'll introduce a base CharmError and make ActionError or HookError catch that, and each individual module can throw specific errors they want. If they need actions or hooks to gracefully fail, those exceptions need to inherit CharmError.
| Данило Шеган (danilo) wrote : | # |
With charm-errors landed, this is now ready for another round of reviews: it hasn't changed much, so I am not resubmitting (basically, just dropped ActionError and switched to CharmError in Action).
Command: make ci-test
Result: Success
Revno: 308
Branch: lp:~danilo/landscape-charm/actions
Jenkins: https:/
- 309. By Данило Шеган on 2015-06-05
-
Drop Action set/fail methods and use hookenv.action_set, action_fail directly.
- 310. By Данило Шеган on 2015-06-05
-
Make HookenvStub action_sets and action_fails public for use in tests: addresses review comments by Free.
| Данило Шеган (danilo) wrote : | # |
I've addressed your review comments, thanks Free.
Will do another round of testing before landing.
Command: make ci-test
Result: Success
Revno: 310
Branch: lp:~danilo/landscape-charm/actions
Jenkins: https:/

Command: make ci-test /ci.lscape. net/job/ latch-test/ 1166/
Result: Success
Revno: 301
Branch: lp:~danilo/landscape-charm/actions
Jenkins: https:/