Merge lp:~adam-collard/charms/trusty/swift-storage/add-pause-resume-actions into lp:~openstack-charmers-archive/charms/trusty/swift-storage/next
- Trusty Tahr (14.04)
- add-pause-resume-actions
- Merge into next
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~adam-collard/charms/trusty/swift-storage/add-pause-resume-actions | ||||
Merge into: | lp:~openstack-charmers-archive/charms/trusty/swift-storage/next | ||||
Diff against target: |
1145 lines (+603/-135) 19 files modified
.coveragerc (+1/-0) Makefile (+2/-0) actions.yaml (+5/-0) actions/actions.py (+93/-0) charmhelpers/cli/__init__.py (+1/-5) charmhelpers/cli/commands.py (+4/-4) charmhelpers/contrib/openstack/amulet/deployment.py (+2/-2) charmhelpers/contrib/openstack/utils.py (+51/-14) charmhelpers/contrib/storage/linux/utils.py (+3/-2) charmhelpers/core/hookenv.py (+1/-20) charmhelpers/core/host.py (+2/-2) charmhelpers/fetch/__init__.py (+8/-0) lib/swift_storage_utils.py (+1/-15) setup.cfg (+1/-1) tests/00-setup (+1/-0) tests/basic_deployment.py (+102/-47) tests/charmhelpers/contrib/amulet/utils.py (+84/-21) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+2/-2) unit_tests/test_actions.py (+239/-0) |
||||
To merge this branch: | bzr merge lp:~adam-collard/charms/trusty/swift-storage/add-pause-resume-actions | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Landscape | Pending | ||
OpenStack Charmers | Pending | ||
Review via email: mp+267681@code.launchpad.net |
This proposal has been superseded by a proposal from 2015-08-17.
Commit message
Description of the change
This branch adds actions for pausing and resuming services on swift-storage units.
In addition to just stopping the services, the actions also set the status of the unit to be "maintenance" with a message on how to resume - I'm not attached to the message, bikesheds welcome :).
Further refinements to the charm to support pause mode will be coming in follow-up branches, notably guarding calls to service_start and service_restart to prevent config-changed, *-relation-changed and other hooks from (re)-starting a unit which should be paused.
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7290 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7871 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7292 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5759 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
- 88. By Adam Collard
-
Fix flake8 findings
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7922 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7340 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5763 swift-storage-next for adam-collard mp267681
AMULET OK: passed
Build: http://
Ryan Beisner (1chb1n) wrote : | # |
RE: Amulet tests
Thank for your contribution and clean-up here. Backgrounder: I've not gotten down the list to swift-* charm amulet test updates, but intend to complete the vivid enablement in 15.08 and wily in 15.09. There are also a number of refactors and helper optimizations, which we're making across all of the os-charm amulet tests, so I'll be shifting these around a bit when I get down the list to the swift-*s.
1 easy change that I'd request: Please chmod -x tests/basic_
Take note that juju test (and bundletester) will juju bootstrap then execute anything in the tests/ dir which is +x, and that any yamls in the tests/ dir will be assumed by bundletester to be a test definition. While we are prepping to switch completely to bundletester, our first priority with the amulet tests is in updating the horizon of u:os test coverage to catch up with charm development efforts. Then we will resume the bundletester effort.
Thanks again, holler with any ?s.
- 89. By Adam Collard
-
Remove #! from basic_deployment.py (beisner's review)
- 90. By Adam Collard
-
chmod -x
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7926 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7344 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5767 swift-storage-next for adam-collard mp267681
AMULET OK: passed
Build: http://
- 91. By Adam Collard
-
Sync charm-helpers
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7927 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7345 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5768 swift-storage-next for adam-collard mp267681
AMULET OK: passed
Build: http://
- 92. By Adam Collard
-
Switch tests to using validate_
service_ by_name( ) - 93. By Adam Collard
-
WiP amulet test
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7929 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7347 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5770 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
- 94. By Adam Collard
-
validate_
serviceS_ by_name (fix typo)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7983 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7397 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5775 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 124
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
- 95. By Adam Collard
-
Move tests into basic_deployment so they get ran for each combination
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7985 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7399 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5777 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
- 96. By Adam Collard
-
Move actions test to the end so basic service tests are done upfront
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #7987 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7401 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5779 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Ryan Beisner (1chb1n) wrote : | # |
Hi. Kicking in with some hopefully helpful hints. I see the proposed test is failing when trying to confirm a system service that doesn't appear to be present at a particular release combo.
Since the basic_deployment.py set of tests is expected to pass for all supported release combos, each test_ method may need to use release detection logic as needed. In some cases, openstack components have shifted options, sections, etc., in conf files. In other cases, some system services may or may not be present across that entire timeline. There was particularly a lot of upstream shifting-around introduced at >= Kilo ... and < Icehouse.
As a usage example, the heat amulet test detects and checks things slightly differently here:
http://
Which ultimately uses this for comparison:
http://
- 97. By Adam Collard
-
Fix tests
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8042 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7453 swift-storage-next for adam-collard mp267681
UNIT OK: passed
Adam Collard (adam-collard) wrote : | # |
> Hi. Kicking in with some hopefully helpful hints. I see the proposed test is
> failing when trying to confirm a system service that doesn't appear to be
> present at a particular release combo.
That was my first guess too, fortunately (or unfortunately?) it wasn't that issue. The problem was the utils code for looking at what processes are running uses "pidof $name" but when $name is the name of a script it fails (using the -x option works as expected). Instead of yet another round of fixing charm-helpers, I opted to just roll my own for this test - there are comments in the test to further explain why.
In the absence of access to a testing environment that could satisfy the requirements, I was using UOSCI bot to run my tests for me. Now that's been resolved I have (hopefully!) got some green tests :)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5786 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
- 98. By Adam Collard
-
Only care about swift-container
-sync for >= Icehouse - 99. By Adam Collard
-
s/pop/remove (d'oh!)
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8044 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7455 swift-storage-next for adam-collard mp267681
UNIT OK: passed
- 100. By Adam Collard
-
More z's
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5788 swift-storage-next for adam-collard mp267681
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8045 swift-storage-next for adam-collard mp267681
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7456 swift-storage-next for adam-collard mp267681
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5789 swift-storage-next for adam-collard mp267681
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 124
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Ryan Beisner (1chb1n) wrote : | # |
RE: amulet tests
Thank you for this work!
Just a couple of things that will make future life easier:
_run_action and _wait_on_action:
Please consider landing these in charmhelpers/
_assert_services:
Please consider enhancing the existing validate_
- 101. By Adam Collard
-
Depend on amulet-
actions- helpers branch of charm-helpers, use those actin helpers (beisner's review) - 102. By Adam Collard
-
Point charm-helpers to latest branch, update tests to use helpers. Add comment about why validate_
unit_process_ ids is not helpful. - 103. By Adam Collard
-
run_action takes the sentry object, not the unit name
- 104. By Adam Collard
-
Use wait_on_action helper for resume test too
- 105. By Adam Collard
-
Sync charm-helpers
- 106. By Adam Collard
-
Revert to pointing at regular charm-helpers
- 107. By Adam Collard
-
Merge in from fix-service-status
Adam Collard (adam-collard) wrote : | # |
Hi Ryan,
Thanks for taking a look at this.
On Fri, 14 Aug 2015 at 17:10 Ryan Beisner <email address hidden>
wrote:
> RE: amulet tests
>
> Thank you for this work!
>
> Just a couple of things that will make future life easier:
>
> _run_action and _wait_on_action:
> Please consider landing these in charmhelpers/
> they will be increasingly-common needs.
>
Done. See charm-helpers r428
>
> _assert_services:
> Please consider enhancing the existing validate_
> helper to fit the needs here. I think it would be a fairly easy mod. We
> could do -x on pidof in all cases (or add a bool with default False to
> "check scripts too" if you want to be safer about it). Then add a boolean
> postive/negative switch to be able to invert its expectations (like you've
> done with should_run). fwiw, expect_success (default True) is such a bool
> name that I've used elsewhere for the same purpose in a different context
> (rmq wip).
>
I modified get_process_id_list and get_unit_
using validate_
me in the previous two methods. See comment in the test itself.
I've split the branch up so that the new code is cleanly separated from the
fixes I made to existing code (see pre-req branch).
--
>
> https:/
> You are the owner of
> lp:~adam-collard/charms/trusty/swift-storage/add-pause-resume-actions.
>
> Launchpad-
> Launchpad-
> Launchpad-Branch:
> ~adam-collard/
>
Unmerged revisions
Preview Diff
1 | === modified file '.coveragerc' |
2 | --- .coveragerc 2015-08-11 08:09:29 +0000 |
3 | +++ .coveragerc 2015-08-17 13:44:46 +0000 |
4 | @@ -3,5 +3,6 @@ |
5 | exclude_lines = |
6 | if __name__ == .__main__.: |
7 | include= |
8 | + actions/actions.py |
9 | hooks/swift_storage_* |
10 | lib/swift_storage_* |
11 | |
12 | === modified file 'Makefile' |
13 | --- Makefile 2015-08-04 11:48:35 +0000 |
14 | +++ Makefile 2015-08-17 13:44:46 +0000 |
15 | @@ -30,3 +30,5 @@ |
16 | publish: lint test |
17 | bzr push lp:charms/swift-storage |
18 | bzr push lp:charms/trusty/swift-storage |
19 | + |
20 | +.PHONY: lint unit_test test sync publish |
21 | |
22 | === added directory 'actions' |
23 | === added file 'actions.yaml' |
24 | --- actions.yaml 1970-01-01 00:00:00 +0000 |
25 | +++ actions.yaml 2015-08-17 13:44:46 +0000 |
26 | @@ -0,0 +1,5 @@ |
27 | +pause: |
28 | + description: Pause the swift-storage unit. This action will stop Swift services. |
29 | +resume: |
30 | + description: Resume the swift-storage unit. This action will start Swift services. |
31 | + |
32 | |
33 | === added file 'actions/__init__.py' |
34 | === added file 'actions/actions.py' |
35 | --- actions/actions.py 1970-01-01 00:00:00 +0000 |
36 | +++ actions/actions.py 2015-08-17 13:44:46 +0000 |
37 | @@ -0,0 +1,93 @@ |
38 | +#!/usr/bin/python |
39 | + |
40 | +import argparse |
41 | +import os |
42 | +import sys |
43 | +import yaml |
44 | + |
45 | +from charmhelpers.core.host import service_pause, service_resume |
46 | +from charmhelpers.core.hookenv import action_fail, status_set |
47 | +from charmhelpers.contrib.openstack.utils import get_os_codename_package |
48 | + |
49 | +from lib.swift_storage_utils import SWIFT_SVCS |
50 | + |
51 | + |
52 | +def _get_services(): |
53 | + """Return a list of services that need to be (un)paused.""" |
54 | + services = SWIFT_SVCS[:] |
55 | + # Before Icehouse there was no swift-container-sync |
56 | + if get_os_codename_package("swift-container") < "icehouse": |
57 | + services.remove("swift-container-sync") |
58 | + return services |
59 | + |
60 | + |
61 | +def get_action_parser(actions_yaml_path, action_name, |
62 | + get_services=_get_services): |
63 | + """Make an argparse.ArgumentParser seeded from actions.yaml definitions.""" |
64 | + with open(actions_yaml_path) as fh: |
65 | + doc = yaml.load(fh)[action_name]["description"] |
66 | + parser = argparse.ArgumentParser(description=doc) |
67 | + parser.add_argument("--services", default=get_services()) |
68 | + # TODO: Add arguments for params defined in the actions.yaml |
69 | + return parser |
70 | + |
71 | + |
72 | +def pause(args): |
73 | + """Pause all the swift services. |
74 | + |
75 | + @raises Exception if any services fail to stop |
76 | + """ |
77 | + for service in args.services: |
78 | + stopped = service_pause(service) |
79 | + if not stopped: |
80 | + raise Exception("{} didn't stop cleanly.".format(service)) |
81 | + status_set( |
82 | + "maintenance", "Paused. Use 'resume' action to resume normal service.") |
83 | + |
84 | + |
85 | +def resume(args): |
86 | + """Resume all the swift services. |
87 | + |
88 | + @raises Exception if any services fail to start |
89 | + """ |
90 | + for service in args.services: |
91 | + started = service_resume(service) |
92 | + if not started: |
93 | + raise Exception("{} didn't start cleanly.".format(service)) |
94 | + status_set("active", "") |
95 | + |
96 | + |
97 | +# A dictionary of all the defined actions to callables (which take |
98 | +# parsed arguments). |
99 | +ACTIONS = {"pause": pause, "resume": resume} |
100 | + |
101 | + |
102 | +def main(argv): |
103 | + action_name = _get_action_name() |
104 | + actions_yaml_path = _get_actions_yaml_path() |
105 | + parser = get_action_parser(actions_yaml_path, action_name) |
106 | + args = parser.parse_args(argv) |
107 | + try: |
108 | + action = ACTIONS[action_name] |
109 | + except KeyError: |
110 | + return "Action %s undefined" % action_name |
111 | + else: |
112 | + try: |
113 | + action(args) |
114 | + except Exception as e: |
115 | + action_fail(str(e)) |
116 | + |
117 | + |
118 | +def _get_action_name(): |
119 | + """Return the name of the action.""" |
120 | + return os.path.basename(__file__) |
121 | + |
122 | + |
123 | +def _get_actions_yaml_path(): |
124 | + """Return the path to actions.yaml""" |
125 | + cwd = os.path.dirname(__file__) |
126 | + return os.path.join(cwd, "..", "actions.yaml") |
127 | + |
128 | + |
129 | +if __name__ == "__main__": |
130 | + sys.exit(main(sys.argv[1:])) |
131 | |
132 | === added symlink 'actions/charmhelpers' |
133 | === target is u'../charmhelpers/' |
134 | === added symlink 'actions/lib' |
135 | === target is u'../lib' |
136 | === added symlink 'actions/pause' |
137 | === target is u'actions.py' |
138 | === added symlink 'actions/resume' |
139 | === target is u'actions.py' |
140 | === modified file 'charmhelpers/cli/__init__.py' |
141 | --- charmhelpers/cli/__init__.py 2015-07-31 13:11:32 +0000 |
142 | +++ charmhelpers/cli/__init__.py 2015-08-17 13:44:46 +0000 |
143 | @@ -152,15 +152,11 @@ |
144 | arguments = self.argument_parser.parse_args() |
145 | argspec = inspect.getargspec(arguments.func) |
146 | vargs = [] |
147 | - kwargs = {} |
148 | for arg in argspec.args: |
149 | vargs.append(getattr(arguments, arg)) |
150 | if argspec.varargs: |
151 | vargs.extend(getattr(arguments, argspec.varargs)) |
152 | - if argspec.keywords: |
153 | - for kwarg in argspec.keywords.items(): |
154 | - kwargs[kwarg] = getattr(arguments, kwarg) |
155 | - output = arguments.func(*vargs, **kwargs) |
156 | + output = arguments.func(*vargs) |
157 | if getattr(arguments.func, '_cli_test_command', False): |
158 | self.exit_code = 0 if output else 1 |
159 | output = '' |
160 | |
161 | === modified file 'charmhelpers/cli/commands.py' |
162 | --- charmhelpers/cli/commands.py 2015-07-31 13:11:32 +0000 |
163 | +++ charmhelpers/cli/commands.py 2015-08-17 13:44:46 +0000 |
164 | @@ -26,7 +26,7 @@ |
165 | """ |
166 | Import the sub-modules which have decorated subcommands to register with chlp. |
167 | """ |
168 | -import host # noqa |
169 | -import benchmark # noqa |
170 | -import unitdata # noqa |
171 | -from charmhelpers.core import hookenv # noqa |
172 | +from . import host # noqa |
173 | +from . import benchmark # noqa |
174 | +from . import unitdata # noqa |
175 | +from . import hookenv # noqa |
176 | |
177 | === modified file 'charmhelpers/contrib/openstack/amulet/deployment.py' |
178 | --- charmhelpers/contrib/openstack/amulet/deployment.py 2015-07-29 10:49:43 +0000 |
179 | +++ charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-17 13:44:46 +0000 |
180 | @@ -44,7 +44,7 @@ |
181 | Determine if the local branch being tested is derived from its |
182 | stable or next (dev) branch, and based on this, use the corresonding |
183 | stable or next branches for the other_services.""" |
184 | - base_charms = ['mysql', 'mongodb'] |
185 | + base_charms = ['mysql', 'mongodb', 'nrpe'] |
186 | |
187 | if self.series in ['precise', 'trusty']: |
188 | base_series = self.series |
189 | @@ -81,7 +81,7 @@ |
190 | 'ceph-osd', 'ceph-radosgw'] |
191 | # Most OpenStack subordinate charms do not expose an origin option |
192 | # as that is controlled by the principle. |
193 | - ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch'] |
194 | + ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] |
195 | |
196 | if self.openstack: |
197 | for svc in services: |
198 | |
199 | === modified file 'charmhelpers/contrib/openstack/utils.py' |
200 | --- charmhelpers/contrib/openstack/utils.py 2015-07-29 10:49:43 +0000 |
201 | +++ charmhelpers/contrib/openstack/utils.py 2015-08-17 13:44:46 +0000 |
202 | @@ -24,6 +24,7 @@ |
203 | import json |
204 | import os |
205 | import sys |
206 | +import re |
207 | |
208 | import six |
209 | import yaml |
210 | @@ -69,7 +70,6 @@ |
211 | DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed ' |
212 | 'restricted main multiverse universe') |
213 | |
214 | - |
215 | UBUNTU_OPENSTACK_RELEASE = OrderedDict([ |
216 | ('oneiric', 'diablo'), |
217 | ('precise', 'essex'), |
218 | @@ -118,6 +118,34 @@ |
219 | ('2.3.0', 'liberty'), |
220 | ]) |
221 | |
222 | +# >= Liberty version->codename mapping |
223 | +PACKAGE_CODENAMES = { |
224 | + 'nova-common': OrderedDict([ |
225 | + ('12.0.0', 'liberty'), |
226 | + ]), |
227 | + 'neutron-common': OrderedDict([ |
228 | + ('7.0.0', 'liberty'), |
229 | + ]), |
230 | + 'cinder-common': OrderedDict([ |
231 | + ('7.0.0', 'liberty'), |
232 | + ]), |
233 | + 'keystone': OrderedDict([ |
234 | + ('8.0.0', 'liberty'), |
235 | + ]), |
236 | + 'horizon-common': OrderedDict([ |
237 | + ('8.0.0', 'liberty'), |
238 | + ]), |
239 | + 'ceilometer-common': OrderedDict([ |
240 | + ('5.0.0', 'liberty'), |
241 | + ]), |
242 | + 'heat-common': OrderedDict([ |
243 | + ('5.0.0', 'liberty'), |
244 | + ]), |
245 | + 'glance-common': OrderedDict([ |
246 | + ('11.0.0', 'liberty'), |
247 | + ]), |
248 | +} |
249 | + |
250 | DEFAULT_LOOPBACK_SIZE = '5G' |
251 | |
252 | |
253 | @@ -201,20 +229,29 @@ |
254 | error_out(e) |
255 | |
256 | vers = apt.upstream_version(pkg.current_ver.ver_str) |
257 | + match = re.match('^(\d)\.(\d)\.(\d)', vers) |
258 | + if match: |
259 | + vers = match.group(0) |
260 | |
261 | - try: |
262 | - if 'swift' in pkg.name: |
263 | - swift_vers = vers[:5] |
264 | - if swift_vers not in SWIFT_CODENAMES: |
265 | - # Deal with 1.10.0 upward |
266 | - swift_vers = vers[:6] |
267 | - return SWIFT_CODENAMES[swift_vers] |
268 | - else: |
269 | - vers = vers[:6] |
270 | - return OPENSTACK_CODENAMES[vers] |
271 | - except KeyError: |
272 | - e = 'Could not determine OpenStack codename for version %s' % vers |
273 | - error_out(e) |
274 | + # >= Liberty independent project versions |
275 | + if (package in PACKAGE_CODENAMES and |
276 | + vers in PACKAGE_CODENAMES[package]): |
277 | + return PACKAGE_CODENAMES[package][vers] |
278 | + else: |
279 | + # < Liberty co-ordinated project versions |
280 | + try: |
281 | + if 'swift' in pkg.name: |
282 | + swift_vers = vers[:5] |
283 | + if swift_vers not in SWIFT_CODENAMES: |
284 | + # Deal with 1.10.0 upward |
285 | + swift_vers = vers[:6] |
286 | + return SWIFT_CODENAMES[swift_vers] |
287 | + else: |
288 | + vers = vers[:6] |
289 | + return OPENSTACK_CODENAMES[vers] |
290 | + except KeyError: |
291 | + e = 'Could not determine OpenStack codename for version %s' % vers |
292 | + error_out(e) |
293 | |
294 | |
295 | def get_os_version_package(pkg, fatal=True): |
296 | |
297 | === modified file 'charmhelpers/contrib/storage/linux/utils.py' |
298 | --- charmhelpers/contrib/storage/linux/utils.py 2015-07-29 10:49:43 +0000 |
299 | +++ charmhelpers/contrib/storage/linux/utils.py 2015-08-17 13:44:46 +0000 |
300 | @@ -43,9 +43,10 @@ |
301 | |
302 | :param block_device: str: Full path of block device to clean. |
303 | ''' |
304 | + # https://github.com/ceph/ceph/commit/fdd7f8d83afa25c4e09aaedd90ab93f3b64a677b |
305 | # sometimes sgdisk exits non-zero; this is OK, dd will clean up |
306 | - call(['sgdisk', '--zap-all', '--mbrtogpt', |
307 | - '--clear', block_device]) |
308 | + call(['sgdisk', '--zap-all', '--', block_device]) |
309 | + call(['sgdisk', '--clear', '--mbrtogpt', '--', block_device]) |
310 | dev_end = check_output(['blockdev', '--getsz', |
311 | block_device]).decode('UTF-8') |
312 | gpt_end = int(dev_end.split()[0]) - 100 |
313 | |
314 | === modified file 'charmhelpers/core/hookenv.py' |
315 | --- charmhelpers/core/hookenv.py 2015-08-03 14:00:44 +0000 |
316 | +++ charmhelpers/core/hookenv.py 2015-08-17 13:44:46 +0000 |
317 | @@ -34,23 +34,6 @@ |
318 | import tempfile |
319 | from subprocess import CalledProcessError |
320 | |
321 | -try: |
322 | - from charmhelpers.cli import cmdline |
323 | -except ImportError as e: |
324 | - # due to the anti-pattern of partially synching charmhelpers directly |
325 | - # into charms, it's possible that charmhelpers.cli is not available; |
326 | - # if that's the case, they don't really care about using the cli anyway, |
327 | - # so mock it out |
328 | - if str(e) == 'No module named cli': |
329 | - class cmdline(object): |
330 | - @classmethod |
331 | - def subcommand(cls, *args, **kwargs): |
332 | - def _wrap(func): |
333 | - return func |
334 | - return _wrap |
335 | - else: |
336 | - raise |
337 | - |
338 | import six |
339 | if not six.PY3: |
340 | from UserDict import UserDict |
341 | @@ -91,6 +74,7 @@ |
342 | res = func(*args, **kwargs) |
343 | cache[key] = res |
344 | return res |
345 | + wrapper._wrapped = func |
346 | return wrapper |
347 | |
348 | |
349 | @@ -190,7 +174,6 @@ |
350 | return os.environ.get('JUJU_RELATION', None) |
351 | |
352 | |
353 | -@cmdline.subcommand() |
354 | @cached |
355 | def relation_id(relation_name=None, service_or_unit=None): |
356 | """The relation ID for the current or a specified relation""" |
357 | @@ -216,13 +199,11 @@ |
358 | return os.environ.get('JUJU_REMOTE_UNIT', None) |
359 | |
360 | |
361 | -@cmdline.subcommand() |
362 | def service_name(): |
363 | """The name service group this unit belongs to""" |
364 | return local_unit().split('/')[0] |
365 | |
366 | |
367 | -@cmdline.subcommand() |
368 | @cached |
369 | def remote_service_name(relid=None): |
370 | """The remote service name for a given relation-id (or the current relation)""" |
371 | |
372 | === modified file 'charmhelpers/core/host.py' |
373 | --- charmhelpers/core/host.py 2015-07-29 10:49:43 +0000 |
374 | +++ charmhelpers/core/host.py 2015-08-17 13:44:46 +0000 |
375 | @@ -72,7 +72,7 @@ |
376 | stopped = service_stop(service_name) |
377 | # XXX: Support systemd too |
378 | override_path = os.path.join( |
379 | - init_dir, '{}.conf.override'.format(service_name)) |
380 | + init_dir, '{}.override'.format(service_name)) |
381 | with open(override_path, 'w') as fh: |
382 | fh.write("manual\n") |
383 | return stopped |
384 | @@ -86,7 +86,7 @@ |
385 | if init_dir is None: |
386 | init_dir = "/etc/init" |
387 | override_path = os.path.join( |
388 | - init_dir, '{}.conf.override'.format(service_name)) |
389 | + init_dir, '{}.override'.format(service_name)) |
390 | if os.path.exists(override_path): |
391 | os.unlink(override_path) |
392 | started = service_start(service_name) |
393 | |
394 | === modified file 'charmhelpers/fetch/__init__.py' |
395 | --- charmhelpers/fetch/__init__.py 2015-07-29 10:49:43 +0000 |
396 | +++ charmhelpers/fetch/__init__.py 2015-08-17 13:44:46 +0000 |
397 | @@ -90,6 +90,14 @@ |
398 | 'kilo/proposed': 'trusty-proposed/kilo', |
399 | 'trusty-kilo/proposed': 'trusty-proposed/kilo', |
400 | 'trusty-proposed/kilo': 'trusty-proposed/kilo', |
401 | + # Liberty |
402 | + 'liberty': 'trusty-updates/liberty', |
403 | + 'trusty-liberty': 'trusty-updates/liberty', |
404 | + 'trusty-liberty/updates': 'trusty-updates/liberty', |
405 | + 'trusty-updates/liberty': 'trusty-updates/liberty', |
406 | + 'liberty/proposed': 'trusty-proposed/liberty', |
407 | + 'trusty-liberty/proposed': 'trusty-proposed/liberty', |
408 | + 'trusty-proposed/liberty': 'trusty-proposed/liberty', |
409 | } |
410 | |
411 | # The order of this list is very important. Handlers should be listed in from |
412 | |
413 | === modified file 'lib/swift_storage_utils.py' |
414 | --- lib/swift_storage_utils.py 2015-07-17 09:57:19 +0000 |
415 | +++ lib/swift_storage_utils.py 2015-08-17 13:44:46 +0000 |
416 | @@ -83,21 +83,7 @@ |
417 | 'swift-object-updater', 'swift-object-replicator' |
418 | ] |
419 | |
420 | -SWIFT_SVCS = [ |
421 | - 'swift-account-auditor', |
422 | - 'swift-account-reaper', |
423 | - 'swift-account-replicator', |
424 | - 'swift-account-server', |
425 | - 'swift-container-auditor', |
426 | - 'swift-container-replicator', |
427 | - 'swift-container-server', |
428 | - 'swift-container-sync', |
429 | - 'swift-container-updater', |
430 | - 'swift-object-auditor', |
431 | - 'swift-object-replicator', |
432 | - 'swift-object-server', |
433 | - 'swift-object-updater', |
434 | - ] |
435 | +SWIFT_SVCS = ACCOUNT_SVCS + CONTAINER_SVCS + OBJECT_SVCS |
436 | |
437 | RESTART_MAP = { |
438 | '/etc/rsync-juju.d/050-swift-storage.conf': ['rsync'], |
439 | |
440 | === modified file 'setup.cfg' |
441 | --- setup.cfg 2015-08-11 08:09:29 +0000 |
442 | +++ setup.cfg 2015-08-17 13:44:46 +0000 |
443 | @@ -2,5 +2,5 @@ |
444 | verbosity=2 |
445 | with-coverage=1 |
446 | cover-erase=1 |
447 | -cover-package=lib,hooks.swift_storage_hooks |
448 | +cover-package=actions.actions,lib,hooks.swift_storage_hooks |
449 | |
450 | |
451 | === modified file 'tests/00-setup' |
452 | --- tests/00-setup 2014-09-29 21:14:05 +0000 |
453 | +++ tests/00-setup 2015-08-17 13:44:46 +0000 |
454 | @@ -7,5 +7,6 @@ |
455 | sudo apt-get install --yes python-amulet \ |
456 | python-swiftclient \ |
457 | python-glanceclient \ |
458 | + python-heatclient \ |
459 | python-keystoneclient \ |
460 | python-novaclient |
461 | |
462 | === modified file 'tests/basic_deployment.py' |
463 | --- tests/basic_deployment.py 2015-04-16 21:31:35 +0000 |
464 | +++ tests/basic_deployment.py 2015-08-17 13:44:46 +0000 |
465 | @@ -1,5 +1,3 @@ |
466 | -#!/usr/bin/python |
467 | - |
468 | import amulet |
469 | import swiftclient |
470 | |
471 | @@ -9,8 +7,7 @@ |
472 | |
473 | from charmhelpers.contrib.openstack.amulet.utils import ( |
474 | OpenStackAmuletUtils, |
475 | - DEBUG, # flake8: noqa |
476 | - ERROR |
477 | + DEBUG, |
478 | ) |
479 | |
480 | # Use DEBUG to turn on debug logging |
481 | @@ -46,12 +43,12 @@ |
482 | def _add_relations(self): |
483 | """Add all of the relations for the services.""" |
484 | relations = { |
485 | - 'keystone:shared-db': 'mysql:shared-db', |
486 | - 'swift-proxy:identity-service': 'keystone:identity-service', |
487 | - 'swift-storage:swift-storage': 'swift-proxy:swift-storage', |
488 | - 'glance:identity-service': 'keystone:identity-service', |
489 | - 'glance:shared-db': 'mysql:shared-db', |
490 | - 'glance:object-store': 'swift-proxy:object-store' |
491 | + 'keystone:shared-db': 'mysql:shared-db', |
492 | + 'swift-proxy:identity-service': 'keystone:identity-service', |
493 | + 'swift-storage:swift-storage': 'swift-proxy:swift-storage', |
494 | + 'glance:identity-service': 'keystone:identity-service', |
495 | + 'glance:shared-db': 'mysql:shared-db', |
496 | + 'glance:object-store': 'swift-proxy:object-store' |
497 | } |
498 | super(SwiftStorageBasicDeployment, self)._add_relations(relations) |
499 | |
500 | @@ -59,9 +56,11 @@ |
501 | """Configure all of the services.""" |
502 | keystone_config = {'admin-password': 'openstack', |
503 | 'admin-token': 'ubuntutesting'} |
504 | - swift_proxy_config = {'zone-assignment': 'manual', |
505 | - 'replicas': '1', |
506 | - 'swift-hash': 'fdfef9d4-8b06-11e2-8ac0-531c923c8fae'} |
507 | + swift_proxy_config = { |
508 | + 'zone-assignment': 'manual', |
509 | + 'replicas': '1', |
510 | + 'swift-hash': 'fdfef9d4-8b06-11e2-8ac0-531c923c8fae' |
511 | + } |
512 | swift_storage_config = {'zone': '1', |
513 | 'block-device': 'vdb', |
514 | 'overwrite': 'true'} |
515 | @@ -89,15 +88,16 @@ |
516 | self.glance = u.authenticate_glance_admin(self.keystone) |
517 | |
518 | # Authenticate swift user |
519 | - keystone_relation = self.keystone_sentry.relation('identity-service', |
520 | - 'swift-proxy:identity-service') |
521 | + keystone_relation = self.keystone_sentry.relation( |
522 | + 'identity-service', 'swift-proxy:identity-service') |
523 | ep = self.keystone.service_catalog.url_for(service_type='identity', |
524 | endpoint_type='publicURL') |
525 | - self.swift = swiftclient.Connection(authurl=ep, |
526 | - user=keystone_relation['service_username'], |
527 | - key=keystone_relation['service_password'], |
528 | - tenant_name=keystone_relation['service_tenant'], |
529 | - auth_version='2.0') |
530 | + self.swift = swiftclient.Connection( |
531 | + authurl=ep, |
532 | + user=keystone_relation['service_username'], |
533 | + key=keystone_relation['service_password'], |
534 | + tenant_name=keystone_relation['service_tenant'], |
535 | + auth_version='2.0') |
536 | |
537 | # Create a demo tenant/role/user |
538 | self.demo_tenant = 'demoTenant' |
539 | @@ -122,29 +122,30 @@ |
540 | def test_services(self): |
541 | """Verify the expected services are running on the corresponding |
542 | service units.""" |
543 | - swift_storage_services = ['status swift-account', |
544 | - 'status swift-account-auditor', |
545 | - 'status swift-account-reaper', |
546 | - 'status swift-account-replicator', |
547 | - 'status swift-container', |
548 | - 'status swift-container-auditor', |
549 | - 'status swift-container-replicator', |
550 | - 'status swift-container-updater', |
551 | - 'status swift-object', |
552 | - 'status swift-object-auditor', |
553 | - 'status swift-object-replicator', |
554 | - 'status swift-object-updater'] |
555 | + swift_storage_services = ['swift-account', |
556 | + 'swift-account-auditor', |
557 | + 'swift-account-reaper', |
558 | + 'swift-account-replicator', |
559 | + 'swift-container', |
560 | + 'swift-container-auditor', |
561 | + 'swift-container-replicator', |
562 | + 'swift-container-updater', |
563 | + 'swift-object', |
564 | + 'swift-object-auditor', |
565 | + 'swift-object-replicator', |
566 | + 'swift-object-updater'] |
567 | if self._get_openstack_release() >= self.precise_icehouse: |
568 | - swift_storage_services.append('status swift-container-sync') |
569 | - commands = { |
570 | - self.mysql_sentry: ['status mysql'], |
571 | - self.keystone_sentry: ['status keystone'], |
572 | - self.glance_sentry: ['status glance-registry', 'status glance-api'], |
573 | - self.swift_proxy_sentry: ['status swift-proxy'], |
574 | + swift_storage_services.append('swift-container-sync') |
575 | + service_names = { |
576 | + self.mysql_sentry: ['mysql'], |
577 | + self.keystone_sentry: ['keystone'], |
578 | + self.glance_sentry: [ |
579 | + 'glance-registry', 'glance-api'], |
580 | + self.swift_proxy_sentry: ['swift-proxy'], |
581 | self.swift_storage_sentry: swift_storage_services |
582 | } |
583 | |
584 | - ret = u.validate_services(commands) |
585 | + ret = u.validate_services_by_name(service_names) |
586 | if ret: |
587 | amulet.raise_status(amulet.FAIL, msg=ret) |
588 | |
589 | @@ -303,8 +304,8 @@ |
590 | file.""" |
591 | unit = self.swift_storage_sentry |
592 | conf = '/etc/swift/swift.conf' |
593 | - swift_proxy_relation = self.swift_proxy_sentry.relation('swift-storage', |
594 | - 'swift-storage:swift-storage') |
595 | + swift_proxy_relation = self.swift_proxy_sentry.relation( |
596 | + 'swift-storage', 'swift-storage:swift-storage') |
597 | expected = { |
598 | 'swift_hash_path_suffix': swift_proxy_relation['swift_hash'] |
599 | } |
600 | @@ -405,7 +406,8 @@ |
601 | |
602 | def test_image_create(self): |
603 | """Create an instance in glance, which is backed by swift, and validate |
604 | - that some of the metadata for the image match in glance and swift.""" |
605 | + that some of the metadata for the image match in glance and swift. |
606 | + """ |
607 | # NOTE(coreycb): Skipping failing test on folsom until resolved. On |
608 | # folsom only, uploading an image to glance gets 400 Bad |
609 | # Request - Error uploading image: (error): [Errno 111] |
610 | @@ -435,7 +437,8 @@ |
611 | # Validate that swift object's checksum/size match that from glance |
612 | headers, containers = self.swift.get_account() |
613 | if len(containers) != 1: |
614 | - msg = "Expected 1 swift container, found {}".format(len(containers)) |
615 | + msg = "Expected 1 swift container, found {}".format( |
616 | + len(containers)) |
617 | amulet.raise_status(amulet.FAIL, msg=msg) |
618 | |
619 | container_name = containers[0].get('name') |
620 | @@ -449,14 +452,66 @@ |
621 | swift_object_md5 = objects[0].get('hash') |
622 | |
623 | if glance_image_size != swift_object_size: |
624 | - msg = "Glance image size {} != swift object size {}".format( \ |
625 | - glance_image_size, swift_object_size) |
626 | + msg = "Glance image size {} != swift object size {}".format( |
627 | + glance_image_size, swift_object_size) |
628 | amulet.raise_status(amulet.FAIL, msg=msg) |
629 | |
630 | if glance_image_md5 != swift_object_md5: |
631 | - msg = "Glance image hash {} != swift object hash {}".format( \ |
632 | - glance_image_md5, swift_object_md5) |
633 | + msg = "Glance image hash {} != swift object hash {}".format( |
634 | + glance_image_md5, swift_object_md5) |
635 | amulet.raise_status(amulet.FAIL, msg=msg) |
636 | |
637 | # Cleanup |
638 | u.delete_image(self.glance, image) |
639 | + |
640 | + def _assert_services(self, should_run): |
641 | + swift_storage_services = ['swift-account-auditor', |
642 | + 'swift-account-reaper', |
643 | + 'swift-account-replicator', |
644 | + 'swift-account-server', |
645 | + 'swift-container-auditor', |
646 | + 'swift-container-replicator', |
647 | + 'swift-container-server', |
648 | + 'swift-container-sync', |
649 | + 'swift-container-updater', |
650 | + 'swift-object-auditor', |
651 | + 'swift-object-replicator', |
652 | + 'swift-object-server', |
653 | + 'swift-object-updater'] |
654 | + if self._get_openstack_release() < self.precise_icehouse: |
655 | + swift_storage_services.remove('swift-container-sync') |
656 | + |
657 | + u.get_unit_process_ids( |
658 | + {self.swift_storage_sentry: swift_storage_services}, |
659 | + expect_success=should_run) |
660 | + # No point using validate_unit_process_ids, since we don't |
661 | + # care about how many PIDs, merely that they're running, so |
662 | + # would populate expected with either True or False. This |
663 | + # validation is already performed in get_process_id_list |
664 | + |
665 | + def _test_pause(self): |
666 | + u.log.info("Testing pause action") |
667 | + self._assert_services(should_run=True) |
668 | + pause_action_id = u.run_action(self.swift_storage_sentry, "pause") |
669 | + assert u.wait_on_action(pause_action_id), "Pause action failed." |
670 | + |
671 | + self._assert_services(should_run=False) |
672 | + |
673 | + def _test_resume(self): |
674 | + u.log.info("Testing resume action") |
675 | + # service is left paused by _test_pause |
676 | + self._assert_services(should_run=False) |
677 | + resume_action_id = u.run_action(self.swift_storage_sentry, "resume") |
678 | + assert u.wait_on_action(resume_action_id), "Resume action failed." |
679 | + |
680 | + self._assert_services(should_run=True) |
681 | + |
682 | + def test_z_actions(self): |
683 | + """Pause and then resume swift-storage. |
684 | + |
685 | + Note(sparkiegeek): The method name with the _z_ is a little odd |
686 | + but it forces the test to run last. It just makes things |
687 | + easier because restarting services requires re-authorization. |
688 | + """ |
689 | + self._test_pause() |
690 | + self._test_resume() |
691 | |
692 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' |
693 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-07-29 10:49:43 +0000 |
694 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-08-17 13:44:46 +0000 |
695 | @@ -14,17 +14,23 @@ |
696 | # You should have received a copy of the GNU Lesser General Public License |
697 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
698 | |
699 | -import amulet |
700 | -import ConfigParser |
701 | -import distro_info |
702 | import io |
703 | +import json |
704 | import logging |
705 | import os |
706 | import re |
707 | -import six |
708 | +import subprocess |
709 | import sys |
710 | import time |
711 | -import urlparse |
712 | + |
713 | +import amulet |
714 | +import distro_info |
715 | +import six |
716 | +from six.moves import configparser |
717 | +if six.PY3: |
718 | + from urllib import parse as urlparse |
719 | +else: |
720 | + import urlparse |
721 | |
722 | |
723 | class AmuletUtils(object): |
724 | @@ -142,19 +148,23 @@ |
725 | |
726 | for service_name in services_list: |
727 | if (self.ubuntu_releases.index(release) >= systemd_switch or |
728 | - service_name == "rabbitmq-server"): |
729 | - # init is systemd |
730 | + service_name in ['rabbitmq-server', 'apache2']): |
731 | + # init is systemd (or regular sysv) |
732 | cmd = 'sudo service {} status'.format(service_name) |
733 | + output, code = sentry_unit.run(cmd) |
734 | + service_running = code == 0 |
735 | elif self.ubuntu_releases.index(release) < systemd_switch: |
736 | # init is upstart |
737 | cmd = 'sudo status {}'.format(service_name) |
738 | + output, code = sentry_unit.run(cmd) |
739 | + service_running = code == 0 and "start/running" in output |
740 | |
741 | - output, code = sentry_unit.run(cmd) |
742 | self.log.debug('{} `{}` returned ' |
743 | '{}'.format(sentry_unit.info['unit_name'], |
744 | cmd, code)) |
745 | - if code != 0: |
746 | - return "command `{}` returned {}".format(cmd, str(code)) |
747 | + if not service_running: |
748 | + return u"command `{}` returned {} {}".format( |
749 | + cmd, output, str(code)) |
750 | return None |
751 | |
752 | def _get_config(self, unit, filename): |
753 | @@ -164,7 +174,7 @@ |
754 | # NOTE(beisner): by default, ConfigParser does not handle options |
755 | # with no value, such as the flags used in the mysql my.cnf file. |
756 | # https://bugs.python.org/issue7005 |
757 | - config = ConfigParser.ConfigParser(allow_no_value=True) |
758 | + config = configparser.ConfigParser(allow_no_value=True) |
759 | config.readfp(io.StringIO(file_contents)) |
760 | return config |
761 | |
762 | @@ -450,15 +460,20 @@ |
763 | cmd, code, output)) |
764 | return None |
765 | |
766 | - def get_process_id_list(self, sentry_unit, process_name): |
767 | + def get_process_id_list(self, sentry_unit, process_name, |
768 | + expect_success=True): |
769 | """Get a list of process ID(s) from a single sentry juju unit |
770 | for a single process name. |
771 | |
772 | - :param sentry_unit: Pointer to amulet sentry instance (juju unit) |
773 | + :param sentry_unit: Amulet sentry instance (juju unit) |
774 | :param process_name: Process name |
775 | + :param expect_success: If False, expect the PID to be missing, |
776 | + raise if it is present. |
777 | :returns: List of process IDs |
778 | """ |
779 | - cmd = 'pidof {}'.format(process_name) |
780 | + cmd = 'pidof -x {}'.format(process_name) |
781 | + if not expect_success: |
782 | + cmd += " || exit 0 && exit 1" |
783 | output, code = sentry_unit.run(cmd) |
784 | if code != 0: |
785 | msg = ('{} `{}` returned {} ' |
786 | @@ -467,14 +482,23 @@ |
787 | amulet.raise_status(amulet.FAIL, msg=msg) |
788 | return str(output).split() |
789 | |
790 | - def get_unit_process_ids(self, unit_processes): |
791 | + def get_unit_process_ids(self, unit_processes, expect_success=True): |
792 | """Construct a dict containing unit sentries, process names, and |
793 | - process IDs.""" |
794 | + process IDs. |
795 | + |
796 | + :param unit_processes: A dictionary of Amulet sentry instance |
797 | + to list of process names. |
798 | + :param expect_success: if False expect the processes to not be |
799 | + running, raise if they are. |
800 | + :returns: Dictionary of Amulet sentry instance to dictionary |
801 | + of process names to PIDs. |
802 | + """ |
803 | pid_dict = {} |
804 | - for sentry_unit, process_list in unit_processes.iteritems(): |
805 | + for sentry_unit, process_list in six.iteritems(unit_processes): |
806 | pid_dict[sentry_unit] = {} |
807 | for process in process_list: |
808 | - pids = self.get_process_id_list(sentry_unit, process) |
809 | + pids = self.get_process_id_list( |
810 | + sentry_unit, process, expect_success=expect_success) |
811 | pid_dict[sentry_unit].update({process: pids}) |
812 | return pid_dict |
813 | |
814 | @@ -488,7 +512,7 @@ |
815 | return ('Unit count mismatch. expected, actual: {}, ' |
816 | '{} '.format(len(expected), len(actual))) |
817 | |
818 | - for (e_sentry, e_proc_names) in expected.iteritems(): |
819 | + for (e_sentry, e_proc_names) in six.iteritems(expected): |
820 | e_sentry_name = e_sentry.info['unit_name'] |
821 | if e_sentry in actual.keys(): |
822 | a_proc_names = actual[e_sentry] |
823 | @@ -507,11 +531,23 @@ |
824 | '{}'.format(e_proc_name, a_proc_name)) |
825 | |
826 | a_pids_length = len(a_pids) |
827 | - if e_pids_length != a_pids_length: |
828 | - return ('PID count mismatch. {} ({}) expected, actual: ' |
829 | + fail_msg = ('PID count mismatch. {} ({}) expected, actual: ' |
830 | '{}, {} ({})'.format(e_sentry_name, e_proc_name, |
831 | e_pids_length, a_pids_length, |
832 | a_pids)) |
833 | + |
834 | + # If expected is not bool, ensure PID quantities match |
835 | + if not isinstance(e_pids_length, bool) and \ |
836 | + a_pids_length != e_pids_length: |
837 | + return fail_msg |
838 | + # If expected is bool True, ensure 1 or more PIDs exist |
839 | + elif isinstance(e_pids_length, bool) and \ |
840 | + e_pids_length is True and a_pids_length < 1: |
841 | + return fail_msg |
842 | + # If expected is bool False, ensure 0 PIDs exist |
843 | + elif isinstance(e_pids_length, bool) and \ |
844 | + e_pids_length is False and a_pids_length != 0: |
845 | + return fail_msg |
846 | else: |
847 | self.log.debug('PID check OK: {} {} {}: ' |
848 | '{}'.format(e_sentry_name, e_proc_name, |
849 | @@ -531,3 +567,30 @@ |
850 | return 'Dicts within list are not identical' |
851 | |
852 | return None |
853 | + |
854 | + def run_action(self, unit_sentry, action, |
855 | + _check_output=subprocess.check_output): |
856 | + """Run the named action on a given unit sentry. |
857 | + |
858 | + _check_output parameter is used for dependency injection. |
859 | + |
860 | + @return action_id. |
861 | + """ |
862 | + unit_id = unit_sentry.info["unit_name"] |
863 | + command = ["juju", "action", "do", "--format=json", unit_id, action] |
864 | + self.log.info("Running command: %s\n" % " ".join(command)) |
865 | + output = _check_output(command, universal_newlines=True) |
866 | + data = json.loads(output) |
867 | + action_id = data[u'Action queued with id'] |
868 | + return action_id |
869 | + |
870 | + def wait_on_action(self, action_id, _check_output=subprocess.check_output): |
871 | + """Wait for a given action, returning if it completed or not. |
872 | + |
873 | + _check_output parameter is used for dependency injection. |
874 | + """ |
875 | + command = ["juju", "action", "fetch", "--format=json", "--wait=0", |
876 | + action_id] |
877 | + output = _check_output(command, universal_newlines=True) |
878 | + data = json.loads(output) |
879 | + return data.get(u"status") == "completed" |
880 | |
881 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
882 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-07-29 10:49:43 +0000 |
883 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-17 13:44:46 +0000 |
884 | @@ -44,7 +44,7 @@ |
885 | Determine if the local branch being tested is derived from its |
886 | stable or next (dev) branch, and based on this, use the corresonding |
887 | stable or next branches for the other_services.""" |
888 | - base_charms = ['mysql', 'mongodb'] |
889 | + base_charms = ['mysql', 'mongodb', 'nrpe'] |
890 | |
891 | if self.series in ['precise', 'trusty']: |
892 | base_series = self.series |
893 | @@ -81,7 +81,7 @@ |
894 | 'ceph-osd', 'ceph-radosgw'] |
895 | # Most OpenStack subordinate charms do not expose an origin option |
896 | # as that is controlled by the principle. |
897 | - ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch'] |
898 | + ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] |
899 | |
900 | if self.openstack: |
901 | for svc in services: |
902 | |
903 | === added file 'unit_tests/test_actions.py' |
904 | --- unit_tests/test_actions.py 1970-01-01 00:00:00 +0000 |
905 | +++ unit_tests/test_actions.py 2015-08-17 13:44:46 +0000 |
906 | @@ -0,0 +1,239 @@ |
907 | +import argparse |
908 | +import tempfile |
909 | +import unittest |
910 | + |
911 | +import mock |
912 | +import yaml |
913 | + |
914 | +from test_utils import CharmTestCase |
915 | + |
916 | +import actions.actions |
917 | + |
918 | + |
919 | +class PauseTestCase(CharmTestCase): |
920 | + |
921 | + def setUp(self): |
922 | + super(PauseTestCase, self).setUp( |
923 | + actions.actions, ["service_pause", "status_set"]) |
924 | + |
925 | + class FakeArgs(object): |
926 | + services = ['swift-account', |
927 | + 'swift-account-auditor', |
928 | + 'swift-account-reaper', |
929 | + 'swift-account-replicator', |
930 | + 'swift-container', |
931 | + 'swift-container-auditor', |
932 | + 'swift-container-updater', |
933 | + 'swift-container-replicator', |
934 | + 'swift-container-sync', |
935 | + 'swift-object', |
936 | + 'swift-object-auditor', |
937 | + 'swift-object-updater', |
938 | + 'swift-object-replicator'] |
939 | + self.args = FakeArgs() |
940 | + |
941 | + def test_pauses_services(self): |
942 | + """Pause action pauses all of the Swift services.""" |
943 | + pause_calls = [] |
944 | + |
945 | + def fake_service_pause(svc): |
946 | + pause_calls.append(svc) |
947 | + return True |
948 | + |
949 | + self.service_pause.side_effect = fake_service_pause |
950 | + |
951 | + actions.actions.pause(self.args) |
952 | + self.assertEqual(pause_calls, ['swift-account', |
953 | + 'swift-account-auditor', |
954 | + 'swift-account-reaper', |
955 | + 'swift-account-replicator', |
956 | + 'swift-container', |
957 | + 'swift-container-auditor', |
958 | + 'swift-container-updater', |
959 | + 'swift-container-replicator', |
960 | + 'swift-container-sync', |
961 | + 'swift-object', |
962 | + 'swift-object-auditor', |
963 | + 'swift-object-updater', |
964 | + 'swift-object-replicator']) |
965 | + |
966 | + def test_bails_out_early_on_error(self): |
967 | + """Pause action fails early if there are errors stopping a service.""" |
968 | + pause_calls = [] |
969 | + |
970 | + def maybe_kill(svc): |
971 | + if svc == "swift-container": |
972 | + return False |
973 | + else: |
974 | + pause_calls.append(svc) |
975 | + return True |
976 | + |
977 | + self.service_pause.side_effect = maybe_kill |
978 | + self.assertRaisesRegexp( |
979 | + Exception, "swift-container didn't stop cleanly.", |
980 | + actions.actions.pause, self.args) |
981 | + self.assertEqual(pause_calls, ['swift-account', |
982 | + 'swift-account-auditor', |
983 | + 'swift-account-reaper', |
984 | + 'swift-account-replicator']) |
985 | + |
986 | + def test_status_mode(self): |
987 | + """Pause action sets the status to maintenance.""" |
988 | + status_calls = [] |
989 | + self.status_set.side_effect = lambda state, msg: status_calls.append( |
990 | + state) |
991 | + |
992 | + actions.actions.pause(self.args) |
993 | + self.assertEqual(status_calls, ["maintenance"]) |
994 | + |
995 | + def test_status_message(self): |
996 | + """Pause action sets a status message reflecting that it's paused.""" |
997 | + status_calls = [] |
998 | + self.status_set.side_effect = lambda state, msg: status_calls.append( |
999 | + msg) |
1000 | + |
1001 | + actions.actions.pause(self.args) |
1002 | + self.assertEqual( |
1003 | + status_calls, ["Paused. " |
1004 | + "Use 'resume' action to resume normal service."]) |
1005 | + |
1006 | + |
1007 | +class ResumeTestCase(CharmTestCase): |
1008 | + |
1009 | + def setUp(self): |
1010 | + super(ResumeTestCase, self).setUp( |
1011 | + actions.actions, ["service_resume", "status_set"]) |
1012 | + |
1013 | + class FakeArgs(object): |
1014 | + services = ['swift-account', |
1015 | + 'swift-account-auditor', |
1016 | + 'swift-account-reaper', |
1017 | + 'swift-account-replicator', |
1018 | + 'swift-container', |
1019 | + 'swift-container-auditor', |
1020 | + 'swift-container-updater', |
1021 | + 'swift-container-replicator', |
1022 | + 'swift-container-sync', |
1023 | + 'swift-object', |
1024 | + 'swift-object-auditor', |
1025 | + 'swift-object-updater', |
1026 | + 'swift-object-replicator'] |
1027 | + self.args = FakeArgs() |
1028 | + |
1029 | + def test_resumes_services(self): |
1030 | + """Resume action resumes all of the Swift services.""" |
1031 | + resume_calls = [] |
1032 | + |
1033 | + def fake_service_resume(svc): |
1034 | + resume_calls.append(svc) |
1035 | + return True |
1036 | + |
1037 | + self.service_resume.side_effect = fake_service_resume |
1038 | + actions.actions.resume(self.args) |
1039 | + self.assertEqual(resume_calls, ['swift-account', |
1040 | + 'swift-account-auditor', |
1041 | + 'swift-account-reaper', |
1042 | + 'swift-account-replicator', |
1043 | + 'swift-container', |
1044 | + 'swift-container-auditor', |
1045 | + 'swift-container-updater', |
1046 | + 'swift-container-replicator', |
1047 | + 'swift-container-sync', |
1048 | + 'swift-object', |
1049 | + 'swift-object-auditor', |
1050 | + 'swift-object-updater', |
1051 | + 'swift-object-replicator']) |
1052 | + |
1053 | + def test_bails_out_early_on_error(self): |
1054 | + """Resume action fails early if there are errors starting a service.""" |
1055 | + resume_calls = [] |
1056 | + |
1057 | + def maybe_kill(svc): |
1058 | + if svc == "swift-container": |
1059 | + return False |
1060 | + else: |
1061 | + resume_calls.append(svc) |
1062 | + return True |
1063 | + |
1064 | + self.service_resume.side_effect = maybe_kill |
1065 | + self.assertRaisesRegexp( |
1066 | + Exception, "swift-container didn't start cleanly.", |
1067 | + actions.actions.resume, self.args) |
1068 | + self.assertEqual(resume_calls, ['swift-account', |
1069 | + 'swift-account-auditor', |
1070 | + 'swift-account-reaper', |
1071 | + 'swift-account-replicator']) |
1072 | + |
1073 | + def test_status_mode(self): |
1074 | + """Resume action sets the status to maintenance.""" |
1075 | + status_calls = [] |
1076 | + self.status_set.side_effect = lambda state, msg: status_calls.append( |
1077 | + state) |
1078 | + |
1079 | + actions.actions.resume(self.args) |
1080 | + self.assertEqual(status_calls, ["active"]) |
1081 | + |
1082 | + def test_status_message(self): |
1083 | + """Resume action sets an empty status message.""" |
1084 | + status_calls = [] |
1085 | + self.status_set.side_effect = lambda state, msg: status_calls.append( |
1086 | + msg) |
1087 | + |
1088 | + actions.actions.resume(self.args) |
1089 | + self.assertEqual(status_calls, [""]) |
1090 | + |
1091 | + |
1092 | +class GetActionParserTestCase(unittest.TestCase): |
1093 | + |
1094 | + def test_definition_from_yaml(self): |
1095 | + """ArgumentParser is seeded from actions.yaml.""" |
1096 | + actions_yaml = tempfile.NamedTemporaryFile( |
1097 | + prefix="GetActionParserTestCase", suffix="yaml") |
1098 | + actions_yaml.write(yaml.dump({"foo": {"description": "Foo is bar"}})) |
1099 | + actions_yaml.seek(0) |
1100 | + parser = actions.actions.get_action_parser(actions_yaml.name, "foo", |
1101 | + get_services=lambda: []) |
1102 | + self.assertEqual(parser.description, 'Foo is bar') |
1103 | + |
1104 | + |
1105 | +class MainTestCase(CharmTestCase): |
1106 | + |
1107 | + def setUp(self): |
1108 | + super(MainTestCase, self).setUp( |
1109 | + actions.actions, ["_get_action_name", |
1110 | + "get_action_parser", |
1111 | + "action_fail"]) |
1112 | + |
1113 | + def test_invokes_pause(self): |
1114 | + dummy_calls = [] |
1115 | + |
1116 | + def dummy_action(args): |
1117 | + dummy_calls.append(True) |
1118 | + |
1119 | + self._get_action_name.side_effect = lambda: "foo" |
1120 | + self.get_action_parser = lambda: argparse.ArgumentParser() |
1121 | + with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}): |
1122 | + actions.actions.main([]) |
1123 | + self.assertEqual(dummy_calls, [True]) |
1124 | + |
1125 | + def test_unknown_action(self): |
1126 | + """Unknown actions aren't a traceback.""" |
1127 | + self._get_action_name.side_effect = lambda: "foo" |
1128 | + self.get_action_parser = lambda: argparse.ArgumentParser() |
1129 | + exit_string = actions.actions.main([]) |
1130 | + self.assertEqual("Action foo undefined", exit_string) |
1131 | + |
1132 | + def test_failing_action(self): |
1133 | + """Actions which traceback trigger action_fail() calls.""" |
1134 | + dummy_calls = [] |
1135 | + |
1136 | + self.action_fail.side_effect = dummy_calls.append |
1137 | + self._get_action_name.side_effect = lambda: "foo" |
1138 | + |
1139 | + def dummy_action(args): |
1140 | + raise ValueError("uh oh") |
1141 | + |
1142 | + self.get_action_parser = lambda: argparse.ArgumentParser() |
1143 | + with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}): |
1144 | + actions.actions.main([]) |
1145 | + self.assertEqual(dummy_calls, ["uh oh"]) |
charm_lint_check #7869 swift-storage-next for adam-collard mp267681
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/7869/