Merge lp:~gnuoy/charms/trusty/glance/1453940 into lp:~openstack-charmers-archive/charms/trusty/glance/next
- Trusty Tahr (14.04)
- 1453940
- Merge into next
Status: | Merged |
---|---|
Merged at revision: | 138 |
Proposed branch: | lp:~gnuoy/charms/trusty/glance/1453940 |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/glance/next |
Diff against target: |
1305 lines (+886/-104) (has conflicts) 8 files modified
charmhelpers/contrib/openstack/context.py (+8/-9) charmhelpers/contrib/storage/linux/ceph.py (+224/-2) hooks/glance_relations.py (+13/-19) templates/ceph.conf (+2/-1) tests/charmhelpers/contrib/amulet/utils.py (+234/-52) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+20/-5) tests/charmhelpers/contrib/openstack/amulet/utils.py (+359/-0) unit_tests/test_glance_relations.py (+26/-16) Text conflict in unit_tests/test_glance_relations.py |
To merge this branch: | bzr merge lp:~gnuoy/charms/trusty/glance/1453940 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Edward Hope-Morley | Approve | ||
Review via email: mp+268605@code.launchpad.net |
Commit message
Description of the change
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7818 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5931 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8481 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7872 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8484 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7875 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5940 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5943 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8547 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7935 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8548 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7936 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8549 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7937 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5962 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5963 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5964 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8648 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7985 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6013 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #8704 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8038 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6026 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #9383 glance-next for gnuoy mp268605
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8679 glance-next for gnuoy mp268605
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6248 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
- 152. By Liam Young
-
Charm helper sync
- 153. By Liam Young
-
Fix unit tests
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #9537 glance-next for gnuoy mp268605
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8779 glance-next for gnuoy mp268605
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6309 glance-next for gnuoy mp268605
AMULET OK: passed
Build: http://
- 154. By Liam Young
-
Charm helper sync
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #9700 glance-next for gnuoy mp268605
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #8933 glance-next for gnuoy mp268605
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6347 glance-next for gnuoy mp268605
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Edward Hope-Morley (hopem) wrote : | # |
LGTM +1 (think amulet fail is unrelated)
Preview Diff
1 | === modified file 'charmhelpers/contrib/openstack/context.py' | |||
2 | --- charmhelpers/contrib/openstack/context.py 2015-09-03 09:41:01 +0000 | |||
3 | +++ charmhelpers/contrib/openstack/context.py 2015-09-10 09:35:29 +0000 | |||
4 | @@ -485,13 +485,15 @@ | |||
5 | 485 | 485 | ||
6 | 486 | log('Generating template context for ceph', level=DEBUG) | 486 | log('Generating template context for ceph', level=DEBUG) |
7 | 487 | mon_hosts = [] | 487 | mon_hosts = [] |
11 | 488 | auth = None | 488 | ctxt = { |
12 | 489 | key = None | 489 | 'use_syslog': str(config('use-syslog')).lower() |
13 | 490 | use_syslog = str(config('use-syslog')).lower() | 490 | } |
14 | 491 | for rid in relation_ids('ceph'): | 491 | for rid in relation_ids('ceph'): |
15 | 492 | for unit in related_units(rid): | 492 | for unit in related_units(rid): |
18 | 493 | auth = relation_get('auth', rid=rid, unit=unit) | 493 | if not ctxt.get('auth'): |
19 | 494 | key = relation_get('key', rid=rid, unit=unit) | 494 | ctxt['auth'] = relation_get('auth', rid=rid, unit=unit) |
20 | 495 | if not ctxt.get('key'): | ||
21 | 496 | ctxt['key'] = relation_get('key', rid=rid, unit=unit) | ||
22 | 495 | ceph_pub_addr = relation_get('ceph-public-address', rid=rid, | 497 | ceph_pub_addr = relation_get('ceph-public-address', rid=rid, |
23 | 496 | unit=unit) | 498 | unit=unit) |
24 | 497 | unit_priv_addr = relation_get('private-address', rid=rid, | 499 | unit_priv_addr = relation_get('private-address', rid=rid, |
25 | @@ -500,10 +502,7 @@ | |||
26 | 500 | ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr | 502 | ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr |
27 | 501 | mon_hosts.append(ceph_addr) | 503 | mon_hosts.append(ceph_addr) |
28 | 502 | 504 | ||
33 | 503 | ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)), | 505 | ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts)) |
30 | 504 | 'auth': auth, | ||
31 | 505 | 'key': key, | ||
32 | 506 | 'use_syslog': use_syslog} | ||
34 | 507 | 506 | ||
35 | 508 | if not os.path.isdir('/etc/ceph'): | 507 | if not os.path.isdir('/etc/ceph'): |
36 | 509 | os.mkdir('/etc/ceph') | 508 | os.mkdir('/etc/ceph') |
37 | 510 | 509 | ||
38 | === modified file 'charmhelpers/contrib/storage/linux/ceph.py' | |||
39 | --- charmhelpers/contrib/storage/linux/ceph.py 2015-07-16 20:17:23 +0000 | |||
40 | +++ charmhelpers/contrib/storage/linux/ceph.py 2015-09-10 09:35:29 +0000 | |||
41 | @@ -28,6 +28,7 @@ | |||
42 | 28 | import shutil | 28 | import shutil |
43 | 29 | import json | 29 | import json |
44 | 30 | import time | 30 | import time |
45 | 31 | import uuid | ||
46 | 31 | 32 | ||
47 | 32 | from subprocess import ( | 33 | from subprocess import ( |
48 | 33 | check_call, | 34 | check_call, |
49 | @@ -35,8 +36,10 @@ | |||
50 | 35 | CalledProcessError, | 36 | CalledProcessError, |
51 | 36 | ) | 37 | ) |
52 | 37 | from charmhelpers.core.hookenv import ( | 38 | from charmhelpers.core.hookenv import ( |
53 | 39 | local_unit, | ||
54 | 38 | relation_get, | 40 | relation_get, |
55 | 39 | relation_ids, | 41 | relation_ids, |
56 | 42 | relation_set, | ||
57 | 40 | related_units, | 43 | related_units, |
58 | 41 | log, | 44 | log, |
59 | 42 | DEBUG, | 45 | DEBUG, |
60 | @@ -411,17 +414,52 @@ | |||
61 | 411 | 414 | ||
62 | 412 | The API is versioned and defaults to version 1. | 415 | The API is versioned and defaults to version 1. |
63 | 413 | """ | 416 | """ |
65 | 414 | def __init__(self, api_version=1): | 417 | def __init__(self, api_version=1, request_id=None): |
66 | 415 | self.api_version = api_version | 418 | self.api_version = api_version |
67 | 419 | if request_id: | ||
68 | 420 | self.request_id = request_id | ||
69 | 421 | else: | ||
70 | 422 | self.request_id = str(uuid.uuid1()) | ||
71 | 416 | self.ops = [] | 423 | self.ops = [] |
72 | 417 | 424 | ||
73 | 418 | def add_op_create_pool(self, name, replica_count=3): | 425 | def add_op_create_pool(self, name, replica_count=3): |
74 | 419 | self.ops.append({'op': 'create-pool', 'name': name, | 426 | self.ops.append({'op': 'create-pool', 'name': name, |
75 | 420 | 'replicas': replica_count}) | 427 | 'replicas': replica_count}) |
76 | 421 | 428 | ||
77 | 429 | def set_ops(self, ops): | ||
78 | 430 | """Set request ops to provided value. | ||
79 | 431 | |||
80 | 432 | Useful for injecting ops that come from a previous request | ||
81 | 433 | to allow comparisons to ensure validity. | ||
82 | 434 | """ | ||
83 | 435 | self.ops = ops | ||
84 | 436 | |||
85 | 422 | @property | 437 | @property |
86 | 423 | def request(self): | 438 | def request(self): |
88 | 424 | return json.dumps({'api-version': self.api_version, 'ops': self.ops}) | 439 | return json.dumps({'api-version': self.api_version, 'ops': self.ops, |
89 | 440 | 'request-id': self.request_id}) | ||
90 | 441 | |||
91 | 442 | def _ops_equal(self, other): | ||
92 | 443 | if len(self.ops) == len(other.ops): | ||
93 | 444 | for req_no in range(0, len(self.ops)): | ||
94 | 445 | for key in ['replicas', 'name', 'op']: | ||
95 | 446 | if self.ops[req_no][key] != other.ops[req_no][key]: | ||
96 | 447 | return False | ||
97 | 448 | else: | ||
98 | 449 | return False | ||
99 | 450 | return True | ||
100 | 451 | |||
101 | 452 | def __eq__(self, other): | ||
102 | 453 | if not isinstance(other, self.__class__): | ||
103 | 454 | return False | ||
104 | 455 | if self.api_version == other.api_version and \ | ||
105 | 456 | self._ops_equal(other): | ||
106 | 457 | return True | ||
107 | 458 | else: | ||
108 | 459 | return False | ||
109 | 460 | |||
110 | 461 | def __ne__(self, other): | ||
111 | 462 | return not self.__eq__(other) | ||
112 | 425 | 463 | ||
113 | 426 | 464 | ||
114 | 427 | class CephBrokerRsp(object): | 465 | class CephBrokerRsp(object): |
115 | @@ -431,14 +469,198 @@ | |||
116 | 431 | 469 | ||
117 | 432 | The API is versioned and defaults to version 1. | 470 | The API is versioned and defaults to version 1. |
118 | 433 | """ | 471 | """ |
119 | 472 | |||
120 | 434 | def __init__(self, encoded_rsp): | 473 | def __init__(self, encoded_rsp): |
121 | 435 | self.api_version = None | 474 | self.api_version = None |
122 | 436 | self.rsp = json.loads(encoded_rsp) | 475 | self.rsp = json.loads(encoded_rsp) |
123 | 437 | 476 | ||
124 | 438 | @property | 477 | @property |
125 | 478 | def request_id(self): | ||
126 | 479 | return self.rsp.get('request-id') | ||
127 | 480 | |||
128 | 481 | @property | ||
129 | 439 | def exit_code(self): | 482 | def exit_code(self): |
130 | 440 | return self.rsp.get('exit-code') | 483 | return self.rsp.get('exit-code') |
131 | 441 | 484 | ||
132 | 442 | @property | 485 | @property |
133 | 443 | def exit_msg(self): | 486 | def exit_msg(self): |
134 | 444 | return self.rsp.get('stderr') | 487 | return self.rsp.get('stderr') |
135 | 488 | |||
136 | 489 | |||
137 | 490 | # Ceph Broker Conversation: | ||
138 | 491 | # If a charm needs an action to be taken by ceph it can create a CephBrokerRq | ||
139 | 492 | # and send that request to ceph via the ceph relation. The CephBrokerRq has a | ||
140 | 493 | # unique id so that the client can identity which CephBrokerRsp is associated | ||
141 | 494 | # with the request. Ceph will also respond to each client unit individually | ||
142 | 495 | # creating a response key per client unit eg glance/0 will get a CephBrokerRsp | ||
143 | 496 | # via key broker-rsp-glance-0 | ||
144 | 497 | # | ||
145 | 498 | # To use this the charm can just do something like: | ||
146 | 499 | # | ||
147 | 500 | # from charmhelpers.contrib.storage.linux.ceph import ( | ||
148 | 501 | # send_request_if_needed, | ||
149 | 502 | # is_request_complete, | ||
150 | 503 | # CephBrokerRq, | ||
151 | 504 | # ) | ||
152 | 505 | # | ||
153 | 506 | # @hooks.hook('ceph-relation-changed') | ||
154 | 507 | # def ceph_changed(): | ||
155 | 508 | # rq = CephBrokerRq() | ||
156 | 509 | # rq.add_op_create_pool(name='poolname', replica_count=3) | ||
157 | 510 | # | ||
158 | 511 | # if is_request_complete(rq): | ||
159 | 512 | # <Request complete actions> | ||
160 | 513 | # else: | ||
161 | 514 | # send_request_if_needed(get_ceph_request()) | ||
162 | 515 | # | ||
163 | 516 | # CephBrokerRq and CephBrokerRsp are serialized into JSON. Below is an example | ||
164 | 517 | # of glance having sent a request to ceph which ceph has successfully processed | ||
165 | 518 | # 'ceph:8': { | ||
166 | 519 | # 'ceph/0': { | ||
167 | 520 | # 'auth': 'cephx', | ||
168 | 521 | # 'broker-rsp-glance-0': '{"request-id": "0bc7dc54", "exit-code": 0}', | ||
169 | 522 | # 'broker_rsp': '{"request-id": "0da543b8", "exit-code": 0}', | ||
170 | 523 | # 'ceph-public-address': '10.5.44.103', | ||
171 | 524 | # 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==', | ||
172 | 525 | # 'private-address': '10.5.44.103', | ||
173 | 526 | # }, | ||
174 | 527 | # 'glance/0': { | ||
175 | 528 | # 'broker_req': ('{"api-version": 1, "request-id": "0bc7dc54", ' | ||
176 | 529 | # '"ops": [{"replicas": 3, "name": "glance", ' | ||
177 | 530 | # '"op": "create-pool"}]}'), | ||
178 | 531 | # 'private-address': '10.5.44.109', | ||
179 | 532 | # }, | ||
180 | 533 | # } | ||
181 | 534 | |||
182 | 535 | def get_previous_request(rid): | ||
183 | 536 | """Return the last ceph broker request sent on a given relation | ||
184 | 537 | |||
185 | 538 | @param rid: Relation id to query for request | ||
186 | 539 | """ | ||
187 | 540 | request = None | ||
188 | 541 | broker_req = relation_get(attribute='broker_req', rid=rid, | ||
189 | 542 | unit=local_unit()) | ||
190 | 543 | if broker_req: | ||
191 | 544 | request_data = json.loads(broker_req) | ||
192 | 545 | request = CephBrokerRq(api_version=request_data['api-version'], | ||
193 | 546 | request_id=request_data['request-id']) | ||
194 | 547 | request.set_ops(request_data['ops']) | ||
195 | 548 | |||
196 | 549 | return request | ||
197 | 550 | |||
198 | 551 | |||
199 | 552 | def get_request_states(request): | ||
200 | 553 | """Return a dict of requests per relation id with their corresponding | ||
201 | 554 | completion state. | ||
202 | 555 | |||
203 | 556 | This allows a charm, which has a request for ceph, to see whether there is | ||
204 | 557 | an equivalent request already being processed and if so what state that | ||
205 | 558 | request is in. | ||
206 | 559 | |||
207 | 560 | @param request: A CephBrokerRq object | ||
208 | 561 | """ | ||
209 | 562 | complete = [] | ||
210 | 563 | requests = {} | ||
211 | 564 | for rid in relation_ids('ceph'): | ||
212 | 565 | complete = False | ||
213 | 566 | previous_request = get_previous_request(rid) | ||
214 | 567 | if request == previous_request: | ||
215 | 568 | sent = True | ||
216 | 569 | complete = is_request_complete_for_rid(previous_request, rid) | ||
217 | 570 | else: | ||
218 | 571 | sent = False | ||
219 | 572 | complete = False | ||
220 | 573 | |||
221 | 574 | requests[rid] = { | ||
222 | 575 | 'sent': sent, | ||
223 | 576 | 'complete': complete, | ||
224 | 577 | } | ||
225 | 578 | |||
226 | 579 | return requests | ||
227 | 580 | |||
228 | 581 | |||
229 | 582 | def is_request_sent(request): | ||
230 | 583 | """Check to see if a functionally equivalent request has already been sent | ||
231 | 584 | |||
232 | 585 | Returns True if a similair request has been sent | ||
233 | 586 | |||
234 | 587 | @param request: A CephBrokerRq object | ||
235 | 588 | """ | ||
236 | 589 | states = get_request_states(request) | ||
237 | 590 | for rid in states.keys(): | ||
238 | 591 | if not states[rid]['sent']: | ||
239 | 592 | return False | ||
240 | 593 | |||
241 | 594 | return True | ||
242 | 595 | |||
243 | 596 | |||
244 | 597 | def is_request_complete(request): | ||
245 | 598 | """Check to see if a functionally equivalent request has already been | ||
246 | 599 | completed | ||
247 | 600 | |||
248 | 601 | Returns True if a similair request has been completed | ||
249 | 602 | |||
250 | 603 | @param request: A CephBrokerRq object | ||
251 | 604 | """ | ||
252 | 605 | states = get_request_states(request) | ||
253 | 606 | for rid in states.keys(): | ||
254 | 607 | if not states[rid]['complete']: | ||
255 | 608 | return False | ||
256 | 609 | |||
257 | 610 | return True | ||
258 | 611 | |||
259 | 612 | |||
260 | 613 | def is_request_complete_for_rid(request, rid): | ||
261 | 614 | """Check if a given request has been completed on the given relation | ||
262 | 615 | |||
263 | 616 | @param request: A CephBrokerRq object | ||
264 | 617 | @param rid: Relation ID | ||
265 | 618 | """ | ||
266 | 619 | broker_key = get_broker_rsp_key() | ||
267 | 620 | for unit in related_units(rid): | ||
268 | 621 | rdata = relation_get(rid=rid, unit=unit) | ||
269 | 622 | if rdata.get(broker_key): | ||
270 | 623 | rsp = CephBrokerRsp(rdata.get(broker_key)) | ||
271 | 624 | if rsp.request_id == request.request_id: | ||
272 | 625 | if not rsp.exit_code: | ||
273 | 626 | return True | ||
274 | 627 | else: | ||
275 | 628 | # The remote unit sent no reply targeted at this unit so either the | ||
276 | 629 | # remote ceph cluster does not support unit targeted replies or it | ||
277 | 630 | # has not processed our request yet. | ||
278 | 631 | if rdata.get('broker_rsp'): | ||
279 | 632 | request_data = json.loads(rdata['broker_rsp']) | ||
280 | 633 | if request_data.get('request-id'): | ||
281 | 634 | log('Ignoring legacy broker_rsp without unit key as remote ' | ||
282 | 635 | 'service supports unit specific replies', level=DEBUG) | ||
283 | 636 | else: | ||
284 | 637 | log('Using legacy broker_rsp as remote service does not ' | ||
285 | 638 | 'supports unit specific replies', level=DEBUG) | ||
286 | 639 | rsp = CephBrokerRsp(rdata['broker_rsp']) | ||
287 | 640 | if not rsp.exit_code: | ||
288 | 641 | return True | ||
289 | 642 | |||
290 | 643 | return False | ||
291 | 644 | |||
292 | 645 | |||
293 | 646 | def get_broker_rsp_key(): | ||
294 | 647 | """Return broker response key for this unit | ||
295 | 648 | |||
296 | 649 | This is the key that ceph is going to use to pass request status | ||
297 | 650 | information back to this unit | ||
298 | 651 | """ | ||
299 | 652 | return 'broker-rsp-' + local_unit().replace('/', '-') | ||
300 | 653 | |||
301 | 654 | |||
302 | 655 | def send_request_if_needed(request): | ||
303 | 656 | """Send broker request if an equivalent request has not already been sent | ||
304 | 657 | |||
305 | 658 | @param request: A CephBrokerRq object | ||
306 | 659 | """ | ||
307 | 660 | if is_request_sent(request): | ||
308 | 661 | log('Request already sent but not complete, not sending new request', | ||
309 | 662 | level=DEBUG) | ||
310 | 663 | else: | ||
311 | 664 | for rid in relation_ids('ceph'): | ||
312 | 665 | log('Sending request {}'.format(request.request_id), level=DEBUG) | ||
313 | 666 | relation_set(relation_id=rid, broker_req=request.request) | ||
314 | 445 | 667 | ||
315 | === modified file 'hooks/glance_relations.py' | |||
316 | --- hooks/glance_relations.py 2015-09-01 06:57:55 +0000 | |||
317 | +++ hooks/glance_relations.py 2015-09-10 09:35:29 +0000 | |||
318 | @@ -29,7 +29,6 @@ | |||
319 | 29 | config, | 29 | config, |
320 | 30 | Hooks, | 30 | Hooks, |
321 | 31 | log as juju_log, | 31 | log as juju_log, |
322 | 32 | INFO, | ||
323 | 33 | ERROR, | 32 | ERROR, |
324 | 34 | open_port, | 33 | open_port, |
325 | 35 | is_relation_made, | 34 | is_relation_made, |
326 | @@ -66,9 +65,10 @@ | |||
327 | 66 | sync_db_with_multi_ipv6_addresses, | 65 | sync_db_with_multi_ipv6_addresses, |
328 | 67 | ) | 66 | ) |
329 | 68 | from charmhelpers.contrib.storage.linux.ceph import ( | 67 | from charmhelpers.contrib.storage.linux.ceph import ( |
330 | 68 | send_request_if_needed, | ||
331 | 69 | is_request_complete, | ||
332 | 69 | ensure_ceph_keyring, | 70 | ensure_ceph_keyring, |
333 | 70 | CephBrokerRq, | 71 | CephBrokerRq, |
334 | 71 | CephBrokerRsp, | ||
335 | 72 | delete_keyring, | 72 | delete_keyring, |
336 | 73 | ) | 73 | ) |
337 | 74 | from charmhelpers.payload.execd import ( | 74 | from charmhelpers.payload.execd import ( |
338 | @@ -240,6 +240,14 @@ | |||
339 | 240 | apt_install(['ceph-common', 'python-ceph']) | 240 | apt_install(['ceph-common', 'python-ceph']) |
340 | 241 | 241 | ||
341 | 242 | 242 | ||
342 | 243 | def get_ceph_request(): | ||
343 | 244 | service = service_name() | ||
344 | 245 | rq = CephBrokerRq() | ||
345 | 246 | replicas = config('ceph-osd-replication-count') | ||
346 | 247 | rq.add_op_create_pool(name=service, replica_count=replicas) | ||
347 | 248 | return rq | ||
348 | 249 | |||
349 | 250 | |||
350 | 243 | @hooks.hook('ceph-relation-changed') | 251 | @hooks.hook('ceph-relation-changed') |
351 | 244 | @restart_on_change(restart_map()) | 252 | @restart_on_change(restart_map()) |
352 | 245 | def ceph_changed(): | 253 | def ceph_changed(): |
353 | @@ -253,29 +261,15 @@ | |||
354 | 253 | juju_log('Could not create ceph keyring: peer not ready?') | 261 | juju_log('Could not create ceph keyring: peer not ready?') |
355 | 254 | return | 262 | return |
356 | 255 | 263 | ||
368 | 256 | settings = relation_get() | 264 | if is_request_complete(get_ceph_request()): |
369 | 257 | if settings and 'broker_rsp' in settings: | 265 | juju_log('Request complete') |
359 | 258 | rsp = CephBrokerRsp(settings['broker_rsp']) | ||
360 | 259 | # Non-zero return code implies failure | ||
361 | 260 | if rsp.exit_code: | ||
362 | 261 | juju_log("Ceph broker request failed (rc=%s, msg=%s)" % | ||
363 | 262 | (rsp.exit_code, rsp.exit_msg), level=ERROR) | ||
364 | 263 | return | ||
365 | 264 | |||
366 | 265 | juju_log("Ceph broker request succeeded (rc=%s, msg=%s)" % | ||
367 | 266 | (rsp.exit_code, rsp.exit_msg), level=INFO) | ||
370 | 267 | CONFIGS.write(GLANCE_API_CONF) | 266 | CONFIGS.write(GLANCE_API_CONF) |
371 | 268 | CONFIGS.write(ceph_config_file()) | 267 | CONFIGS.write(ceph_config_file()) |
372 | 269 | # Ensure that glance-api is restarted since only now can we | 268 | # Ensure that glance-api is restarted since only now can we |
373 | 270 | # guarantee that ceph resources are ready. | 269 | # guarantee that ceph resources are ready. |
374 | 271 | service_restart('glance-api') | 270 | service_restart('glance-api') |
375 | 272 | else: | 271 | else: |
382 | 273 | rq = CephBrokerRq() | 272 | send_request_if_needed(get_ceph_request()) |
377 | 274 | replicas = config('ceph-osd-replication-count') | ||
378 | 275 | rq.add_op_create_pool(name=service, replica_count=replicas) | ||
379 | 276 | for rid in relation_ids('ceph'): | ||
380 | 277 | relation_set(relation_id=rid, broker_req=rq.request) | ||
381 | 278 | juju_log("Request(s) sent to Ceph broker (rid=%s)" % (rid)) | ||
383 | 279 | 273 | ||
384 | 280 | 274 | ||
385 | 281 | @hooks.hook('ceph-relation-broken') | 275 | @hooks.hook('ceph-relation-broken') |
386 | 282 | 276 | ||
387 | === modified file 'templates/ceph.conf' | |||
388 | --- templates/ceph.conf 2014-03-25 18:44:22 +0000 | |||
389 | +++ templates/ceph.conf 2015-09-10 09:35:29 +0000 | |||
390 | @@ -10,7 +10,8 @@ | |||
391 | 10 | keyring = /etc/ceph/ceph.$name.keyring | 10 | keyring = /etc/ceph/ceph.$name.keyring |
392 | 11 | mon host = {{ mon_hosts }} | 11 | mon host = {{ mon_hosts }} |
393 | 12 | {% endif -%} | 12 | {% endif -%} |
394 | 13 | {% if use_syslog -%} | ||
395 | 13 | log to syslog = {{ use_syslog }} | 14 | log to syslog = {{ use_syslog }} |
396 | 14 | err to syslog = {{ use_syslog }} | 15 | err to syslog = {{ use_syslog }} |
397 | 15 | clog to syslog = {{ use_syslog }} | 16 | clog to syslog = {{ use_syslog }} |
399 | 16 | 17 | {% endif -%} | |
400 | 17 | 18 | ||
401 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' | |||
402 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-08-18 17:34:34 +0000 | |||
403 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-10 09:35:29 +0000 | |||
404 | @@ -19,9 +19,11 @@ | |||
405 | 19 | import logging | 19 | import logging |
406 | 20 | import os | 20 | import os |
407 | 21 | import re | 21 | import re |
408 | 22 | import socket | ||
409 | 22 | import subprocess | 23 | import subprocess |
410 | 23 | import sys | 24 | import sys |
411 | 24 | import time | 25 | import time |
412 | 26 | import uuid | ||
413 | 25 | 27 | ||
414 | 26 | import amulet | 28 | import amulet |
415 | 27 | import distro_info | 29 | import distro_info |
416 | @@ -114,7 +116,7 @@ | |||
417 | 114 | # /!\ DEPRECATION WARNING (beisner): | 116 | # /!\ DEPRECATION WARNING (beisner): |
418 | 115 | # New and existing tests should be rewritten to use | 117 | # New and existing tests should be rewritten to use |
419 | 116 | # validate_services_by_name() as it is aware of init systems. | 118 | # validate_services_by_name() as it is aware of init systems. |
421 | 117 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | 119 | self.log.warn('DEPRECATION WARNING: use ' |
422 | 118 | 'validate_services_by_name instead of validate_services ' | 120 | 'validate_services_by_name instead of validate_services ' |
423 | 119 | 'due to init system differences.') | 121 | 'due to init system differences.') |
424 | 120 | 122 | ||
425 | @@ -269,33 +271,52 @@ | |||
426 | 269 | """Get last modification time of directory.""" | 271 | """Get last modification time of directory.""" |
427 | 270 | return sentry_unit.directory_stat(directory)['mtime'] | 272 | return sentry_unit.directory_stat(directory)['mtime'] |
428 | 271 | 273 | ||
447 | 272 | def _get_proc_start_time(self, sentry_unit, service, pgrep_full=False): | 274 | def _get_proc_start_time(self, sentry_unit, service, pgrep_full=None): |
448 | 273 | """Get process' start time. | 275 | """Get start time of a process based on the last modification time |
449 | 274 | 276 | of the /proc/pid directory. | |
450 | 275 | Determine start time of the process based on the last modification | 277 | |
451 | 276 | time of the /proc/pid directory. If pgrep_full is True, the process | 278 | :sentry_unit: The sentry unit to check for the service on |
452 | 277 | name is matched against the full command line. | 279 | :service: service name to look for in process table |
453 | 278 | """ | 280 | :pgrep_full: [Deprecated] Use full command line search mode with pgrep |
454 | 279 | if pgrep_full: | 281 | :returns: epoch time of service process start |
455 | 280 | cmd = 'pgrep -o -f {}'.format(service) | 282 | :param commands: list of bash commands |
456 | 281 | else: | 283 | :param sentry_units: list of sentry unit pointers |
457 | 282 | cmd = 'pgrep -o {}'.format(service) | 284 | :returns: None if successful; Failure message otherwise |
458 | 283 | cmd = cmd + ' | grep -v pgrep || exit 0' | 285 | """ |
459 | 284 | cmd_out = sentry_unit.run(cmd) | 286 | if pgrep_full is not None: |
460 | 285 | self.log.debug('CMDout: ' + str(cmd_out)) | 287 | # /!\ DEPRECATION WARNING (beisner): |
461 | 286 | if cmd_out[0]: | 288 | # No longer implemented, as pidof is now used instead of pgrep. |
462 | 287 | self.log.debug('Pid for %s %s' % (service, str(cmd_out[0]))) | 289 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 |
463 | 288 | proc_dir = '/proc/{}'.format(cmd_out[0].strip()) | 290 | self.log.warn('DEPRECATION WARNING: pgrep_full bool is no ' |
464 | 289 | return self._get_dir_mtime(sentry_unit, proc_dir) | 291 | 'longer implemented re: lp 1474030.') |
465 | 292 | |||
466 | 293 | pid_list = self.get_process_id_list(sentry_unit, service) | ||
467 | 294 | pid = pid_list[0] | ||
468 | 295 | proc_dir = '/proc/{}'.format(pid) | ||
469 | 296 | self.log.debug('Pid for {} on {}: {}'.format( | ||
470 | 297 | service, sentry_unit.info['unit_name'], pid)) | ||
471 | 298 | |||
472 | 299 | return self._get_dir_mtime(sentry_unit, proc_dir) | ||
473 | 290 | 300 | ||
474 | 291 | def service_restarted(self, sentry_unit, service, filename, | 301 | def service_restarted(self, sentry_unit, service, filename, |
476 | 292 | pgrep_full=False, sleep_time=20): | 302 | pgrep_full=None, sleep_time=20): |
477 | 293 | """Check if service was restarted. | 303 | """Check if service was restarted. |
478 | 294 | 304 | ||
479 | 295 | Compare a service's start time vs a file's last modification time | 305 | Compare a service's start time vs a file's last modification time |
480 | 296 | (such as a config file for that service) to determine if the service | 306 | (such as a config file for that service) to determine if the service |
481 | 297 | has been restarted. | 307 | has been restarted. |
482 | 298 | """ | 308 | """ |
483 | 309 | # /!\ DEPRECATION WARNING (beisner): | ||
484 | 310 | # This method is prone to races in that no before-time is known. | ||
485 | 311 | # Use validate_service_config_changed instead. | ||
486 | 312 | |||
487 | 313 | # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now | ||
488 | 314 | # used instead of pgrep. pgrep_full is still passed through to ensure | ||
489 | 315 | # deprecation WARNS. lp1474030 | ||
490 | 316 | self.log.warn('DEPRECATION WARNING: use ' | ||
491 | 317 | 'validate_service_config_changed instead of ' | ||
492 | 318 | 'service_restarted due to known races.') | ||
493 | 319 | |||
494 | 299 | time.sleep(sleep_time) | 320 | time.sleep(sleep_time) |
495 | 300 | if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >= | 321 | if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >= |
496 | 301 | self._get_file_mtime(sentry_unit, filename)): | 322 | self._get_file_mtime(sentry_unit, filename)): |
497 | @@ -304,15 +325,15 @@ | |||
498 | 304 | return False | 325 | return False |
499 | 305 | 326 | ||
500 | 306 | def service_restarted_since(self, sentry_unit, mtime, service, | 327 | def service_restarted_since(self, sentry_unit, mtime, service, |
503 | 307 | pgrep_full=False, sleep_time=20, | 328 | pgrep_full=None, sleep_time=20, |
504 | 308 | retry_count=2): | 329 | retry_count=2, retry_sleep_time=30): |
505 | 309 | """Check if service was been started after a given time. | 330 | """Check if service was been started after a given time. |
506 | 310 | 331 | ||
507 | 311 | Args: | 332 | Args: |
508 | 312 | sentry_unit (sentry): The sentry unit to check for the service on | 333 | sentry_unit (sentry): The sentry unit to check for the service on |
509 | 313 | mtime (float): The epoch time to check against | 334 | mtime (float): The epoch time to check against |
510 | 314 | service (string): service name to look for in process table | 335 | service (string): service name to look for in process table |
512 | 315 | pgrep_full (boolean): Use full command line search mode with pgrep | 336 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
513 | 316 | sleep_time (int): Seconds to sleep before looking for process | 337 | sleep_time (int): Seconds to sleep before looking for process |
514 | 317 | retry_count (int): If service is not found, how many times to retry | 338 | retry_count (int): If service is not found, how many times to retry |
515 | 318 | 339 | ||
516 | @@ -321,30 +342,44 @@ | |||
517 | 321 | False if service is older than mtime or if service was | 342 | False if service is older than mtime or if service was |
518 | 322 | not found. | 343 | not found. |
519 | 323 | """ | 344 | """ |
521 | 324 | self.log.debug('Checking %s restarted since %s' % (service, mtime)) | 345 | # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now |
522 | 346 | # used instead of pgrep. pgrep_full is still passed through to ensure | ||
523 | 347 | # deprecation WARNS. lp1474030 | ||
524 | 348 | |||
525 | 349 | unit_name = sentry_unit.info['unit_name'] | ||
526 | 350 | self.log.debug('Checking that %s service restarted since %s on ' | ||
527 | 351 | '%s' % (service, mtime, unit_name)) | ||
528 | 325 | time.sleep(sleep_time) | 352 | time.sleep(sleep_time) |
538 | 326 | proc_start_time = self._get_proc_start_time(sentry_unit, service, | 353 | proc_start_time = None |
539 | 327 | pgrep_full) | 354 | tries = 0 |
540 | 328 | while retry_count > 0 and not proc_start_time: | 355 | while tries <= retry_count and not proc_start_time: |
541 | 329 | self.log.debug('No pid file found for service %s, will retry %i ' | 356 | try: |
542 | 330 | 'more times' % (service, retry_count)) | 357 | proc_start_time = self._get_proc_start_time(sentry_unit, |
543 | 331 | time.sleep(30) | 358 | service, |
544 | 332 | proc_start_time = self._get_proc_start_time(sentry_unit, service, | 359 | pgrep_full) |
545 | 333 | pgrep_full) | 360 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
546 | 334 | retry_count = retry_count - 1 | 361 | 'OK'.format(tries, service, unit_name)) |
547 | 362 | except IOError: | ||
548 | 363 | # NOTE(beisner) - race avoidance, proc may not exist yet. | ||
549 | 364 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 | ||
550 | 365 | self.log.debug('Attempt {} to get {} proc start time on {} ' | ||
551 | 366 | 'failed'.format(tries, service, unit_name)) | ||
552 | 367 | time.sleep(retry_sleep_time) | ||
553 | 368 | tries += 1 | ||
554 | 335 | 369 | ||
555 | 336 | if not proc_start_time: | 370 | if not proc_start_time: |
556 | 337 | self.log.warn('No proc start time found, assuming service did ' | 371 | self.log.warn('No proc start time found, assuming service did ' |
557 | 338 | 'not start') | 372 | 'not start') |
558 | 339 | return False | 373 | return False |
559 | 340 | if proc_start_time >= mtime: | 374 | if proc_start_time >= mtime: |
562 | 341 | self.log.debug('proc start time is newer than provided mtime' | 375 | self.log.debug('Proc start time is newer than provided mtime' |
563 | 342 | '(%s >= %s)' % (proc_start_time, mtime)) | 376 | '(%s >= %s) on %s (OK)' % (proc_start_time, |
564 | 377 | mtime, unit_name)) | ||
565 | 343 | return True | 378 | return True |
566 | 344 | else: | 379 | else: |
570 | 345 | self.log.warn('proc start time (%s) is older than provided mtime ' | 380 | self.log.warn('Proc start time (%s) is older than provided mtime ' |
571 | 346 | '(%s), service did not restart' % (proc_start_time, | 381 | '(%s) on %s, service did not ' |
572 | 347 | mtime)) | 382 | 'restart' % (proc_start_time, mtime, unit_name)) |
573 | 348 | return False | 383 | return False |
574 | 349 | 384 | ||
575 | 350 | def config_updated_since(self, sentry_unit, filename, mtime, | 385 | def config_updated_since(self, sentry_unit, filename, mtime, |
576 | @@ -374,8 +409,9 @@ | |||
577 | 374 | return False | 409 | return False |
578 | 375 | 410 | ||
579 | 376 | def validate_service_config_changed(self, sentry_unit, mtime, service, | 411 | def validate_service_config_changed(self, sentry_unit, mtime, service, |
582 | 377 | filename, pgrep_full=False, | 412 | filename, pgrep_full=None, |
583 | 378 | sleep_time=20, retry_count=2): | 413 | sleep_time=20, retry_count=2, |
584 | 414 | retry_sleep_time=30): | ||
585 | 379 | """Check service and file were updated after mtime | 415 | """Check service and file were updated after mtime |
586 | 380 | 416 | ||
587 | 381 | Args: | 417 | Args: |
588 | @@ -383,9 +419,10 @@ | |||
589 | 383 | mtime (float): The epoch time to check against | 419 | mtime (float): The epoch time to check against |
590 | 384 | service (string): service name to look for in process table | 420 | service (string): service name to look for in process table |
591 | 385 | filename (string): The file to check mtime of | 421 | filename (string): The file to check mtime of |
594 | 386 | pgrep_full (boolean): Use full command line search mode with pgrep | 422 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
595 | 387 | sleep_time (int): Seconds to sleep before looking for process | 423 | sleep_time (int): Initial sleep in seconds to pass to test helpers |
596 | 388 | retry_count (int): If service is not found, how many times to retry | 424 | retry_count (int): If service is not found, how many times to retry |
597 | 425 | retry_sleep_time (int): Time in seconds to wait between retries | ||
598 | 389 | 426 | ||
599 | 390 | Typical Usage: | 427 | Typical Usage: |
600 | 391 | u = OpenStackAmuletUtils(ERROR) | 428 | u = OpenStackAmuletUtils(ERROR) |
601 | @@ -402,15 +439,25 @@ | |||
602 | 402 | mtime, False if service is older than mtime or if service was | 439 | mtime, False if service is older than mtime or if service was |
603 | 403 | not found or if filename was modified before mtime. | 440 | not found or if filename was modified before mtime. |
604 | 404 | """ | 441 | """ |
614 | 405 | self.log.debug('Checking %s restarted since %s' % (service, mtime)) | 442 | |
615 | 406 | time.sleep(sleep_time) | 443 | # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now |
616 | 407 | service_restart = self.service_restarted_since(sentry_unit, mtime, | 444 | # used instead of pgrep. pgrep_full is still passed through to ensure |
617 | 408 | service, | 445 | # deprecation WARNS. lp1474030 |
618 | 409 | pgrep_full=pgrep_full, | 446 | |
619 | 410 | sleep_time=0, | 447 | service_restart = self.service_restarted_since( |
620 | 411 | retry_count=retry_count) | 448 | sentry_unit, mtime, |
621 | 412 | config_update = self.config_updated_since(sentry_unit, filename, mtime, | 449 | service, |
622 | 413 | sleep_time=0) | 450 | pgrep_full=pgrep_full, |
623 | 451 | sleep_time=sleep_time, | ||
624 | 452 | retry_count=retry_count, | ||
625 | 453 | retry_sleep_time=retry_sleep_time) | ||
626 | 454 | |||
627 | 455 | config_update = self.config_updated_since( | ||
628 | 456 | sentry_unit, | ||
629 | 457 | filename, | ||
630 | 458 | mtime, | ||
631 | 459 | sleep_time=0) | ||
632 | 460 | |||
633 | 414 | return service_restart and config_update | 461 | return service_restart and config_update |
634 | 415 | 462 | ||
635 | 416 | def get_sentry_time(self, sentry_unit): | 463 | def get_sentry_time(self, sentry_unit): |
636 | @@ -428,7 +475,6 @@ | |||
637 | 428 | """Return a list of all Ubuntu releases in order of release.""" | 475 | """Return a list of all Ubuntu releases in order of release.""" |
638 | 429 | _d = distro_info.UbuntuDistroInfo() | 476 | _d = distro_info.UbuntuDistroInfo() |
639 | 430 | _release_list = _d.all | 477 | _release_list = _d.all |
640 | 431 | self.log.debug('Ubuntu release list: {}'.format(_release_list)) | ||
641 | 432 | return _release_list | 478 | return _release_list |
642 | 433 | 479 | ||
643 | 434 | def file_to_url(self, file_rel_path): | 480 | def file_to_url(self, file_rel_path): |
644 | @@ -568,6 +614,142 @@ | |||
645 | 568 | 614 | ||
646 | 569 | return None | 615 | return None |
647 | 570 | 616 | ||
648 | 617 | def validate_sectionless_conf(self, file_contents, expected): | ||
649 | 618 | """A crude conf parser. Useful to inspect configuration files which | ||
650 | 619 | do not have section headers (as would be necessary in order to use | ||
651 | 620 | the configparser). Such as openstack-dashboard or rabbitmq confs.""" | ||
652 | 621 | for line in file_contents.split('\n'): | ||
653 | 622 | if '=' in line: | ||
654 | 623 | args = line.split('=') | ||
655 | 624 | if len(args) <= 1: | ||
656 | 625 | continue | ||
657 | 626 | key = args[0].strip() | ||
658 | 627 | value = args[1].strip() | ||
659 | 628 | if key in expected.keys(): | ||
660 | 629 | if expected[key] != value: | ||
661 | 630 | msg = ('Config mismatch. Expected, actual: {}, ' | ||
662 | 631 | '{}'.format(expected[key], value)) | ||
663 | 632 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
664 | 633 | |||
665 | 634 | def get_unit_hostnames(self, units): | ||
666 | 635 | """Return a dict of juju unit names to hostnames.""" | ||
667 | 636 | host_names = {} | ||
668 | 637 | for unit in units: | ||
669 | 638 | host_names[unit.info['unit_name']] = \ | ||
670 | 639 | str(unit.file_contents('/etc/hostname').strip()) | ||
671 | 640 | self.log.debug('Unit host names: {}'.format(host_names)) | ||
672 | 641 | return host_names | ||
673 | 642 | |||
674 | 643 | def run_cmd_unit(self, sentry_unit, cmd): | ||
675 | 644 | """Run a command on a unit, return the output and exit code.""" | ||
676 | 645 | output, code = sentry_unit.run(cmd) | ||
677 | 646 | if code == 0: | ||
678 | 647 | self.log.debug('{} `{}` command returned {} ' | ||
679 | 648 | '(OK)'.format(sentry_unit.info['unit_name'], | ||
680 | 649 | cmd, code)) | ||
681 | 650 | else: | ||
682 | 651 | msg = ('{} `{}` command returned {} ' | ||
683 | 652 | '{}'.format(sentry_unit.info['unit_name'], | ||
684 | 653 | cmd, code, output)) | ||
685 | 654 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
686 | 655 | return str(output), code | ||
687 | 656 | |||
688 | 657 | def file_exists_on_unit(self, sentry_unit, file_name): | ||
689 | 658 | """Check if a file exists on a unit.""" | ||
690 | 659 | try: | ||
691 | 660 | sentry_unit.file_stat(file_name) | ||
692 | 661 | return True | ||
693 | 662 | except IOError: | ||
694 | 663 | return False | ||
695 | 664 | except Exception as e: | ||
696 | 665 | msg = 'Error checking file {}: {}'.format(file_name, e) | ||
697 | 666 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
698 | 667 | |||
699 | 668 | def file_contents_safe(self, sentry_unit, file_name, | ||
700 | 669 | max_wait=60, fatal=False): | ||
701 | 670 | """Get file contents from a sentry unit. Wrap amulet file_contents | ||
702 | 671 | with retry logic to address races where a file checks as existing, | ||
703 | 672 | but no longer exists by the time file_contents is called. | ||
704 | 673 | Return None if file not found. Optionally raise if fatal is True.""" | ||
705 | 674 | unit_name = sentry_unit.info['unit_name'] | ||
706 | 675 | file_contents = False | ||
707 | 676 | tries = 0 | ||
708 | 677 | while not file_contents and tries < (max_wait / 4): | ||
709 | 678 | try: | ||
710 | 679 | file_contents = sentry_unit.file_contents(file_name) | ||
711 | 680 | except IOError: | ||
712 | 681 | self.log.debug('Attempt {} to open file {} from {} ' | ||
713 | 682 | 'failed'.format(tries, file_name, | ||
714 | 683 | unit_name)) | ||
715 | 684 | time.sleep(4) | ||
716 | 685 | tries += 1 | ||
717 | 686 | |||
718 | 687 | if file_contents: | ||
719 | 688 | return file_contents | ||
720 | 689 | elif not fatal: | ||
721 | 690 | return None | ||
722 | 691 | elif fatal: | ||
723 | 692 | msg = 'Failed to get file contents from unit.' | ||
724 | 693 | amulet.raise_status(amulet.FAIL, msg) | ||
725 | 694 | |||
726 | 695 | def port_knock_tcp(self, host="localhost", port=22, timeout=15): | ||
727 | 696 | """Open a TCP socket to check for a listening sevice on a host. | ||
728 | 697 | |||
729 | 698 | :param host: host name or IP address, default to localhost | ||
730 | 699 | :param port: TCP port number, default to 22 | ||
731 | 700 | :param timeout: Connect timeout, default to 15 seconds | ||
732 | 701 | :returns: True if successful, False if connect failed | ||
733 | 702 | """ | ||
734 | 703 | |||
735 | 704 | # Resolve host name if possible | ||
736 | 705 | try: | ||
737 | 706 | connect_host = socket.gethostbyname(host) | ||
738 | 707 | host_human = "{} ({})".format(connect_host, host) | ||
739 | 708 | except socket.error as e: | ||
740 | 709 | self.log.warn('Unable to resolve address: ' | ||
741 | 710 | '{} ({}) Trying anyway!'.format(host, e)) | ||
742 | 711 | connect_host = host | ||
743 | 712 | host_human = connect_host | ||
744 | 713 | |||
745 | 714 | # Attempt socket connection | ||
746 | 715 | try: | ||
747 | 716 | knock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
748 | 717 | knock.settimeout(timeout) | ||
749 | 718 | knock.connect((connect_host, port)) | ||
750 | 719 | knock.close() | ||
751 | 720 | self.log.debug('Socket connect OK for host ' | ||
752 | 721 | '{} on port {}.'.format(host_human, port)) | ||
753 | 722 | return True | ||
754 | 723 | except socket.error as e: | ||
755 | 724 | self.log.debug('Socket connect FAIL for' | ||
756 | 725 | ' {} port {} ({})'.format(host_human, port, e)) | ||
757 | 726 | return False | ||
758 | 727 | |||
759 | 728 | def port_knock_units(self, sentry_units, port=22, | ||
760 | 729 | timeout=15, expect_success=True): | ||
761 | 730 | """Open a TCP socket to check for a listening sevice on each | ||
762 | 731 | listed juju unit. | ||
763 | 732 | |||
764 | 733 | :param sentry_units: list of sentry unit pointers | ||
765 | 734 | :param port: TCP port number, default to 22 | ||
766 | 735 | :param timeout: Connect timeout, default to 15 seconds | ||
767 | 736 | :expect_success: True by default, set False to invert logic | ||
768 | 737 | :returns: None if successful, Failure message otherwise | ||
769 | 738 | """ | ||
770 | 739 | for unit in sentry_units: | ||
771 | 740 | host = unit.info['public-address'] | ||
772 | 741 | connected = self.port_knock_tcp(host, port, timeout) | ||
773 | 742 | if not connected and expect_success: | ||
774 | 743 | return 'Socket connect failed.' | ||
775 | 744 | elif connected and not expect_success: | ||
776 | 745 | return 'Socket connected unexpectedly.' | ||
777 | 746 | |||
778 | 747 | def get_uuid_epoch_stamp(self): | ||
779 | 748 | """Returns a stamp string based on uuid4 and epoch time. Useful in | ||
780 | 749 | generating test messages which need to be unique-ish.""" | ||
781 | 750 | return '[{}-{}]'.format(uuid.uuid4(), time.time()) | ||
782 | 751 | |||
783 | 752 | # amulet juju action helpers: | ||
784 | 571 | def run_action(self, unit_sentry, action, | 753 | def run_action(self, unit_sentry, action, |
785 | 572 | _check_output=subprocess.check_output): | 754 | _check_output=subprocess.check_output): |
786 | 573 | """Run the named action on a given unit sentry. | 755 | """Run the named action on a given unit sentry. |
787 | 574 | 756 | ||
788 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
789 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-18 17:34:34 +0000 | |||
790 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-10 09:35:29 +0000 | |||
791 | @@ -44,8 +44,15 @@ | |||
792 | 44 | Determine if the local branch being tested is derived from its | 44 | Determine if the local branch being tested is derived from its |
793 | 45 | stable or next (dev) branch, and based on this, use the corresonding | 45 | stable or next (dev) branch, and based on this, use the corresonding |
794 | 46 | stable or next branches for the other_services.""" | 46 | stable or next branches for the other_services.""" |
795 | 47 | |||
796 | 48 | # Charms outside the lp:~openstack-charmers namespace | ||
797 | 47 | base_charms = ['mysql', 'mongodb', 'nrpe'] | 49 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
798 | 48 | 50 | ||
799 | 51 | # Force these charms to current series even when using an older series. | ||
800 | 52 | # ie. Use trusty/nrpe even when series is precise, as the P charm | ||
801 | 53 | # does not possess the necessary external master config and hooks. | ||
802 | 54 | force_series_current = ['nrpe'] | ||
803 | 55 | |||
804 | 49 | if self.series in ['precise', 'trusty']: | 56 | if self.series in ['precise', 'trusty']: |
805 | 50 | base_series = self.series | 57 | base_series = self.series |
806 | 51 | else: | 58 | else: |
807 | @@ -53,11 +60,17 @@ | |||
808 | 53 | 60 | ||
809 | 54 | if self.stable: | 61 | if self.stable: |
810 | 55 | for svc in other_services: | 62 | for svc in other_services: |
811 | 63 | if svc['name'] in force_series_current: | ||
812 | 64 | base_series = self.current_next | ||
813 | 65 | |||
814 | 56 | temp = 'lp:charms/{}/{}' | 66 | temp = 'lp:charms/{}/{}' |
815 | 57 | svc['location'] = temp.format(base_series, | 67 | svc['location'] = temp.format(base_series, |
816 | 58 | svc['name']) | 68 | svc['name']) |
817 | 59 | else: | 69 | else: |
818 | 60 | for svc in other_services: | 70 | for svc in other_services: |
819 | 71 | if svc['name'] in force_series_current: | ||
820 | 72 | base_series = self.current_next | ||
821 | 73 | |||
822 | 61 | if svc['name'] in base_charms: | 74 | if svc['name'] in base_charms: |
823 | 62 | temp = 'lp:charms/{}/{}' | 75 | temp = 'lp:charms/{}/{}' |
824 | 63 | svc['location'] = temp.format(base_series, | 76 | svc['location'] = temp.format(base_series, |
825 | @@ -77,21 +90,23 @@ | |||
826 | 77 | 90 | ||
827 | 78 | services = other_services | 91 | services = other_services |
828 | 79 | services.append(this_service) | 92 | services.append(this_service) |
829 | 93 | |||
830 | 94 | # Charms which should use the source config option | ||
831 | 80 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | 95 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
832 | 81 | 'ceph-osd', 'ceph-radosgw'] | 96 | 'ceph-osd', 'ceph-radosgw'] |
836 | 82 | # Most OpenStack subordinate charms do not expose an origin option | 97 | |
837 | 83 | # as that is controlled by the principle. | 98 | # Charms which can not use openstack-origin, ie. many subordinates |
838 | 84 | ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] | 99 | no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] |
839 | 85 | 100 | ||
840 | 86 | if self.openstack: | 101 | if self.openstack: |
841 | 87 | for svc in services: | 102 | for svc in services: |
843 | 88 | if svc['name'] not in use_source + ignore: | 103 | if svc['name'] not in use_source + no_origin: |
844 | 89 | config = {'openstack-origin': self.openstack} | 104 | config = {'openstack-origin': self.openstack} |
845 | 90 | self.d.configure(svc['name'], config) | 105 | self.d.configure(svc['name'], config) |
846 | 91 | 106 | ||
847 | 92 | if self.source: | 107 | if self.source: |
848 | 93 | for svc in services: | 108 | for svc in services: |
850 | 94 | if svc['name'] in use_source and svc['name'] not in ignore: | 109 | if svc['name'] in use_source and svc['name'] not in no_origin: |
851 | 95 | config = {'source': self.source} | 110 | config = {'source': self.source} |
852 | 96 | self.d.configure(svc['name'], config) | 111 | self.d.configure(svc['name'], config) |
853 | 97 | 112 | ||
854 | 98 | 113 | ||
855 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' | |||
856 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:17:23 +0000 | |||
857 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-10 09:35:29 +0000 | |||
858 | @@ -27,6 +27,7 @@ | |||
859 | 27 | import heatclient.v1.client as heat_client | 27 | import heatclient.v1.client as heat_client |
860 | 28 | import keystoneclient.v2_0 as keystone_client | 28 | import keystoneclient.v2_0 as keystone_client |
861 | 29 | import novaclient.v1_1.client as nova_client | 29 | import novaclient.v1_1.client as nova_client |
862 | 30 | import pika | ||
863 | 30 | import swiftclient | 31 | import swiftclient |
864 | 31 | 32 | ||
865 | 32 | from charmhelpers.contrib.amulet.utils import ( | 33 | from charmhelpers.contrib.amulet.utils import ( |
866 | @@ -602,3 +603,361 @@ | |||
867 | 602 | self.log.debug('Ceph {} samples (OK): ' | 603 | self.log.debug('Ceph {} samples (OK): ' |
868 | 603 | '{}'.format(sample_type, samples)) | 604 | '{}'.format(sample_type, samples)) |
869 | 604 | return None | 605 | return None |
870 | 606 | |||
871 | 607 | # rabbitmq/amqp specific helpers: | ||
872 | 608 | def add_rmq_test_user(self, sentry_units, | ||
873 | 609 | username="testuser1", password="changeme"): | ||
874 | 610 | """Add a test user via the first rmq juju unit, check connection as | ||
875 | 611 | the new user against all sentry units. | ||
876 | 612 | |||
877 | 613 | :param sentry_units: list of sentry unit pointers | ||
878 | 614 | :param username: amqp user name, default to testuser1 | ||
879 | 615 | :param password: amqp user password | ||
880 | 616 | :returns: None if successful. Raise on error. | ||
881 | 617 | """ | ||
882 | 618 | self.log.debug('Adding rmq user ({})...'.format(username)) | ||
883 | 619 | |||
884 | 620 | # Check that user does not already exist | ||
885 | 621 | cmd_user_list = 'rabbitmqctl list_users' | ||
886 | 622 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
887 | 623 | if username in output: | ||
888 | 624 | self.log.warning('User ({}) already exists, returning ' | ||
889 | 625 | 'gracefully.'.format(username)) | ||
890 | 626 | return | ||
891 | 627 | |||
892 | 628 | perms = '".*" ".*" ".*"' | ||
893 | 629 | cmds = ['rabbitmqctl add_user {} {}'.format(username, password), | ||
894 | 630 | 'rabbitmqctl set_permissions {} {}'.format(username, perms)] | ||
895 | 631 | |||
896 | 632 | # Add user via first unit | ||
897 | 633 | for cmd in cmds: | ||
898 | 634 | output, _ = self.run_cmd_unit(sentry_units[0], cmd) | ||
899 | 635 | |||
900 | 636 | # Check connection against the other sentry_units | ||
901 | 637 | self.log.debug('Checking user connect against units...') | ||
902 | 638 | for sentry_unit in sentry_units: | ||
903 | 639 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=False, | ||
904 | 640 | username=username, | ||
905 | 641 | password=password) | ||
906 | 642 | connection.close() | ||
907 | 643 | |||
908 | 644 | def delete_rmq_test_user(self, sentry_units, username="testuser1"): | ||
909 | 645 | """Delete a rabbitmq user via the first rmq juju unit. | ||
910 | 646 | |||
911 | 647 | :param sentry_units: list of sentry unit pointers | ||
912 | 648 | :param username: amqp user name, default to testuser1 | ||
913 | 649 | :param password: amqp user password | ||
914 | 650 | :returns: None if successful or no such user. | ||
915 | 651 | """ | ||
916 | 652 | self.log.debug('Deleting rmq user ({})...'.format(username)) | ||
917 | 653 | |||
918 | 654 | # Check that the user exists | ||
919 | 655 | cmd_user_list = 'rabbitmqctl list_users' | ||
920 | 656 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
921 | 657 | |||
922 | 658 | if username not in output: | ||
923 | 659 | self.log.warning('User ({}) does not exist, returning ' | ||
924 | 660 | 'gracefully.'.format(username)) | ||
925 | 661 | return | ||
926 | 662 | |||
927 | 663 | # Delete the user | ||
928 | 664 | cmd_user_del = 'rabbitmqctl delete_user {}'.format(username) | ||
929 | 665 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_del) | ||
930 | 666 | |||
931 | 667 | def get_rmq_cluster_status(self, sentry_unit): | ||
932 | 668 | """Execute rabbitmq cluster status command on a unit and return | ||
933 | 669 | the full output. | ||
934 | 670 | |||
935 | 671 | :param unit: sentry unit | ||
936 | 672 | :returns: String containing console output of cluster status command | ||
937 | 673 | """ | ||
938 | 674 | cmd = 'rabbitmqctl cluster_status' | ||
939 | 675 | output, _ = self.run_cmd_unit(sentry_unit, cmd) | ||
940 | 676 | self.log.debug('{} cluster_status:\n{}'.format( | ||
941 | 677 | sentry_unit.info['unit_name'], output)) | ||
942 | 678 | return str(output) | ||
943 | 679 | |||
944 | 680 | def get_rmq_cluster_running_nodes(self, sentry_unit): | ||
945 | 681 | """Parse rabbitmqctl cluster_status output string, return list of | ||
946 | 682 | running rabbitmq cluster nodes. | ||
947 | 683 | |||
948 | 684 | :param unit: sentry unit | ||
949 | 685 | :returns: List containing node names of running nodes | ||
950 | 686 | """ | ||
951 | 687 | # NOTE(beisner): rabbitmqctl cluster_status output is not | ||
952 | 688 | # json-parsable, do string chop foo, then json.loads that. | ||
953 | 689 | str_stat = self.get_rmq_cluster_status(sentry_unit) | ||
954 | 690 | if 'running_nodes' in str_stat: | ||
955 | 691 | pos_start = str_stat.find("{running_nodes,") + 15 | ||
956 | 692 | pos_end = str_stat.find("]},", pos_start) + 1 | ||
957 | 693 | str_run_nodes = str_stat[pos_start:pos_end].replace("'", '"') | ||
958 | 694 | run_nodes = json.loads(str_run_nodes) | ||
959 | 695 | return run_nodes | ||
960 | 696 | else: | ||
961 | 697 | return [] | ||
962 | 698 | |||
963 | 699 | def validate_rmq_cluster_running_nodes(self, sentry_units): | ||
964 | 700 | """Check that all rmq unit hostnames are represented in the | ||
965 | 701 | cluster_status output of all units. | ||
966 | 702 | |||
967 | 703 | :param host_names: dict of juju unit names to host names | ||
968 | 704 | :param units: list of sentry unit pointers (all rmq units) | ||
969 | 705 | :returns: None if successful, otherwise return error message | ||
970 | 706 | """ | ||
971 | 707 | host_names = self.get_unit_hostnames(sentry_units) | ||
972 | 708 | errors = [] | ||
973 | 709 | |||
974 | 710 | # Query every unit for cluster_status running nodes | ||
975 | 711 | for query_unit in sentry_units: | ||
976 | 712 | query_unit_name = query_unit.info['unit_name'] | ||
977 | 713 | running_nodes = self.get_rmq_cluster_running_nodes(query_unit) | ||
978 | 714 | |||
979 | 715 | # Confirm that every unit is represented in the queried unit's | ||
980 | 716 | # cluster_status running nodes output. | ||
981 | 717 | for validate_unit in sentry_units: | ||
982 | 718 | val_host_name = host_names[validate_unit.info['unit_name']] | ||
983 | 719 | val_node_name = 'rabbit@{}'.format(val_host_name) | ||
984 | 720 | |||
985 | 721 | if val_node_name not in running_nodes: | ||
986 | 722 | errors.append('Cluster member check failed on {}: {} not ' | ||
987 | 723 | 'in {}\n'.format(query_unit_name, | ||
988 | 724 | val_node_name, | ||
989 | 725 | running_nodes)) | ||
990 | 726 | if errors: | ||
991 | 727 | return ''.join(errors) | ||
992 | 728 | |||
993 | 729 | def rmq_ssl_is_enabled_on_unit(self, sentry_unit, port=None): | ||
994 | 730 | """Check a single juju rmq unit for ssl and port in the config file.""" | ||
995 | 731 | host = sentry_unit.info['public-address'] | ||
996 | 732 | unit_name = sentry_unit.info['unit_name'] | ||
997 | 733 | |||
998 | 734 | conf_file = '/etc/rabbitmq/rabbitmq.config' | ||
999 | 735 | conf_contents = str(self.file_contents_safe(sentry_unit, | ||
1000 | 736 | conf_file, max_wait=16)) | ||
1001 | 737 | # Checks | ||
1002 | 738 | conf_ssl = 'ssl' in conf_contents | ||
1003 | 739 | conf_port = str(port) in conf_contents | ||
1004 | 740 | |||
1005 | 741 | # Port explicitly checked in config | ||
1006 | 742 | if port and conf_port and conf_ssl: | ||
1007 | 743 | self.log.debug('SSL is enabled @{}:{} ' | ||
1008 | 744 | '({})'.format(host, port, unit_name)) | ||
1009 | 745 | return True | ||
1010 | 746 | elif port and not conf_port and conf_ssl: | ||
1011 | 747 | self.log.debug('SSL is enabled @{} but not on port {} ' | ||
1012 | 748 | '({})'.format(host, port, unit_name)) | ||
1013 | 749 | return False | ||
1014 | 750 | # Port not checked (useful when checking that ssl is disabled) | ||
1015 | 751 | elif not port and conf_ssl: | ||
1016 | 752 | self.log.debug('SSL is enabled @{}:{} ' | ||
1017 | 753 | '({})'.format(host, port, unit_name)) | ||
1018 | 754 | return True | ||
1019 | 755 | elif not port and not conf_ssl: | ||
1020 | 756 | self.log.debug('SSL not enabled @{}:{} ' | ||
1021 | 757 | '({})'.format(host, port, unit_name)) | ||
1022 | 758 | return False | ||
1023 | 759 | else: | ||
1024 | 760 | msg = ('Unknown condition when checking SSL status @{}:{} ' | ||
1025 | 761 | '({})'.format(host, port, unit_name)) | ||
1026 | 762 | amulet.raise_status(amulet.FAIL, msg) | ||
1027 | 763 | |||
1028 | 764 | def validate_rmq_ssl_enabled_units(self, sentry_units, port=None): | ||
1029 | 765 | """Check that ssl is enabled on rmq juju sentry units. | ||
1030 | 766 | |||
1031 | 767 | :param sentry_units: list of all rmq sentry units | ||
1032 | 768 | :param port: optional ssl port override to validate | ||
1033 | 769 | :returns: None if successful, otherwise return error message | ||
1034 | 770 | """ | ||
1035 | 771 | for sentry_unit in sentry_units: | ||
1036 | 772 | if not self.rmq_ssl_is_enabled_on_unit(sentry_unit, port=port): | ||
1037 | 773 | return ('Unexpected condition: ssl is disabled on unit ' | ||
1038 | 774 | '({})'.format(sentry_unit.info['unit_name'])) | ||
1039 | 775 | return None | ||
1040 | 776 | |||
1041 | 777 | def validate_rmq_ssl_disabled_units(self, sentry_units): | ||
1042 | 778 | """Check that ssl is enabled on listed rmq juju sentry units. | ||
1043 | 779 | |||
1044 | 780 | :param sentry_units: list of all rmq sentry units | ||
1045 | 781 | :returns: True if successful. Raise on error. | ||
1046 | 782 | """ | ||
1047 | 783 | for sentry_unit in sentry_units: | ||
1048 | 784 | if self.rmq_ssl_is_enabled_on_unit(sentry_unit): | ||
1049 | 785 | return ('Unexpected condition: ssl is enabled on unit ' | ||
1050 | 786 | '({})'.format(sentry_unit.info['unit_name'])) | ||
1051 | 787 | return None | ||
1052 | 788 | |||
1053 | 789 | def configure_rmq_ssl_on(self, sentry_units, deployment, | ||
1054 | 790 | port=None, max_wait=60): | ||
1055 | 791 | """Turn ssl charm config option on, with optional non-default | ||
1056 | 792 | ssl port specification. Confirm that it is enabled on every | ||
1057 | 793 | unit. | ||
1058 | 794 | |||
1059 | 795 | :param sentry_units: list of sentry units | ||
1060 | 796 | :param deployment: amulet deployment object pointer | ||
1061 | 797 | :param port: amqp port, use defaults if None | ||
1062 | 798 | :param max_wait: maximum time to wait in seconds to confirm | ||
1063 | 799 | :returns: None if successful. Raise on error. | ||
1064 | 800 | """ | ||
1065 | 801 | self.log.debug('Setting ssl charm config option: on') | ||
1066 | 802 | |||
1067 | 803 | # Enable RMQ SSL | ||
1068 | 804 | config = {'ssl': 'on'} | ||
1069 | 805 | if port: | ||
1070 | 806 | config['ssl_port'] = port | ||
1071 | 807 | |||
1072 | 808 | deployment.configure('rabbitmq-server', config) | ||
1073 | 809 | |||
1074 | 810 | # Confirm | ||
1075 | 811 | tries = 0 | ||
1076 | 812 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
1077 | 813 | while ret and tries < (max_wait / 4): | ||
1078 | 814 | time.sleep(4) | ||
1079 | 815 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
1080 | 816 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
1081 | 817 | tries += 1 | ||
1082 | 818 | |||
1083 | 819 | if ret: | ||
1084 | 820 | amulet.raise_status(amulet.FAIL, ret) | ||
1085 | 821 | |||
1086 | 822 | def configure_rmq_ssl_off(self, sentry_units, deployment, max_wait=60): | ||
1087 | 823 | """Turn ssl charm config option off, confirm that it is disabled | ||
1088 | 824 | on every unit. | ||
1089 | 825 | |||
1090 | 826 | :param sentry_units: list of sentry units | ||
1091 | 827 | :param deployment: amulet deployment object pointer | ||
1092 | 828 | :param max_wait: maximum time to wait in seconds to confirm | ||
1093 | 829 | :returns: None if successful. Raise on error. | ||
1094 | 830 | """ | ||
1095 | 831 | self.log.debug('Setting ssl charm config option: off') | ||
1096 | 832 | |||
1097 | 833 | # Disable RMQ SSL | ||
1098 | 834 | config = {'ssl': 'off'} | ||
1099 | 835 | deployment.configure('rabbitmq-server', config) | ||
1100 | 836 | |||
1101 | 837 | # Confirm | ||
1102 | 838 | tries = 0 | ||
1103 | 839 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
1104 | 840 | while ret and tries < (max_wait / 4): | ||
1105 | 841 | time.sleep(4) | ||
1106 | 842 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
1107 | 843 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
1108 | 844 | tries += 1 | ||
1109 | 845 | |||
1110 | 846 | if ret: | ||
1111 | 847 | amulet.raise_status(amulet.FAIL, ret) | ||
1112 | 848 | |||
1113 | 849 | def connect_amqp_by_unit(self, sentry_unit, ssl=False, | ||
1114 | 850 | port=None, fatal=True, | ||
1115 | 851 | username="testuser1", password="changeme"): | ||
1116 | 852 | """Establish and return a pika amqp connection to the rabbitmq service | ||
1117 | 853 | running on a rmq juju unit. | ||
1118 | 854 | |||
1119 | 855 | :param sentry_unit: sentry unit pointer | ||
1120 | 856 | :param ssl: boolean, default to False | ||
1121 | 857 | :param port: amqp port, use defaults if None | ||
1122 | 858 | :param fatal: boolean, default to True (raises on connect error) | ||
1123 | 859 | :param username: amqp user name, default to testuser1 | ||
1124 | 860 | :param password: amqp user password | ||
1125 | 861 | :returns: pika amqp connection pointer or None if failed and non-fatal | ||
1126 | 862 | """ | ||
1127 | 863 | host = sentry_unit.info['public-address'] | ||
1128 | 864 | unit_name = sentry_unit.info['unit_name'] | ||
1129 | 865 | |||
1130 | 866 | # Default port logic if port is not specified | ||
1131 | 867 | if ssl and not port: | ||
1132 | 868 | port = 5671 | ||
1133 | 869 | elif not ssl and not port: | ||
1134 | 870 | port = 5672 | ||
1135 | 871 | |||
1136 | 872 | self.log.debug('Connecting to amqp on {}:{} ({}) as ' | ||
1137 | 873 | '{}...'.format(host, port, unit_name, username)) | ||
1138 | 874 | |||
1139 | 875 | try: | ||
1140 | 876 | credentials = pika.PlainCredentials(username, password) | ||
1141 | 877 | parameters = pika.ConnectionParameters(host=host, port=port, | ||
1142 | 878 | credentials=credentials, | ||
1143 | 879 | ssl=ssl, | ||
1144 | 880 | connection_attempts=3, | ||
1145 | 881 | retry_delay=5, | ||
1146 | 882 | socket_timeout=1) | ||
1147 | 883 | connection = pika.BlockingConnection(parameters) | ||
1148 | 884 | assert connection.server_properties['product'] == 'RabbitMQ' | ||
1149 | 885 | self.log.debug('Connect OK') | ||
1150 | 886 | return connection | ||
1151 | 887 | except Exception as e: | ||
1152 | 888 | msg = ('amqp connection failed to {}:{} as ' | ||
1153 | 889 | '{} ({})'.format(host, port, username, str(e))) | ||
1154 | 890 | if fatal: | ||
1155 | 891 | amulet.raise_status(amulet.FAIL, msg) | ||
1156 | 892 | else: | ||
1157 | 893 | self.log.warn(msg) | ||
1158 | 894 | return None | ||
1159 | 895 | |||
1160 | 896 | def publish_amqp_message_by_unit(self, sentry_unit, message, | ||
1161 | 897 | queue="test", ssl=False, | ||
1162 | 898 | username="testuser1", | ||
1163 | 899 | password="changeme", | ||
1164 | 900 | port=None): | ||
1165 | 901 | """Publish an amqp message to a rmq juju unit. | ||
1166 | 902 | |||
1167 | 903 | :param sentry_unit: sentry unit pointer | ||
1168 | 904 | :param message: amqp message string | ||
1169 | 905 | :param queue: message queue, default to test | ||
1170 | 906 | :param username: amqp user name, default to testuser1 | ||
1171 | 907 | :param password: amqp user password | ||
1172 | 908 | :param ssl: boolean, default to False | ||
1173 | 909 | :param port: amqp port, use defaults if None | ||
1174 | 910 | :returns: None. Raises exception if publish failed. | ||
1175 | 911 | """ | ||
1176 | 912 | self.log.debug('Publishing message to {} queue:\n{}'.format(queue, | ||
1177 | 913 | message)) | ||
1178 | 914 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
1179 | 915 | port=port, | ||
1180 | 916 | username=username, | ||
1181 | 917 | password=password) | ||
1182 | 918 | |||
1183 | 919 | # NOTE(beisner): extra debug here re: pika hang potential: | ||
1184 | 920 | # https://github.com/pika/pika/issues/297 | ||
1185 | 921 | # https://groups.google.com/forum/#!topic/rabbitmq-users/Ja0iyfF0Szw | ||
1186 | 922 | self.log.debug('Defining channel...') | ||
1187 | 923 | channel = connection.channel() | ||
1188 | 924 | self.log.debug('Declaring queue...') | ||
1189 | 925 | channel.queue_declare(queue=queue, auto_delete=False, durable=True) | ||
1190 | 926 | self.log.debug('Publishing message...') | ||
1191 | 927 | channel.basic_publish(exchange='', routing_key=queue, body=message) | ||
1192 | 928 | self.log.debug('Closing channel...') | ||
1193 | 929 | channel.close() | ||
1194 | 930 | self.log.debug('Closing connection...') | ||
1195 | 931 | connection.close() | ||
1196 | 932 | |||
1197 | 933 | def get_amqp_message_by_unit(self, sentry_unit, queue="test", | ||
1198 | 934 | username="testuser1", | ||
1199 | 935 | password="changeme", | ||
1200 | 936 | ssl=False, port=None): | ||
1201 | 937 | """Get an amqp message from a rmq juju unit. | ||
1202 | 938 | |||
1203 | 939 | :param sentry_unit: sentry unit pointer | ||
1204 | 940 | :param queue: message queue, default to test | ||
1205 | 941 | :param username: amqp user name, default to testuser1 | ||
1206 | 942 | :param password: amqp user password | ||
1207 | 943 | :param ssl: boolean, default to False | ||
1208 | 944 | :param port: amqp port, use defaults if None | ||
1209 | 945 | :returns: amqp message body as string. Raise if get fails. | ||
1210 | 946 | """ | ||
1211 | 947 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
1212 | 948 | port=port, | ||
1213 | 949 | username=username, | ||
1214 | 950 | password=password) | ||
1215 | 951 | channel = connection.channel() | ||
1216 | 952 | method_frame, _, body = channel.basic_get(queue) | ||
1217 | 953 | |||
1218 | 954 | if method_frame: | ||
1219 | 955 | self.log.debug('Retreived message from {} queue:\n{}'.format(queue, | ||
1220 | 956 | body)) | ||
1221 | 957 | channel.basic_ack(method_frame.delivery_tag) | ||
1222 | 958 | channel.close() | ||
1223 | 959 | connection.close() | ||
1224 | 960 | return body | ||
1225 | 961 | else: | ||
1226 | 962 | msg = 'No message retrieved.' | ||
1227 | 963 | amulet.raise_status(amulet.FAIL, msg) | ||
1228 | 605 | 964 | ||
1229 | === modified file 'unit_tests/test_glance_relations.py' | |||
1230 | --- unit_tests/test_glance_relations.py 2015-08-26 08:43:57 +0000 | |||
1231 | +++ unit_tests/test_glance_relations.py 2015-09-10 09:35:29 +0000 | |||
1232 | @@ -1,5 +1,4 @@ | |||
1233 | 1 | from mock import call, patch, MagicMock | 1 | from mock import call, patch, MagicMock |
1234 | 2 | import json | ||
1235 | 3 | import os | 2 | import os |
1236 | 4 | import yaml | 3 | import yaml |
1237 | 5 | 4 | ||
1238 | @@ -392,42 +391,53 @@ | |||
1239 | 392 | 'Could not create ceph keyring: peer not ready?' | 391 | 'Could not create ceph keyring: peer not ready?' |
1240 | 393 | ) | 392 | ) |
1241 | 394 | 393 | ||
1242 | 394 | <<<<<<< TREE | ||
1243 | 395 | @patch("hooks.glance_relations.relation_set") | 395 | @patch("hooks.glance_relations.relation_set") |
1244 | 396 | @patch("hooks.glance_relations.relation_get") | 396 | @patch("hooks.glance_relations.relation_get") |
1245 | 397 | ======= | ||
1246 | 398 | @patch("glance_relations.get_ceph_request") | ||
1247 | 399 | @patch("glance_relations.send_request_if_needed") | ||
1248 | 400 | @patch("glance_relations.is_request_complete") | ||
1249 | 401 | >>>>>>> MERGE-SOURCE | ||
1250 | 397 | @patch.object(relations, 'CONFIGS') | 402 | @patch.object(relations, 'CONFIGS') |
1253 | 398 | def test_ceph_changed_broker_send_rq(self, configs, mock_relation_get, | 403 | def test_ceph_changed_broker_send_rq(self, configs, mock_request_complete, |
1254 | 399 | mock_relation_set): | 404 | mock_send_request_if_needed, |
1255 | 405 | mock_get_ceph_request): | ||
1256 | 406 | self.service_name.return_value = 'glance' | ||
1257 | 407 | configs.complete_contexts = MagicMock() | ||
1258 | 400 | configs.complete_contexts.return_value = ['ceph'] | 408 | configs.complete_contexts.return_value = ['ceph'] |
1260 | 401 | self.service_name.return_value = 'glance' | 409 | mock_get_ceph_request.return_value = 'cephrq' |
1261 | 402 | self.ensure_ceph_keyring.return_value = True | 410 | self.ensure_ceph_keyring.return_value = True |
1263 | 403 | self.relation_ids.return_value = ['ceph:0'] | 411 | mock_request_complete.return_value = False |
1264 | 404 | relations.hooks.execute(['hooks/ceph-relation-changed']) | 412 | relations.hooks.execute(['hooks/ceph-relation-changed']) |
1265 | 405 | self.ensure_ceph_keyring.assert_called_with(service='glance', | 413 | self.ensure_ceph_keyring.assert_called_with(service='glance', |
1266 | 406 | user='glance', | 414 | user='glance', |
1267 | 407 | group='glance') | 415 | group='glance') |
1268 | 408 | req = {'api-version': 1, | ||
1269 | 409 | 'ops': [{"op": "create-pool", "name": "glance", "replicas": 3}]} | ||
1270 | 410 | broker_dict = json.dumps(req) | ||
1271 | 411 | mock_relation_set.assert_called_with(broker_req=broker_dict, | ||
1272 | 412 | relation_id='ceph:0') | ||
1273 | 413 | for c in [call('/etc/glance/glance.conf')]: | 416 | for c in [call('/etc/glance/glance.conf')]: |
1274 | 414 | self.assertNotIn(c, configs.write.call_args_list) | 417 | self.assertNotIn(c, configs.write.call_args_list) |
1275 | 415 | 418 | ||
1276 | 419 | <<<<<<< TREE | ||
1277 | 416 | @patch("charmhelpers.core.host.service") | 420 | @patch("charmhelpers.core.host.service") |
1278 | 417 | @patch("hooks.glance_relations.relation_get", autospec=True) | 421 | @patch("hooks.glance_relations.relation_get", autospec=True) |
1279 | 422 | ======= | ||
1280 | 423 | @patch("glance_relations.get_ceph_request") | ||
1281 | 424 | @patch("glance_relations.send_request_if_needed") | ||
1282 | 425 | @patch("glance_relations.is_request_complete") | ||
1283 | 426 | >>>>>>> MERGE-SOURCE | ||
1284 | 418 | @patch.object(relations, 'CONFIGS') | 427 | @patch.object(relations, 'CONFIGS') |
1288 | 419 | def test_ceph_changed_with_key_and_relation_data(self, configs, | 428 | def test_ceph_changed_key_and_relation_data(self, configs, |
1289 | 420 | mock_relation_get, | 429 | mock_request_complete, |
1290 | 421 | mock_service): | 430 | mock_send_request_if_needed, |
1291 | 431 | mock_service): | ||
1292 | 422 | configs.complete_contexts = MagicMock() | 432 | configs.complete_contexts = MagicMock() |
1293 | 423 | configs.complete_contexts.return_value = ['ceph'] | 433 | configs.complete_contexts.return_value = ['ceph'] |
1294 | 424 | configs.write = MagicMock() | 434 | configs.write = MagicMock() |
1295 | 425 | self.ensure_ceph_keyring.return_value = True | 435 | self.ensure_ceph_keyring.return_value = True |
1298 | 426 | mock_relation_get.return_value = {'broker_rsp': | 436 | mock_request_complete.return_value = True |
1299 | 427 | json.dumps({'exit-code': 0})} | 437 | self.ceph_config_file.return_value = '/etc/ceph/ceph.conf' |
1300 | 428 | relations.ceph_changed() | 438 | relations.ceph_changed() |
1301 | 429 | self.assertEquals([call('/etc/glance/glance-api.conf'), | 439 | self.assertEquals([call('/etc/glance/glance-api.conf'), |
1303 | 430 | call(self.ceph_config_file())], | 440 | call('/etc/ceph/ceph.conf')], |
1304 | 431 | configs.write.call_args_list) | 441 | configs.write.call_args_list) |
1305 | 432 | self.service_restart.assert_called_with('glance-api') | 442 | self.service_restart.assert_called_with('glance-api') |
1306 | 433 | 443 |
charm_lint_check #8424 glance-next for gnuoy mp268605
LINT FAIL: lint-test failed
LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.
Full lint test output: http:// paste.ubuntu. com/12136056/ 10.245. 162.77: 8080/job/ charm_lint_ check/8424/
Build: http://