Merge lp:~xianghui/charms/trusty/nova-cloud-controller/live_migration into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next
- Trusty Tahr (14.04)
- live_migration
- Merge into next
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 189 | ||||
Proposed branch: | lp:~xianghui/charms/trusty/nova-cloud-controller/live_migration | ||||
Merge into: | lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next | ||||
Diff against target: |
2135 lines (+1496/-110) 15 files modified
hooks/charmhelpers/contrib/network/ip.py (+5/-3) hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+13/-6) hooks/charmhelpers/contrib/openstack/amulet/utils.py (+359/-0) hooks/charmhelpers/contrib/openstack/context.py (+60/-16) hooks/charmhelpers/contrib/openstack/templating.py (+29/-2) hooks/charmhelpers/contrib/openstack/utils.py (+221/-1) hooks/charmhelpers/contrib/storage/linux/ceph.py (+226/-13) hooks/charmhelpers/core/host.py (+3/-4) hooks/charmhelpers/core/kernel.py (+68/-0) hooks/charmhelpers/core/strutils.py (+30/-0) hooks/nova_cc_utils.py (+17/-5) tests/charmhelpers/contrib/amulet/deployment.py (+4/-2) tests/charmhelpers/contrib/amulet/utils.py (+96/-52) tests/charmhelpers/contrib/openstack/amulet/utils.py (+359/-0) unit_tests/test_nova_cc_utils.py (+6/-6) |
||||
To merge this branch: | bzr merge lp:~xianghui/charms/trusty/nova-cloud-controller/live_migration | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Billy Olsen | Approve | ||
Review via email: mp+267132@code.launchpad.net |
Commit message
Description of the change
Fix live-migration failed when re-add nova-compute unit.
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #7047 nova-cloud-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5646 nova-cloud-
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://
- 183. By Xiang Hui
-
Merge lp:charm-helpers.
- 184. By Xiang Hui
-
Fix conflicts.
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #10420 nova-cloud-
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 #9617 nova-cloud-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6564 nova-cloud-
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://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #10428 nova-cloud-
LINT OK: passed
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #9619 nova-cloud-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6571 nova-cloud-
AMULET OK: passed
Build: http://
Billy Olsen (billy-olsen) wrote : | # |
LGTM, thanks Xiang Hui!
Preview Diff
1 | === modified file 'hooks/charmhelpers/contrib/network/ip.py' | |||
2 | --- hooks/charmhelpers/contrib/network/ip.py 2015-09-03 09:39:34 +0000 | |||
3 | +++ hooks/charmhelpers/contrib/network/ip.py 2015-09-22 06:12:36 +0000 | |||
4 | @@ -23,7 +23,7 @@ | |||
5 | 23 | from functools import partial | 23 | from functools import partial |
6 | 24 | 24 | ||
7 | 25 | from charmhelpers.core.hookenv import unit_get | 25 | from charmhelpers.core.hookenv import unit_get |
9 | 26 | from charmhelpers.fetch import apt_install | 26 | from charmhelpers.fetch import apt_install, apt_update |
10 | 27 | from charmhelpers.core.hookenv import ( | 27 | from charmhelpers.core.hookenv import ( |
11 | 28 | log, | 28 | log, |
12 | 29 | WARNING, | 29 | WARNING, |
13 | @@ -32,13 +32,15 @@ | |||
14 | 32 | try: | 32 | try: |
15 | 33 | import netifaces | 33 | import netifaces |
16 | 34 | except ImportError: | 34 | except ImportError: |
18 | 35 | apt_install('python-netifaces') | 35 | apt_update(fatal=True) |
19 | 36 | apt_install('python-netifaces', fatal=True) | ||
20 | 36 | import netifaces | 37 | import netifaces |
21 | 37 | 38 | ||
22 | 38 | try: | 39 | try: |
23 | 39 | import netaddr | 40 | import netaddr |
24 | 40 | except ImportError: | 41 | except ImportError: |
26 | 41 | apt_install('python-netaddr') | 42 | apt_update(fatal=True) |
27 | 43 | apt_install('python-netaddr', fatal=True) | ||
28 | 42 | import netaddr | 44 | import netaddr |
29 | 43 | 45 | ||
30 | 44 | 46 | ||
31 | 45 | 47 | ||
32 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
33 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-18 17:34:35 +0000 | |||
34 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-22 06:12:36 +0000 | |||
35 | @@ -51,13 +51,17 @@ | |||
36 | 51 | else: | 51 | else: |
37 | 52 | base_series = self.current_next | 52 | base_series = self.current_next |
38 | 53 | 53 | ||
41 | 54 | if self.stable: | 54 | for svc in other_services: |
42 | 55 | for svc in other_services: | 55 | if svc['name'] in force_series_current: |
43 | 56 | base_series = self.current_next | ||
44 | 57 | # If a location has been explicitly set, use it | ||
45 | 58 | if svc.get('location'): | ||
46 | 59 | continue | ||
47 | 60 | if self.stable: | ||
48 | 56 | temp = 'lp:charms/{}/{}' | 61 | temp = 'lp:charms/{}/{}' |
49 | 57 | svc['location'] = temp.format(base_series, | 62 | svc['location'] = temp.format(base_series, |
50 | 58 | svc['name']) | 63 | svc['name']) |
53 | 59 | else: | 64 | else: |
52 | 60 | for svc in other_services: | ||
54 | 61 | if svc['name'] in base_charms: | 65 | if svc['name'] in base_charms: |
55 | 62 | temp = 'lp:charms/{}/{}' | 66 | temp = 'lp:charms/{}/{}' |
56 | 63 | svc['location'] = temp.format(base_series, | 67 | svc['location'] = temp.format(base_series, |
57 | @@ -66,6 +70,7 @@ | |||
58 | 66 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' | 70 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
59 | 67 | svc['location'] = temp.format(self.current_next, | 71 | svc['location'] = temp.format(self.current_next, |
60 | 68 | svc['name']) | 72 | svc['name']) |
61 | 73 | |||
62 | 69 | return other_services | 74 | return other_services |
63 | 70 | 75 | ||
64 | 71 | def _add_services(self, this_service, other_services): | 76 | def _add_services(self, this_service, other_services): |
65 | @@ -77,6 +82,8 @@ | |||
66 | 77 | 82 | ||
67 | 78 | services = other_services | 83 | services = other_services |
68 | 79 | services.append(this_service) | 84 | services.append(this_service) |
69 | 85 | |||
70 | 86 | # Charms which should use the source config option | ||
71 | 80 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | 87 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
72 | 81 | 'ceph-osd', 'ceph-radosgw'] | 88 | 'ceph-osd', 'ceph-radosgw'] |
73 | 82 | # Most OpenStack subordinate charms do not expose an origin option | 89 | # Most OpenStack subordinate charms do not expose an origin option |
74 | @@ -85,13 +92,13 @@ | |||
75 | 85 | 92 | ||
76 | 86 | if self.openstack: | 93 | if self.openstack: |
77 | 87 | for svc in services: | 94 | for svc in services: |
79 | 88 | if svc['name'] not in use_source + ignore: | 95 | if svc['name'] not in use_source + no_origin: |
80 | 89 | config = {'openstack-origin': self.openstack} | 96 | config = {'openstack-origin': self.openstack} |
81 | 90 | self.d.configure(svc['name'], config) | 97 | self.d.configure(svc['name'], config) |
82 | 91 | 98 | ||
83 | 92 | if self.source: | 99 | if self.source: |
84 | 93 | for svc in services: | 100 | for svc in services: |
86 | 94 | if svc['name'] in use_source and svc['name'] not in ignore: | 101 | if svc['name'] in use_source and svc['name'] not in no_origin: |
87 | 95 | config = {'source': self.source} | 102 | config = {'source': self.source} |
88 | 96 | self.d.configure(svc['name'], config) | 103 | self.d.configure(svc['name'], config) |
89 | 97 | 104 | ||
90 | 98 | 105 | ||
91 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' | |||
92 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:18:38 +0000 | |||
93 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-22 06:12:36 +0000 | |||
94 | @@ -27,6 +27,7 @@ | |||
95 | 27 | import heatclient.v1.client as heat_client | 27 | import heatclient.v1.client as heat_client |
96 | 28 | import keystoneclient.v2_0 as keystone_client | 28 | import keystoneclient.v2_0 as keystone_client |
97 | 29 | import novaclient.v1_1.client as nova_client | 29 | import novaclient.v1_1.client as nova_client |
98 | 30 | import pika | ||
99 | 30 | import swiftclient | 31 | import swiftclient |
100 | 31 | 32 | ||
101 | 32 | from charmhelpers.contrib.amulet.utils import ( | 33 | from charmhelpers.contrib.amulet.utils import ( |
102 | @@ -602,3 +603,361 @@ | |||
103 | 602 | self.log.debug('Ceph {} samples (OK): ' | 603 | self.log.debug('Ceph {} samples (OK): ' |
104 | 603 | '{}'.format(sample_type, samples)) | 604 | '{}'.format(sample_type, samples)) |
105 | 604 | return None | 605 | return None |
106 | 606 | |||
107 | 607 | # rabbitmq/amqp specific helpers: | ||
108 | 608 | def add_rmq_test_user(self, sentry_units, | ||
109 | 609 | username="testuser1", password="changeme"): | ||
110 | 610 | """Add a test user via the first rmq juju unit, check connection as | ||
111 | 611 | the new user against all sentry units. | ||
112 | 612 | |||
113 | 613 | :param sentry_units: list of sentry unit pointers | ||
114 | 614 | :param username: amqp user name, default to testuser1 | ||
115 | 615 | :param password: amqp user password | ||
116 | 616 | :returns: None if successful. Raise on error. | ||
117 | 617 | """ | ||
118 | 618 | self.log.debug('Adding rmq user ({})...'.format(username)) | ||
119 | 619 | |||
120 | 620 | # Check that user does not already exist | ||
121 | 621 | cmd_user_list = 'rabbitmqctl list_users' | ||
122 | 622 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
123 | 623 | if username in output: | ||
124 | 624 | self.log.warning('User ({}) already exists, returning ' | ||
125 | 625 | 'gracefully.'.format(username)) | ||
126 | 626 | return | ||
127 | 627 | |||
128 | 628 | perms = '".*" ".*" ".*"' | ||
129 | 629 | cmds = ['rabbitmqctl add_user {} {}'.format(username, password), | ||
130 | 630 | 'rabbitmqctl set_permissions {} {}'.format(username, perms)] | ||
131 | 631 | |||
132 | 632 | # Add user via first unit | ||
133 | 633 | for cmd in cmds: | ||
134 | 634 | output, _ = self.run_cmd_unit(sentry_units[0], cmd) | ||
135 | 635 | |||
136 | 636 | # Check connection against the other sentry_units | ||
137 | 637 | self.log.debug('Checking user connect against units...') | ||
138 | 638 | for sentry_unit in sentry_units: | ||
139 | 639 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=False, | ||
140 | 640 | username=username, | ||
141 | 641 | password=password) | ||
142 | 642 | connection.close() | ||
143 | 643 | |||
144 | 644 | def delete_rmq_test_user(self, sentry_units, username="testuser1"): | ||
145 | 645 | """Delete a rabbitmq user via the first rmq juju unit. | ||
146 | 646 | |||
147 | 647 | :param sentry_units: list of sentry unit pointers | ||
148 | 648 | :param username: amqp user name, default to testuser1 | ||
149 | 649 | :param password: amqp user password | ||
150 | 650 | :returns: None if successful or no such user. | ||
151 | 651 | """ | ||
152 | 652 | self.log.debug('Deleting rmq user ({})...'.format(username)) | ||
153 | 653 | |||
154 | 654 | # Check that the user exists | ||
155 | 655 | cmd_user_list = 'rabbitmqctl list_users' | ||
156 | 656 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
157 | 657 | |||
158 | 658 | if username not in output: | ||
159 | 659 | self.log.warning('User ({}) does not exist, returning ' | ||
160 | 660 | 'gracefully.'.format(username)) | ||
161 | 661 | return | ||
162 | 662 | |||
163 | 663 | # Delete the user | ||
164 | 664 | cmd_user_del = 'rabbitmqctl delete_user {}'.format(username) | ||
165 | 665 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_del) | ||
166 | 666 | |||
167 | 667 | def get_rmq_cluster_status(self, sentry_unit): | ||
168 | 668 | """Execute rabbitmq cluster status command on a unit and return | ||
169 | 669 | the full output. | ||
170 | 670 | |||
171 | 671 | :param unit: sentry unit | ||
172 | 672 | :returns: String containing console output of cluster status command | ||
173 | 673 | """ | ||
174 | 674 | cmd = 'rabbitmqctl cluster_status' | ||
175 | 675 | output, _ = self.run_cmd_unit(sentry_unit, cmd) | ||
176 | 676 | self.log.debug('{} cluster_status:\n{}'.format( | ||
177 | 677 | sentry_unit.info['unit_name'], output)) | ||
178 | 678 | return str(output) | ||
179 | 679 | |||
180 | 680 | def get_rmq_cluster_running_nodes(self, sentry_unit): | ||
181 | 681 | """Parse rabbitmqctl cluster_status output string, return list of | ||
182 | 682 | running rabbitmq cluster nodes. | ||
183 | 683 | |||
184 | 684 | :param unit: sentry unit | ||
185 | 685 | :returns: List containing node names of running nodes | ||
186 | 686 | """ | ||
187 | 687 | # NOTE(beisner): rabbitmqctl cluster_status output is not | ||
188 | 688 | # json-parsable, do string chop foo, then json.loads that. | ||
189 | 689 | str_stat = self.get_rmq_cluster_status(sentry_unit) | ||
190 | 690 | if 'running_nodes' in str_stat: | ||
191 | 691 | pos_start = str_stat.find("{running_nodes,") + 15 | ||
192 | 692 | pos_end = str_stat.find("]},", pos_start) + 1 | ||
193 | 693 | str_run_nodes = str_stat[pos_start:pos_end].replace("'", '"') | ||
194 | 694 | run_nodes = json.loads(str_run_nodes) | ||
195 | 695 | return run_nodes | ||
196 | 696 | else: | ||
197 | 697 | return [] | ||
198 | 698 | |||
199 | 699 | def validate_rmq_cluster_running_nodes(self, sentry_units): | ||
200 | 700 | """Check that all rmq unit hostnames are represented in the | ||
201 | 701 | cluster_status output of all units. | ||
202 | 702 | |||
203 | 703 | :param host_names: dict of juju unit names to host names | ||
204 | 704 | :param units: list of sentry unit pointers (all rmq units) | ||
205 | 705 | :returns: None if successful, otherwise return error message | ||
206 | 706 | """ | ||
207 | 707 | host_names = self.get_unit_hostnames(sentry_units) | ||
208 | 708 | errors = [] | ||
209 | 709 | |||
210 | 710 | # Query every unit for cluster_status running nodes | ||
211 | 711 | for query_unit in sentry_units: | ||
212 | 712 | query_unit_name = query_unit.info['unit_name'] | ||
213 | 713 | running_nodes = self.get_rmq_cluster_running_nodes(query_unit) | ||
214 | 714 | |||
215 | 715 | # Confirm that every unit is represented in the queried unit's | ||
216 | 716 | # cluster_status running nodes output. | ||
217 | 717 | for validate_unit in sentry_units: | ||
218 | 718 | val_host_name = host_names[validate_unit.info['unit_name']] | ||
219 | 719 | val_node_name = 'rabbit@{}'.format(val_host_name) | ||
220 | 720 | |||
221 | 721 | if val_node_name not in running_nodes: | ||
222 | 722 | errors.append('Cluster member check failed on {}: {} not ' | ||
223 | 723 | 'in {}\n'.format(query_unit_name, | ||
224 | 724 | val_node_name, | ||
225 | 725 | running_nodes)) | ||
226 | 726 | if errors: | ||
227 | 727 | return ''.join(errors) | ||
228 | 728 | |||
229 | 729 | def rmq_ssl_is_enabled_on_unit(self, sentry_unit, port=None): | ||
230 | 730 | """Check a single juju rmq unit for ssl and port in the config file.""" | ||
231 | 731 | host = sentry_unit.info['public-address'] | ||
232 | 732 | unit_name = sentry_unit.info['unit_name'] | ||
233 | 733 | |||
234 | 734 | conf_file = '/etc/rabbitmq/rabbitmq.config' | ||
235 | 735 | conf_contents = str(self.file_contents_safe(sentry_unit, | ||
236 | 736 | conf_file, max_wait=16)) | ||
237 | 737 | # Checks | ||
238 | 738 | conf_ssl = 'ssl' in conf_contents | ||
239 | 739 | conf_port = str(port) in conf_contents | ||
240 | 740 | |||
241 | 741 | # Port explicitly checked in config | ||
242 | 742 | if port and conf_port and conf_ssl: | ||
243 | 743 | self.log.debug('SSL is enabled @{}:{} ' | ||
244 | 744 | '({})'.format(host, port, unit_name)) | ||
245 | 745 | return True | ||
246 | 746 | elif port and not conf_port and conf_ssl: | ||
247 | 747 | self.log.debug('SSL is enabled @{} but not on port {} ' | ||
248 | 748 | '({})'.format(host, port, unit_name)) | ||
249 | 749 | return False | ||
250 | 750 | # Port not checked (useful when checking that ssl is disabled) | ||
251 | 751 | elif not port and conf_ssl: | ||
252 | 752 | self.log.debug('SSL is enabled @{}:{} ' | ||
253 | 753 | '({})'.format(host, port, unit_name)) | ||
254 | 754 | return True | ||
255 | 755 | elif not port and not conf_ssl: | ||
256 | 756 | self.log.debug('SSL not enabled @{}:{} ' | ||
257 | 757 | '({})'.format(host, port, unit_name)) | ||
258 | 758 | return False | ||
259 | 759 | else: | ||
260 | 760 | msg = ('Unknown condition when checking SSL status @{}:{} ' | ||
261 | 761 | '({})'.format(host, port, unit_name)) | ||
262 | 762 | amulet.raise_status(amulet.FAIL, msg) | ||
263 | 763 | |||
264 | 764 | def validate_rmq_ssl_enabled_units(self, sentry_units, port=None): | ||
265 | 765 | """Check that ssl is enabled on rmq juju sentry units. | ||
266 | 766 | |||
267 | 767 | :param sentry_units: list of all rmq sentry units | ||
268 | 768 | :param port: optional ssl port override to validate | ||
269 | 769 | :returns: None if successful, otherwise return error message | ||
270 | 770 | """ | ||
271 | 771 | for sentry_unit in sentry_units: | ||
272 | 772 | if not self.rmq_ssl_is_enabled_on_unit(sentry_unit, port=port): | ||
273 | 773 | return ('Unexpected condition: ssl is disabled on unit ' | ||
274 | 774 | '({})'.format(sentry_unit.info['unit_name'])) | ||
275 | 775 | return None | ||
276 | 776 | |||
277 | 777 | def validate_rmq_ssl_disabled_units(self, sentry_units): | ||
278 | 778 | """Check that ssl is enabled on listed rmq juju sentry units. | ||
279 | 779 | |||
280 | 780 | :param sentry_units: list of all rmq sentry units | ||
281 | 781 | :returns: True if successful. Raise on error. | ||
282 | 782 | """ | ||
283 | 783 | for sentry_unit in sentry_units: | ||
284 | 784 | if self.rmq_ssl_is_enabled_on_unit(sentry_unit): | ||
285 | 785 | return ('Unexpected condition: ssl is enabled on unit ' | ||
286 | 786 | '({})'.format(sentry_unit.info['unit_name'])) | ||
287 | 787 | return None | ||
288 | 788 | |||
289 | 789 | def configure_rmq_ssl_on(self, sentry_units, deployment, | ||
290 | 790 | port=None, max_wait=60): | ||
291 | 791 | """Turn ssl charm config option on, with optional non-default | ||
292 | 792 | ssl port specification. Confirm that it is enabled on every | ||
293 | 793 | unit. | ||
294 | 794 | |||
295 | 795 | :param sentry_units: list of sentry units | ||
296 | 796 | :param deployment: amulet deployment object pointer | ||
297 | 797 | :param port: amqp port, use defaults if None | ||
298 | 798 | :param max_wait: maximum time to wait in seconds to confirm | ||
299 | 799 | :returns: None if successful. Raise on error. | ||
300 | 800 | """ | ||
301 | 801 | self.log.debug('Setting ssl charm config option: on') | ||
302 | 802 | |||
303 | 803 | # Enable RMQ SSL | ||
304 | 804 | config = {'ssl': 'on'} | ||
305 | 805 | if port: | ||
306 | 806 | config['ssl_port'] = port | ||
307 | 807 | |||
308 | 808 | deployment.configure('rabbitmq-server', config) | ||
309 | 809 | |||
310 | 810 | # Confirm | ||
311 | 811 | tries = 0 | ||
312 | 812 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
313 | 813 | while ret and tries < (max_wait / 4): | ||
314 | 814 | time.sleep(4) | ||
315 | 815 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
316 | 816 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
317 | 817 | tries += 1 | ||
318 | 818 | |||
319 | 819 | if ret: | ||
320 | 820 | amulet.raise_status(amulet.FAIL, ret) | ||
321 | 821 | |||
322 | 822 | def configure_rmq_ssl_off(self, sentry_units, deployment, max_wait=60): | ||
323 | 823 | """Turn ssl charm config option off, confirm that it is disabled | ||
324 | 824 | on every unit. | ||
325 | 825 | |||
326 | 826 | :param sentry_units: list of sentry units | ||
327 | 827 | :param deployment: amulet deployment object pointer | ||
328 | 828 | :param max_wait: maximum time to wait in seconds to confirm | ||
329 | 829 | :returns: None if successful. Raise on error. | ||
330 | 830 | """ | ||
331 | 831 | self.log.debug('Setting ssl charm config option: off') | ||
332 | 832 | |||
333 | 833 | # Disable RMQ SSL | ||
334 | 834 | config = {'ssl': 'off'} | ||
335 | 835 | deployment.configure('rabbitmq-server', config) | ||
336 | 836 | |||
337 | 837 | # Confirm | ||
338 | 838 | tries = 0 | ||
339 | 839 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
340 | 840 | while ret and tries < (max_wait / 4): | ||
341 | 841 | time.sleep(4) | ||
342 | 842 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
343 | 843 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
344 | 844 | tries += 1 | ||
345 | 845 | |||
346 | 846 | if ret: | ||
347 | 847 | amulet.raise_status(amulet.FAIL, ret) | ||
348 | 848 | |||
349 | 849 | def connect_amqp_by_unit(self, sentry_unit, ssl=False, | ||
350 | 850 | port=None, fatal=True, | ||
351 | 851 | username="testuser1", password="changeme"): | ||
352 | 852 | """Establish and return a pika amqp connection to the rabbitmq service | ||
353 | 853 | running on a rmq juju unit. | ||
354 | 854 | |||
355 | 855 | :param sentry_unit: sentry unit pointer | ||
356 | 856 | :param ssl: boolean, default to False | ||
357 | 857 | :param port: amqp port, use defaults if None | ||
358 | 858 | :param fatal: boolean, default to True (raises on connect error) | ||
359 | 859 | :param username: amqp user name, default to testuser1 | ||
360 | 860 | :param password: amqp user password | ||
361 | 861 | :returns: pika amqp connection pointer or None if failed and non-fatal | ||
362 | 862 | """ | ||
363 | 863 | host = sentry_unit.info['public-address'] | ||
364 | 864 | unit_name = sentry_unit.info['unit_name'] | ||
365 | 865 | |||
366 | 866 | # Default port logic if port is not specified | ||
367 | 867 | if ssl and not port: | ||
368 | 868 | port = 5671 | ||
369 | 869 | elif not ssl and not port: | ||
370 | 870 | port = 5672 | ||
371 | 871 | |||
372 | 872 | self.log.debug('Connecting to amqp on {}:{} ({}) as ' | ||
373 | 873 | '{}...'.format(host, port, unit_name, username)) | ||
374 | 874 | |||
375 | 875 | try: | ||
376 | 876 | credentials = pika.PlainCredentials(username, password) | ||
377 | 877 | parameters = pika.ConnectionParameters(host=host, port=port, | ||
378 | 878 | credentials=credentials, | ||
379 | 879 | ssl=ssl, | ||
380 | 880 | connection_attempts=3, | ||
381 | 881 | retry_delay=5, | ||
382 | 882 | socket_timeout=1) | ||
383 | 883 | connection = pika.BlockingConnection(parameters) | ||
384 | 884 | assert connection.server_properties['product'] == 'RabbitMQ' | ||
385 | 885 | self.log.debug('Connect OK') | ||
386 | 886 | return connection | ||
387 | 887 | except Exception as e: | ||
388 | 888 | msg = ('amqp connection failed to {}:{} as ' | ||
389 | 889 | '{} ({})'.format(host, port, username, str(e))) | ||
390 | 890 | if fatal: | ||
391 | 891 | amulet.raise_status(amulet.FAIL, msg) | ||
392 | 892 | else: | ||
393 | 893 | self.log.warn(msg) | ||
394 | 894 | return None | ||
395 | 895 | |||
396 | 896 | def publish_amqp_message_by_unit(self, sentry_unit, message, | ||
397 | 897 | queue="test", ssl=False, | ||
398 | 898 | username="testuser1", | ||
399 | 899 | password="changeme", | ||
400 | 900 | port=None): | ||
401 | 901 | """Publish an amqp message to a rmq juju unit. | ||
402 | 902 | |||
403 | 903 | :param sentry_unit: sentry unit pointer | ||
404 | 904 | :param message: amqp message string | ||
405 | 905 | :param queue: message queue, default to test | ||
406 | 906 | :param username: amqp user name, default to testuser1 | ||
407 | 907 | :param password: amqp user password | ||
408 | 908 | :param ssl: boolean, default to False | ||
409 | 909 | :param port: amqp port, use defaults if None | ||
410 | 910 | :returns: None. Raises exception if publish failed. | ||
411 | 911 | """ | ||
412 | 912 | self.log.debug('Publishing message to {} queue:\n{}'.format(queue, | ||
413 | 913 | message)) | ||
414 | 914 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
415 | 915 | port=port, | ||
416 | 916 | username=username, | ||
417 | 917 | password=password) | ||
418 | 918 | |||
419 | 919 | # NOTE(beisner): extra debug here re: pika hang potential: | ||
420 | 920 | # https://github.com/pika/pika/issues/297 | ||
421 | 921 | # https://groups.google.com/forum/#!topic/rabbitmq-users/Ja0iyfF0Szw | ||
422 | 922 | self.log.debug('Defining channel...') | ||
423 | 923 | channel = connection.channel() | ||
424 | 924 | self.log.debug('Declaring queue...') | ||
425 | 925 | channel.queue_declare(queue=queue, auto_delete=False, durable=True) | ||
426 | 926 | self.log.debug('Publishing message...') | ||
427 | 927 | channel.basic_publish(exchange='', routing_key=queue, body=message) | ||
428 | 928 | self.log.debug('Closing channel...') | ||
429 | 929 | channel.close() | ||
430 | 930 | self.log.debug('Closing connection...') | ||
431 | 931 | connection.close() | ||
432 | 932 | |||
433 | 933 | def get_amqp_message_by_unit(self, sentry_unit, queue="test", | ||
434 | 934 | username="testuser1", | ||
435 | 935 | password="changeme", | ||
436 | 936 | ssl=False, port=None): | ||
437 | 937 | """Get an amqp message from a rmq juju unit. | ||
438 | 938 | |||
439 | 939 | :param sentry_unit: sentry unit pointer | ||
440 | 940 | :param queue: message queue, default to test | ||
441 | 941 | :param username: amqp user name, default to testuser1 | ||
442 | 942 | :param password: amqp user password | ||
443 | 943 | :param ssl: boolean, default to False | ||
444 | 944 | :param port: amqp port, use defaults if None | ||
445 | 945 | :returns: amqp message body as string. Raise if get fails. | ||
446 | 946 | """ | ||
447 | 947 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
448 | 948 | port=port, | ||
449 | 949 | username=username, | ||
450 | 950 | password=password) | ||
451 | 951 | channel = connection.channel() | ||
452 | 952 | method_frame, _, body = channel.basic_get(queue) | ||
453 | 953 | |||
454 | 954 | if method_frame: | ||
455 | 955 | self.log.debug('Retreived message from {} queue:\n{}'.format(queue, | ||
456 | 956 | body)) | ||
457 | 957 | channel.basic_ack(method_frame.delivery_tag) | ||
458 | 958 | channel.close() | ||
459 | 959 | connection.close() | ||
460 | 960 | return body | ||
461 | 961 | else: | ||
462 | 962 | msg = 'No message retrieved.' | ||
463 | 963 | amulet.raise_status(amulet.FAIL, msg) | ||
464 | 605 | 964 | ||
465 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' | |||
466 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-09-03 09:39:34 +0000 | |||
467 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-09-22 06:12:36 +0000 | |||
468 | @@ -194,10 +194,50 @@ | |||
469 | 194 | class OSContextGenerator(object): | 194 | class OSContextGenerator(object): |
470 | 195 | """Base class for all context generators.""" | 195 | """Base class for all context generators.""" |
471 | 196 | interfaces = [] | 196 | interfaces = [] |
472 | 197 | related = False | ||
473 | 198 | complete = False | ||
474 | 199 | missing_data = [] | ||
475 | 197 | 200 | ||
476 | 198 | def __call__(self): | 201 | def __call__(self): |
477 | 199 | raise NotImplementedError | 202 | raise NotImplementedError |
478 | 200 | 203 | ||
479 | 204 | def context_complete(self, ctxt): | ||
480 | 205 | """Check for missing data for the required context data. | ||
481 | 206 | Set self.missing_data if it exists and return False. | ||
482 | 207 | Set self.complete if no missing data and return True. | ||
483 | 208 | """ | ||
484 | 209 | # Fresh start | ||
485 | 210 | self.complete = False | ||
486 | 211 | self.missing_data = [] | ||
487 | 212 | for k, v in six.iteritems(ctxt): | ||
488 | 213 | if v is None or v == '': | ||
489 | 214 | if k not in self.missing_data: | ||
490 | 215 | self.missing_data.append(k) | ||
491 | 216 | |||
492 | 217 | if self.missing_data: | ||
493 | 218 | self.complete = False | ||
494 | 219 | log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO) | ||
495 | 220 | else: | ||
496 | 221 | self.complete = True | ||
497 | 222 | return self.complete | ||
498 | 223 | |||
499 | 224 | def get_related(self): | ||
500 | 225 | """Check if any of the context interfaces have relation ids. | ||
501 | 226 | Set self.related and return True if one of the interfaces | ||
502 | 227 | has relation ids. | ||
503 | 228 | """ | ||
504 | 229 | # Fresh start | ||
505 | 230 | self.related = False | ||
506 | 231 | try: | ||
507 | 232 | for interface in self.interfaces: | ||
508 | 233 | if relation_ids(interface): | ||
509 | 234 | self.related = True | ||
510 | 235 | return self.related | ||
511 | 236 | except AttributeError as e: | ||
512 | 237 | log("{} {}" | ||
513 | 238 | "".format(self, e), 'INFO') | ||
514 | 239 | return self.related | ||
515 | 240 | |||
516 | 201 | 241 | ||
517 | 202 | class SharedDBContext(OSContextGenerator): | 242 | class SharedDBContext(OSContextGenerator): |
518 | 203 | interfaces = ['shared-db'] | 243 | interfaces = ['shared-db'] |
519 | @@ -213,6 +253,7 @@ | |||
520 | 213 | self.database = database | 253 | self.database = database |
521 | 214 | self.user = user | 254 | self.user = user |
522 | 215 | self.ssl_dir = ssl_dir | 255 | self.ssl_dir = ssl_dir |
523 | 256 | self.rel_name = self.interfaces[0] | ||
524 | 216 | 257 | ||
525 | 217 | def __call__(self): | 258 | def __call__(self): |
526 | 218 | self.database = self.database or config('database') | 259 | self.database = self.database or config('database') |
527 | @@ -246,6 +287,7 @@ | |||
528 | 246 | password_setting = self.relation_prefix + '_password' | 287 | password_setting = self.relation_prefix + '_password' |
529 | 247 | 288 | ||
530 | 248 | for rid in relation_ids(self.interfaces[0]): | 289 | for rid in relation_ids(self.interfaces[0]): |
531 | 290 | self.related = True | ||
532 | 249 | for unit in related_units(rid): | 291 | for unit in related_units(rid): |
533 | 250 | rdata = relation_get(rid=rid, unit=unit) | 292 | rdata = relation_get(rid=rid, unit=unit) |
534 | 251 | host = rdata.get('db_host') | 293 | host = rdata.get('db_host') |
535 | @@ -257,7 +299,7 @@ | |||
536 | 257 | 'database_password': rdata.get(password_setting), | 299 | 'database_password': rdata.get(password_setting), |
537 | 258 | 'database_type': 'mysql' | 300 | 'database_type': 'mysql' |
538 | 259 | } | 301 | } |
540 | 260 | if context_complete(ctxt): | 302 | if self.context_complete(ctxt): |
541 | 261 | db_ssl(rdata, ctxt, self.ssl_dir) | 303 | db_ssl(rdata, ctxt, self.ssl_dir) |
542 | 262 | return ctxt | 304 | return ctxt |
543 | 263 | return {} | 305 | return {} |
544 | @@ -278,6 +320,7 @@ | |||
545 | 278 | 320 | ||
546 | 279 | ctxt = {} | 321 | ctxt = {} |
547 | 280 | for rid in relation_ids(self.interfaces[0]): | 322 | for rid in relation_ids(self.interfaces[0]): |
548 | 323 | self.related = True | ||
549 | 281 | for unit in related_units(rid): | 324 | for unit in related_units(rid): |
550 | 282 | rel_host = relation_get('host', rid=rid, unit=unit) | 325 | rel_host = relation_get('host', rid=rid, unit=unit) |
551 | 283 | rel_user = relation_get('user', rid=rid, unit=unit) | 326 | rel_user = relation_get('user', rid=rid, unit=unit) |
552 | @@ -287,7 +330,7 @@ | |||
553 | 287 | 'database_user': rel_user, | 330 | 'database_user': rel_user, |
554 | 288 | 'database_password': rel_passwd, | 331 | 'database_password': rel_passwd, |
555 | 289 | 'database_type': 'postgresql'} | 332 | 'database_type': 'postgresql'} |
557 | 290 | if context_complete(ctxt): | 333 | if self.context_complete(ctxt): |
558 | 291 | return ctxt | 334 | return ctxt |
559 | 292 | 335 | ||
560 | 293 | return {} | 336 | return {} |
561 | @@ -348,6 +391,7 @@ | |||
562 | 348 | ctxt['signing_dir'] = cachedir | 391 | ctxt['signing_dir'] = cachedir |
563 | 349 | 392 | ||
564 | 350 | for rid in relation_ids(self.rel_name): | 393 | for rid in relation_ids(self.rel_name): |
565 | 394 | self.related = True | ||
566 | 351 | for unit in related_units(rid): | 395 | for unit in related_units(rid): |
567 | 352 | rdata = relation_get(rid=rid, unit=unit) | 396 | rdata = relation_get(rid=rid, unit=unit) |
568 | 353 | serv_host = rdata.get('service_host') | 397 | serv_host = rdata.get('service_host') |
569 | @@ -366,7 +410,7 @@ | |||
570 | 366 | 'service_protocol': svc_protocol, | 410 | 'service_protocol': svc_protocol, |
571 | 367 | 'auth_protocol': auth_protocol}) | 411 | 'auth_protocol': auth_protocol}) |
572 | 368 | 412 | ||
574 | 369 | if context_complete(ctxt): | 413 | if self.context_complete(ctxt): |
575 | 370 | # NOTE(jamespage) this is required for >= icehouse | 414 | # NOTE(jamespage) this is required for >= icehouse |
576 | 371 | # so a missing value just indicates keystone needs | 415 | # so a missing value just indicates keystone needs |
577 | 372 | # upgrading | 416 | # upgrading |
578 | @@ -405,6 +449,7 @@ | |||
579 | 405 | ctxt = {} | 449 | ctxt = {} |
580 | 406 | for rid in relation_ids(self.rel_name): | 450 | for rid in relation_ids(self.rel_name): |
581 | 407 | ha_vip_only = False | 451 | ha_vip_only = False |
582 | 452 | self.related = True | ||
583 | 408 | for unit in related_units(rid): | 453 | for unit in related_units(rid): |
584 | 409 | if relation_get('clustered', rid=rid, unit=unit): | 454 | if relation_get('clustered', rid=rid, unit=unit): |
585 | 410 | ctxt['clustered'] = True | 455 | ctxt['clustered'] = True |
586 | @@ -437,7 +482,7 @@ | |||
587 | 437 | ha_vip_only = relation_get('ha-vip-only', | 482 | ha_vip_only = relation_get('ha-vip-only', |
588 | 438 | rid=rid, unit=unit) is not None | 483 | rid=rid, unit=unit) is not None |
589 | 439 | 484 | ||
591 | 440 | if context_complete(ctxt): | 485 | if self.context_complete(ctxt): |
592 | 441 | if 'rabbit_ssl_ca' in ctxt: | 486 | if 'rabbit_ssl_ca' in ctxt: |
593 | 442 | if not self.ssl_dir: | 487 | if not self.ssl_dir: |
594 | 443 | log("Charm not setup for ssl support but ssl ca " | 488 | log("Charm not setup for ssl support but ssl ca " |
595 | @@ -469,7 +514,7 @@ | |||
596 | 469 | ctxt['oslo_messaging_flags'] = config_flags_parser( | 514 | ctxt['oslo_messaging_flags'] = config_flags_parser( |
597 | 470 | oslo_messaging_flags) | 515 | oslo_messaging_flags) |
598 | 471 | 516 | ||
600 | 472 | if not context_complete(ctxt): | 517 | if not self.complete: |
601 | 473 | return {} | 518 | return {} |
602 | 474 | 519 | ||
603 | 475 | return ctxt | 520 | return ctxt |
604 | @@ -485,13 +530,15 @@ | |||
605 | 485 | 530 | ||
606 | 486 | log('Generating template context for ceph', level=DEBUG) | 531 | log('Generating template context for ceph', level=DEBUG) |
607 | 487 | mon_hosts = [] | 532 | mon_hosts = [] |
611 | 488 | auth = None | 533 | ctxt = { |
612 | 489 | key = None | 534 | 'use_syslog': str(config('use-syslog')).lower() |
613 | 490 | use_syslog = str(config('use-syslog')).lower() | 535 | } |
614 | 491 | for rid in relation_ids('ceph'): | 536 | for rid in relation_ids('ceph'): |
615 | 492 | for unit in related_units(rid): | 537 | for unit in related_units(rid): |
618 | 493 | auth = relation_get('auth', rid=rid, unit=unit) | 538 | if not ctxt.get('auth'): |
619 | 494 | key = relation_get('key', rid=rid, unit=unit) | 539 | ctxt['auth'] = relation_get('auth', rid=rid, unit=unit) |
620 | 540 | if not ctxt.get('key'): | ||
621 | 541 | ctxt['key'] = relation_get('key', rid=rid, unit=unit) | ||
622 | 495 | ceph_pub_addr = relation_get('ceph-public-address', rid=rid, | 542 | ceph_pub_addr = relation_get('ceph-public-address', rid=rid, |
623 | 496 | unit=unit) | 543 | unit=unit) |
624 | 497 | unit_priv_addr = relation_get('private-address', rid=rid, | 544 | unit_priv_addr = relation_get('private-address', rid=rid, |
625 | @@ -500,15 +547,12 @@ | |||
626 | 500 | ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr | 547 | ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr |
627 | 501 | mon_hosts.append(ceph_addr) | 548 | mon_hosts.append(ceph_addr) |
628 | 502 | 549 | ||
633 | 503 | ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)), | 550 | ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts)) |
630 | 504 | 'auth': auth, | ||
631 | 505 | 'key': key, | ||
632 | 506 | 'use_syslog': use_syslog} | ||
634 | 507 | 551 | ||
635 | 508 | if not os.path.isdir('/etc/ceph'): | 552 | if not os.path.isdir('/etc/ceph'): |
636 | 509 | os.mkdir('/etc/ceph') | 553 | os.mkdir('/etc/ceph') |
637 | 510 | 554 | ||
639 | 511 | if not context_complete(ctxt): | 555 | if not self.context_complete(ctxt): |
640 | 512 | return {} | 556 | return {} |
641 | 513 | 557 | ||
642 | 514 | ensure_packages(['ceph-common']) | 558 | ensure_packages(['ceph-common']) |
643 | @@ -1367,6 +1411,6 @@ | |||
644 | 1367 | 'auth_protocol': | 1411 | 'auth_protocol': |
645 | 1368 | rdata.get('auth_protocol') or 'http', | 1412 | rdata.get('auth_protocol') or 'http', |
646 | 1369 | } | 1413 | } |
648 | 1370 | if context_complete(ctxt): | 1414 | if self.context_complete(ctxt): |
649 | 1371 | return ctxt | 1415 | return ctxt |
650 | 1372 | return {} | 1416 | return {} |
651 | 1373 | 1417 | ||
652 | === modified file 'hooks/charmhelpers/contrib/openstack/templating.py' | |||
653 | --- hooks/charmhelpers/contrib/openstack/templating.py 2015-07-29 10:46:43 +0000 | |||
654 | +++ hooks/charmhelpers/contrib/openstack/templating.py 2015-09-22 06:12:36 +0000 | |||
655 | @@ -18,7 +18,7 @@ | |||
656 | 18 | 18 | ||
657 | 19 | import six | 19 | import six |
658 | 20 | 20 | ||
660 | 21 | from charmhelpers.fetch import apt_install | 21 | from charmhelpers.fetch import apt_install, apt_update |
661 | 22 | from charmhelpers.core.hookenv import ( | 22 | from charmhelpers.core.hookenv import ( |
662 | 23 | log, | 23 | log, |
663 | 24 | ERROR, | 24 | ERROR, |
664 | @@ -112,7 +112,7 @@ | |||
665 | 112 | 112 | ||
666 | 113 | def complete_contexts(self): | 113 | def complete_contexts(self): |
667 | 114 | ''' | 114 | ''' |
669 | 115 | Return a list of interfaces that have atisfied contexts. | 115 | Return a list of interfaces that have satisfied contexts. |
670 | 116 | ''' | 116 | ''' |
671 | 117 | if self._complete_contexts: | 117 | if self._complete_contexts: |
672 | 118 | return self._complete_contexts | 118 | return self._complete_contexts |
673 | @@ -293,3 +293,30 @@ | |||
674 | 293 | [interfaces.extend(i.complete_contexts()) | 293 | [interfaces.extend(i.complete_contexts()) |
675 | 294 | for i in six.itervalues(self.templates)] | 294 | for i in six.itervalues(self.templates)] |
676 | 295 | return interfaces | 295 | return interfaces |
677 | 296 | |||
678 | 297 | def get_incomplete_context_data(self, interfaces): | ||
679 | 298 | ''' | ||
680 | 299 | Return dictionary of relation status of interfaces and any missing | ||
681 | 300 | required context data. Example: | ||
682 | 301 | {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True}, | ||
683 | 302 | 'zeromq-configuration': {'related': False}} | ||
684 | 303 | ''' | ||
685 | 304 | incomplete_context_data = {} | ||
686 | 305 | |||
687 | 306 | for i in six.itervalues(self.templates): | ||
688 | 307 | for context in i.contexts: | ||
689 | 308 | for interface in interfaces: | ||
690 | 309 | related = False | ||
691 | 310 | if interface in context.interfaces: | ||
692 | 311 | related = context.get_related() | ||
693 | 312 | missing_data = context.missing_data | ||
694 | 313 | if missing_data: | ||
695 | 314 | incomplete_context_data[interface] = {'missing_data': missing_data} | ||
696 | 315 | if related: | ||
697 | 316 | if incomplete_context_data.get(interface): | ||
698 | 317 | incomplete_context_data[interface].update({'related': True}) | ||
699 | 318 | else: | ||
700 | 319 | incomplete_context_data[interface] = {'related': True} | ||
701 | 320 | else: | ||
702 | 321 | incomplete_context_data[interface] = {'related': False} | ||
703 | 322 | return incomplete_context_data | ||
704 | 296 | 323 | ||
705 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' | |||
706 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-09-03 09:39:34 +0000 | |||
707 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2015-09-22 06:12:36 +0000 | |||
708 | @@ -25,6 +25,7 @@ | |||
709 | 25 | import re | 25 | import re |
710 | 26 | 26 | ||
711 | 27 | import six | 27 | import six |
712 | 28 | import traceback | ||
713 | 28 | import yaml | 29 | import yaml |
714 | 29 | 30 | ||
715 | 30 | from charmhelpers.contrib.network import ip | 31 | from charmhelpers.contrib.network import ip |
716 | @@ -34,12 +35,16 @@ | |||
717 | 34 | ) | 35 | ) |
718 | 35 | 36 | ||
719 | 36 | from charmhelpers.core.hookenv import ( | 37 | from charmhelpers.core.hookenv import ( |
720 | 38 | action_fail, | ||
721 | 39 | action_set, | ||
722 | 37 | config, | 40 | config, |
723 | 38 | log as juju_log, | 41 | log as juju_log, |
724 | 39 | charm_dir, | 42 | charm_dir, |
725 | 40 | INFO, | 43 | INFO, |
726 | 41 | relation_ids, | 44 | relation_ids, |
728 | 42 | relation_set | 45 | relation_set, |
729 | 46 | status_set, | ||
730 | 47 | hook_name | ||
731 | 43 | ) | 48 | ) |
732 | 44 | 49 | ||
733 | 45 | from charmhelpers.contrib.storage.linux.lvm import ( | 50 | from charmhelpers.contrib.storage.linux.lvm import ( |
734 | @@ -114,6 +119,7 @@ | |||
735 | 114 | ('2.2.1', 'kilo'), | 119 | ('2.2.1', 'kilo'), |
736 | 115 | ('2.2.2', 'kilo'), | 120 | ('2.2.2', 'kilo'), |
737 | 116 | ('2.3.0', 'liberty'), | 121 | ('2.3.0', 'liberty'), |
738 | 122 | ('2.4.0', 'liberty'), | ||
739 | 117 | ]) | 123 | ]) |
740 | 118 | 124 | ||
741 | 119 | # >= Liberty version->codename mapping | 125 | # >= Liberty version->codename mapping |
742 | @@ -745,3 +751,217 @@ | |||
743 | 745 | return projects[key] | 751 | return projects[key] |
744 | 746 | 752 | ||
745 | 747 | return None | 753 | return None |
746 | 754 | |||
747 | 755 | |||
748 | 756 | def os_workload_status(configs, required_interfaces, charm_func=None): | ||
749 | 757 | """ | ||
750 | 758 | Decorator to set workload status based on complete contexts | ||
751 | 759 | """ | ||
752 | 760 | def wrap(f): | ||
753 | 761 | @wraps(f) | ||
754 | 762 | def wrapped_f(*args, **kwargs): | ||
755 | 763 | # Run the original function first | ||
756 | 764 | f(*args, **kwargs) | ||
757 | 765 | # Set workload status now that contexts have been | ||
758 | 766 | # acted on | ||
759 | 767 | set_os_workload_status(configs, required_interfaces, charm_func) | ||
760 | 768 | return wrapped_f | ||
761 | 769 | return wrap | ||
762 | 770 | |||
763 | 771 | |||
764 | 772 | def set_os_workload_status(configs, required_interfaces, charm_func=None): | ||
765 | 773 | """ | ||
766 | 774 | Set workload status based on complete contexts. | ||
767 | 775 | status-set missing or incomplete contexts | ||
768 | 776 | and juju-log details of missing required data. | ||
769 | 777 | charm_func is a charm specific function to run checking | ||
770 | 778 | for charm specific requirements such as a VIP setting. | ||
771 | 779 | """ | ||
772 | 780 | incomplete_rel_data = incomplete_relation_data(configs, required_interfaces) | ||
773 | 781 | state = 'active' | ||
774 | 782 | missing_relations = [] | ||
775 | 783 | incomplete_relations = [] | ||
776 | 784 | message = None | ||
777 | 785 | charm_state = None | ||
778 | 786 | charm_message = None | ||
779 | 787 | |||
780 | 788 | for generic_interface in incomplete_rel_data.keys(): | ||
781 | 789 | related_interface = None | ||
782 | 790 | missing_data = {} | ||
783 | 791 | # Related or not? | ||
784 | 792 | for interface in incomplete_rel_data[generic_interface]: | ||
785 | 793 | if incomplete_rel_data[generic_interface][interface].get('related'): | ||
786 | 794 | related_interface = interface | ||
787 | 795 | missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data') | ||
788 | 796 | # No relation ID for the generic_interface | ||
789 | 797 | if not related_interface: | ||
790 | 798 | juju_log("{} relation is missing and must be related for " | ||
791 | 799 | "functionality. ".format(generic_interface), 'WARN') | ||
792 | 800 | state = 'blocked' | ||
793 | 801 | if generic_interface not in missing_relations: | ||
794 | 802 | missing_relations.append(generic_interface) | ||
795 | 803 | else: | ||
796 | 804 | # Relation ID exists but no related unit | ||
797 | 805 | if not missing_data: | ||
798 | 806 | # Edge case relation ID exists but departing | ||
799 | 807 | if ('departed' in hook_name() or 'broken' in hook_name()) \ | ||
800 | 808 | and related_interface in hook_name(): | ||
801 | 809 | state = 'blocked' | ||
802 | 810 | if generic_interface not in missing_relations: | ||
803 | 811 | missing_relations.append(generic_interface) | ||
804 | 812 | juju_log("{} relation's interface, {}, " | ||
805 | 813 | "relationship is departed or broken " | ||
806 | 814 | "and is required for functionality." | ||
807 | 815 | "".format(generic_interface, related_interface), "WARN") | ||
808 | 816 | # Normal case relation ID exists but no related unit | ||
809 | 817 | # (joining) | ||
810 | 818 | else: | ||
811 | 819 | juju_log("{} relations's interface, {}, is related but has " | ||
812 | 820 | "no units in the relation." | ||
813 | 821 | "".format(generic_interface, related_interface), "INFO") | ||
814 | 822 | # Related unit exists and data missing on the relation | ||
815 | 823 | else: | ||
816 | 824 | juju_log("{} relation's interface, {}, is related awaiting " | ||
817 | 825 | "the following data from the relationship: {}. " | ||
818 | 826 | "".format(generic_interface, related_interface, | ||
819 | 827 | ", ".join(missing_data)), "INFO") | ||
820 | 828 | if state != 'blocked': | ||
821 | 829 | state = 'waiting' | ||
822 | 830 | if generic_interface not in incomplete_relations \ | ||
823 | 831 | and generic_interface not in missing_relations: | ||
824 | 832 | incomplete_relations.append(generic_interface) | ||
825 | 833 | |||
826 | 834 | if missing_relations: | ||
827 | 835 | message = "Missing relations: {}".format(", ".join(missing_relations)) | ||
828 | 836 | if incomplete_relations: | ||
829 | 837 | message += "; incomplete relations: {}" \ | ||
830 | 838 | "".format(", ".join(incomplete_relations)) | ||
831 | 839 | state = 'blocked' | ||
832 | 840 | elif incomplete_relations: | ||
833 | 841 | message = "Incomplete relations: {}" \ | ||
834 | 842 | "".format(", ".join(incomplete_relations)) | ||
835 | 843 | state = 'waiting' | ||
836 | 844 | |||
837 | 845 | # Run charm specific checks | ||
838 | 846 | if charm_func: | ||
839 | 847 | charm_state, charm_message = charm_func(configs) | ||
840 | 848 | if charm_state != 'active' and charm_state != 'unknown': | ||
841 | 849 | state = workload_state_compare(state, charm_state) | ||
842 | 850 | if message: | ||
843 | 851 | message = "{} {}".format(message, charm_message) | ||
844 | 852 | else: | ||
845 | 853 | message = charm_message | ||
846 | 854 | |||
847 | 855 | # Set to active if all requirements have been met | ||
848 | 856 | if state == 'active': | ||
849 | 857 | message = "Unit is ready" | ||
850 | 858 | juju_log(message, "INFO") | ||
851 | 859 | |||
852 | 860 | status_set(state, message) | ||
853 | 861 | |||
854 | 862 | |||
855 | 863 | def workload_state_compare(current_workload_state, workload_state): | ||
856 | 864 | """ Return highest priority of two states""" | ||
857 | 865 | hierarchy = {'unknown': -1, | ||
858 | 866 | 'active': 0, | ||
859 | 867 | 'maintenance': 1, | ||
860 | 868 | 'waiting': 2, | ||
861 | 869 | 'blocked': 3, | ||
862 | 870 | } | ||
863 | 871 | |||
864 | 872 | if hierarchy.get(workload_state) is None: | ||
865 | 873 | workload_state = 'unknown' | ||
866 | 874 | if hierarchy.get(current_workload_state) is None: | ||
867 | 875 | current_workload_state = 'unknown' | ||
868 | 876 | |||
869 | 877 | # Set workload_state based on hierarchy of statuses | ||
870 | 878 | if hierarchy.get(current_workload_state) > hierarchy.get(workload_state): | ||
871 | 879 | return current_workload_state | ||
872 | 880 | else: | ||
873 | 881 | return workload_state | ||
874 | 882 | |||
875 | 883 | |||
876 | 884 | def incomplete_relation_data(configs, required_interfaces): | ||
877 | 885 | """ | ||
878 | 886 | Check complete contexts against required_interfaces | ||
879 | 887 | Return dictionary of incomplete relation data. | ||
880 | 888 | |||
881 | 889 | configs is an OSConfigRenderer object with configs registered | ||
882 | 890 | |||
883 | 891 | required_interfaces is a dictionary of required general interfaces | ||
884 | 892 | with dictionary values of possible specific interfaces. | ||
885 | 893 | Example: | ||
886 | 894 | required_interfaces = {'database': ['shared-db', 'pgsql-db']} | ||
887 | 895 | |||
888 | 896 | The interface is said to be satisfied if anyone of the interfaces in the | ||
889 | 897 | list has a complete context. | ||
890 | 898 | |||
891 | 899 | Return dictionary of incomplete or missing required contexts with relation | ||
892 | 900 | status of interfaces and any missing data points. Example: | ||
893 | 901 | {'message': | ||
894 | 902 | {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True}, | ||
895 | 903 | 'zeromq-configuration': {'related': False}}, | ||
896 | 904 | 'identity': | ||
897 | 905 | {'identity-service': {'related': False}}, | ||
898 | 906 | 'database': | ||
899 | 907 | {'pgsql-db': {'related': False}, | ||
900 | 908 | 'shared-db': {'related': True}}} | ||
901 | 909 | """ | ||
902 | 910 | complete_ctxts = configs.complete_contexts() | ||
903 | 911 | incomplete_relations = [] | ||
904 | 912 | for svc_type in required_interfaces.keys(): | ||
905 | 913 | # Avoid duplicates | ||
906 | 914 | found_ctxt = False | ||
907 | 915 | for interface in required_interfaces[svc_type]: | ||
908 | 916 | if interface in complete_ctxts: | ||
909 | 917 | found_ctxt = True | ||
910 | 918 | if not found_ctxt: | ||
911 | 919 | incomplete_relations.append(svc_type) | ||
912 | 920 | incomplete_context_data = {} | ||
913 | 921 | for i in incomplete_relations: | ||
914 | 922 | incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i]) | ||
915 | 923 | return incomplete_context_data | ||
916 | 924 | |||
917 | 925 | |||
918 | 926 | def do_action_openstack_upgrade(package, upgrade_callback, configs): | ||
919 | 927 | """Perform action-managed OpenStack upgrade. | ||
920 | 928 | |||
921 | 929 | Upgrades packages to the configured openstack-origin version and sets | ||
922 | 930 | the corresponding action status as a result. | ||
923 | 931 | |||
924 | 932 | If the charm was installed from source we cannot upgrade it. | ||
925 | 933 | For backwards compatibility a config flag (action-managed-upgrade) must | ||
926 | 934 | be set for this code to run, otherwise a full service level upgrade will | ||
927 | 935 | fire on config-changed. | ||
928 | 936 | |||
929 | 937 | @param package: package name for determining if upgrade available | ||
930 | 938 | @param upgrade_callback: function callback to charm's upgrade function | ||
931 | 939 | @param configs: templating object derived from OSConfigRenderer class | ||
932 | 940 | |||
933 | 941 | @return: True if upgrade successful; False if upgrade failed or skipped | ||
934 | 942 | """ | ||
935 | 943 | ret = False | ||
936 | 944 | |||
937 | 945 | if git_install_requested(): | ||
938 | 946 | action_set({'outcome': 'installed from source, skipped upgrade.'}) | ||
939 | 947 | else: | ||
940 | 948 | if openstack_upgrade_available(package): | ||
941 | 949 | if config('action-managed-upgrade'): | ||
942 | 950 | juju_log('Upgrading OpenStack release') | ||
943 | 951 | |||
944 | 952 | try: | ||
945 | 953 | upgrade_callback(configs=configs) | ||
946 | 954 | action_set({'outcome': 'success, upgrade completed.'}) | ||
947 | 955 | ret = True | ||
948 | 956 | except: | ||
949 | 957 | action_set({'outcome': 'upgrade failed, see traceback.'}) | ||
950 | 958 | action_set({'traceback': traceback.format_exc()}) | ||
951 | 959 | action_fail('do_openstack_upgrade resulted in an ' | ||
952 | 960 | 'unexpected error') | ||
953 | 961 | else: | ||
954 | 962 | action_set({'outcome': 'action-managed-upgrade config is ' | ||
955 | 963 | 'False, skipped upgrade.'}) | ||
956 | 964 | else: | ||
957 | 965 | action_set({'outcome': 'no upgrade available.'}) | ||
958 | 966 | |||
959 | 967 | return ret | ||
960 | 748 | 968 | ||
961 | === modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py' | |||
962 | --- hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-07-16 20:18:38 +0000 | |||
963 | +++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-09-22 06:12:36 +0000 | |||
964 | @@ -28,6 +28,7 @@ | |||
965 | 28 | import shutil | 28 | import shutil |
966 | 29 | import json | 29 | import json |
967 | 30 | import time | 30 | import time |
968 | 31 | import uuid | ||
969 | 31 | 32 | ||
970 | 32 | from subprocess import ( | 33 | from subprocess import ( |
971 | 33 | check_call, | 34 | check_call, |
972 | @@ -35,8 +36,10 @@ | |||
973 | 35 | CalledProcessError, | 36 | CalledProcessError, |
974 | 36 | ) | 37 | ) |
975 | 37 | from charmhelpers.core.hookenv import ( | 38 | from charmhelpers.core.hookenv import ( |
976 | 39 | local_unit, | ||
977 | 38 | relation_get, | 40 | relation_get, |
978 | 39 | relation_ids, | 41 | relation_ids, |
979 | 42 | relation_set, | ||
980 | 40 | related_units, | 43 | related_units, |
981 | 41 | log, | 44 | log, |
982 | 42 | DEBUG, | 45 | DEBUG, |
983 | @@ -56,6 +59,8 @@ | |||
984 | 56 | apt_install, | 59 | apt_install, |
985 | 57 | ) | 60 | ) |
986 | 58 | 61 | ||
987 | 62 | from charmhelpers.core.kernel import modprobe | ||
988 | 63 | |||
989 | 59 | KEYRING = '/etc/ceph/ceph.client.{}.keyring' | 64 | KEYRING = '/etc/ceph/ceph.client.{}.keyring' |
990 | 60 | KEYFILE = '/etc/ceph/ceph.client.{}.key' | 65 | KEYFILE = '/etc/ceph/ceph.client.{}.key' |
991 | 61 | 66 | ||
992 | @@ -288,17 +293,6 @@ | |||
993 | 288 | os.chown(data_src_dst, uid, gid) | 293 | os.chown(data_src_dst, uid, gid) |
994 | 289 | 294 | ||
995 | 290 | 295 | ||
996 | 291 | # TODO: re-use | ||
997 | 292 | def modprobe(module): | ||
998 | 293 | """Load a kernel module and configure for auto-load on reboot.""" | ||
999 | 294 | log('Loading kernel module', level=INFO) | ||
1000 | 295 | cmd = ['modprobe', module] | ||
1001 | 296 | check_call(cmd) | ||
1002 | 297 | with open('/etc/modules', 'r+') as modules: | ||
1003 | 298 | if module not in modules.read(): | ||
1004 | 299 | modules.write(module) | ||
1005 | 300 | |||
1006 | 301 | |||
1007 | 302 | def copy_files(src, dst, symlinks=False, ignore=None): | 296 | def copy_files(src, dst, symlinks=False, ignore=None): |
1008 | 303 | """Copy files from src to dst.""" | 297 | """Copy files from src to dst.""" |
1009 | 304 | for item in os.listdir(src): | 298 | for item in os.listdir(src): |
1010 | @@ -411,17 +405,52 @@ | |||
1011 | 411 | 405 | ||
1012 | 412 | The API is versioned and defaults to version 1. | 406 | The API is versioned and defaults to version 1. |
1013 | 413 | """ | 407 | """ |
1015 | 414 | def __init__(self, api_version=1): | 408 | def __init__(self, api_version=1, request_id=None): |
1016 | 415 | self.api_version = api_version | 409 | self.api_version = api_version |
1017 | 410 | if request_id: | ||
1018 | 411 | self.request_id = request_id | ||
1019 | 412 | else: | ||
1020 | 413 | self.request_id = str(uuid.uuid1()) | ||
1021 | 416 | self.ops = [] | 414 | self.ops = [] |
1022 | 417 | 415 | ||
1023 | 418 | def add_op_create_pool(self, name, replica_count=3): | 416 | def add_op_create_pool(self, name, replica_count=3): |
1024 | 419 | self.ops.append({'op': 'create-pool', 'name': name, | 417 | self.ops.append({'op': 'create-pool', 'name': name, |
1025 | 420 | 'replicas': replica_count}) | 418 | 'replicas': replica_count}) |
1026 | 421 | 419 | ||
1027 | 420 | def set_ops(self, ops): | ||
1028 | 421 | """Set request ops to provided value. | ||
1029 | 422 | |||
1030 | 423 | Useful for injecting ops that come from a previous request | ||
1031 | 424 | to allow comparisons to ensure validity. | ||
1032 | 425 | """ | ||
1033 | 426 | self.ops = ops | ||
1034 | 427 | |||
1035 | 422 | @property | 428 | @property |
1036 | 423 | def request(self): | 429 | def request(self): |
1038 | 424 | return json.dumps({'api-version': self.api_version, 'ops': self.ops}) | 430 | return json.dumps({'api-version': self.api_version, 'ops': self.ops, |
1039 | 431 | 'request-id': self.request_id}) | ||
1040 | 432 | |||
1041 | 433 | def _ops_equal(self, other): | ||
1042 | 434 | if len(self.ops) == len(other.ops): | ||
1043 | 435 | for req_no in range(0, len(self.ops)): | ||
1044 | 436 | for key in ['replicas', 'name', 'op']: | ||
1045 | 437 | if self.ops[req_no][key] != other.ops[req_no][key]: | ||
1046 | 438 | return False | ||
1047 | 439 | else: | ||
1048 | 440 | return False | ||
1049 | 441 | return True | ||
1050 | 442 | |||
1051 | 443 | def __eq__(self, other): | ||
1052 | 444 | if not isinstance(other, self.__class__): | ||
1053 | 445 | return False | ||
1054 | 446 | if self.api_version == other.api_version and \ | ||
1055 | 447 | self._ops_equal(other): | ||
1056 | 448 | return True | ||
1057 | 449 | else: | ||
1058 | 450 | return False | ||
1059 | 451 | |||
1060 | 452 | def __ne__(self, other): | ||
1061 | 453 | return not self.__eq__(other) | ||
1062 | 425 | 454 | ||
1063 | 426 | 455 | ||
1064 | 427 | class CephBrokerRsp(object): | 456 | class CephBrokerRsp(object): |
1065 | @@ -431,14 +460,198 @@ | |||
1066 | 431 | 460 | ||
1067 | 432 | The API is versioned and defaults to version 1. | 461 | The API is versioned and defaults to version 1. |
1068 | 433 | """ | 462 | """ |
1069 | 463 | |||
1070 | 434 | def __init__(self, encoded_rsp): | 464 | def __init__(self, encoded_rsp): |
1071 | 435 | self.api_version = None | 465 | self.api_version = None |
1072 | 436 | self.rsp = json.loads(encoded_rsp) | 466 | self.rsp = json.loads(encoded_rsp) |
1073 | 437 | 467 | ||
1074 | 438 | @property | 468 | @property |
1075 | 469 | def request_id(self): | ||
1076 | 470 | return self.rsp.get('request-id') | ||
1077 | 471 | |||
1078 | 472 | @property | ||
1079 | 439 | def exit_code(self): | 473 | def exit_code(self): |
1080 | 440 | return self.rsp.get('exit-code') | 474 | return self.rsp.get('exit-code') |
1081 | 441 | 475 | ||
1082 | 442 | @property | 476 | @property |
1083 | 443 | def exit_msg(self): | 477 | def exit_msg(self): |
1084 | 444 | return self.rsp.get('stderr') | 478 | return self.rsp.get('stderr') |
1085 | 479 | |||
1086 | 480 | |||
1087 | 481 | # Ceph Broker Conversation: | ||
1088 | 482 | # If a charm needs an action to be taken by ceph it can create a CephBrokerRq | ||
1089 | 483 | # and send that request to ceph via the ceph relation. The CephBrokerRq has a | ||
1090 | 484 | # unique id so that the client can identity which CephBrokerRsp is associated | ||
1091 | 485 | # with the request. Ceph will also respond to each client unit individually | ||
1092 | 486 | # creating a response key per client unit eg glance/0 will get a CephBrokerRsp | ||
1093 | 487 | # via key broker-rsp-glance-0 | ||
1094 | 488 | # | ||
1095 | 489 | # To use this the charm can just do something like: | ||
1096 | 490 | # | ||
1097 | 491 | # from charmhelpers.contrib.storage.linux.ceph import ( | ||
1098 | 492 | # send_request_if_needed, | ||
1099 | 493 | # is_request_complete, | ||
1100 | 494 | # CephBrokerRq, | ||
1101 | 495 | # ) | ||
1102 | 496 | # | ||
1103 | 497 | # @hooks.hook('ceph-relation-changed') | ||
1104 | 498 | # def ceph_changed(): | ||
1105 | 499 | # rq = CephBrokerRq() | ||
1106 | 500 | # rq.add_op_create_pool(name='poolname', replica_count=3) | ||
1107 | 501 | # | ||
1108 | 502 | # if is_request_complete(rq): | ||
1109 | 503 | # <Request complete actions> | ||
1110 | 504 | # else: | ||
1111 | 505 | # send_request_if_needed(get_ceph_request()) | ||
1112 | 506 | # | ||
1113 | 507 | # CephBrokerRq and CephBrokerRsp are serialized into JSON. Below is an example | ||
1114 | 508 | # of glance having sent a request to ceph which ceph has successfully processed | ||
1115 | 509 | # 'ceph:8': { | ||
1116 | 510 | # 'ceph/0': { | ||
1117 | 511 | # 'auth': 'cephx', | ||
1118 | 512 | # 'broker-rsp-glance-0': '{"request-id": "0bc7dc54", "exit-code": 0}', | ||
1119 | 513 | # 'broker_rsp': '{"request-id": "0da543b8", "exit-code": 0}', | ||
1120 | 514 | # 'ceph-public-address': '10.5.44.103', | ||
1121 | 515 | # 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==', | ||
1122 | 516 | # 'private-address': '10.5.44.103', | ||
1123 | 517 | # }, | ||
1124 | 518 | # 'glance/0': { | ||
1125 | 519 | # 'broker_req': ('{"api-version": 1, "request-id": "0bc7dc54", ' | ||
1126 | 520 | # '"ops": [{"replicas": 3, "name": "glance", ' | ||
1127 | 521 | # '"op": "create-pool"}]}'), | ||
1128 | 522 | # 'private-address': '10.5.44.109', | ||
1129 | 523 | # }, | ||
1130 | 524 | # } | ||
1131 | 525 | |||
1132 | 526 | def get_previous_request(rid): | ||
1133 | 527 | """Return the last ceph broker request sent on a given relation | ||
1134 | 528 | |||
1135 | 529 | @param rid: Relation id to query for request | ||
1136 | 530 | """ | ||
1137 | 531 | request = None | ||
1138 | 532 | broker_req = relation_get(attribute='broker_req', rid=rid, | ||
1139 | 533 | unit=local_unit()) | ||
1140 | 534 | if broker_req: | ||
1141 | 535 | request_data = json.loads(broker_req) | ||
1142 | 536 | request = CephBrokerRq(api_version=request_data['api-version'], | ||
1143 | 537 | request_id=request_data['request-id']) | ||
1144 | 538 | request.set_ops(request_data['ops']) | ||
1145 | 539 | |||
1146 | 540 | return request | ||
1147 | 541 | |||
1148 | 542 | |||
1149 | 543 | def get_request_states(request): | ||
1150 | 544 | """Return a dict of requests per relation id with their corresponding | ||
1151 | 545 | completion state. | ||
1152 | 546 | |||
1153 | 547 | This allows a charm, which has a request for ceph, to see whether there is | ||
1154 | 548 | an equivalent request already being processed and if so what state that | ||
1155 | 549 | request is in. | ||
1156 | 550 | |||
1157 | 551 | @param request: A CephBrokerRq object | ||
1158 | 552 | """ | ||
1159 | 553 | complete = [] | ||
1160 | 554 | requests = {} | ||
1161 | 555 | for rid in relation_ids('ceph'): | ||
1162 | 556 | complete = False | ||
1163 | 557 | previous_request = get_previous_request(rid) | ||
1164 | 558 | if request == previous_request: | ||
1165 | 559 | sent = True | ||
1166 | 560 | complete = is_request_complete_for_rid(previous_request, rid) | ||
1167 | 561 | else: | ||
1168 | 562 | sent = False | ||
1169 | 563 | complete = False | ||
1170 | 564 | |||
1171 | 565 | requests[rid] = { | ||
1172 | 566 | 'sent': sent, | ||
1173 | 567 | 'complete': complete, | ||
1174 | 568 | } | ||
1175 | 569 | |||
1176 | 570 | return requests | ||
1177 | 571 | |||
1178 | 572 | |||
1179 | 573 | def is_request_sent(request): | ||
1180 | 574 | """Check to see if a functionally equivalent request has already been sent | ||
1181 | 575 | |||
1182 | 576 | Returns True if a similair request has been sent | ||
1183 | 577 | |||
1184 | 578 | @param request: A CephBrokerRq object | ||
1185 | 579 | """ | ||
1186 | 580 | states = get_request_states(request) | ||
1187 | 581 | for rid in states.keys(): | ||
1188 | 582 | if not states[rid]['sent']: | ||
1189 | 583 | return False | ||
1190 | 584 | |||
1191 | 585 | return True | ||
1192 | 586 | |||
1193 | 587 | |||
1194 | 588 | def is_request_complete(request): | ||
1195 | 589 | """Check to see if a functionally equivalent request has already been | ||
1196 | 590 | completed | ||
1197 | 591 | |||
1198 | 592 | Returns True if a similair request has been completed | ||
1199 | 593 | |||
1200 | 594 | @param request: A CephBrokerRq object | ||
1201 | 595 | """ | ||
1202 | 596 | states = get_request_states(request) | ||
1203 | 597 | for rid in states.keys(): | ||
1204 | 598 | if not states[rid]['complete']: | ||
1205 | 599 | return False | ||
1206 | 600 | |||
1207 | 601 | return True | ||
1208 | 602 | |||
1209 | 603 | |||
1210 | 604 | def is_request_complete_for_rid(request, rid): | ||
1211 | 605 | """Check if a given request has been completed on the given relation | ||
1212 | 606 | |||
1213 | 607 | @param request: A CephBrokerRq object | ||
1214 | 608 | @param rid: Relation ID | ||
1215 | 609 | """ | ||
1216 | 610 | broker_key = get_broker_rsp_key() | ||
1217 | 611 | for unit in related_units(rid): | ||
1218 | 612 | rdata = relation_get(rid=rid, unit=unit) | ||
1219 | 613 | if rdata.get(broker_key): | ||
1220 | 614 | rsp = CephBrokerRsp(rdata.get(broker_key)) | ||
1221 | 615 | if rsp.request_id == request.request_id: | ||
1222 | 616 | if not rsp.exit_code: | ||
1223 | 617 | return True | ||
1224 | 618 | else: | ||
1225 | 619 | # The remote unit sent no reply targeted at this unit so either the | ||
1226 | 620 | # remote ceph cluster does not support unit targeted replies or it | ||
1227 | 621 | # has not processed our request yet. | ||
1228 | 622 | if rdata.get('broker_rsp'): | ||
1229 | 623 | request_data = json.loads(rdata['broker_rsp']) | ||
1230 | 624 | if request_data.get('request-id'): | ||
1231 | 625 | log('Ignoring legacy broker_rsp without unit key as remote ' | ||
1232 | 626 | 'service supports unit specific replies', level=DEBUG) | ||
1233 | 627 | else: | ||
1234 | 628 | log('Using legacy broker_rsp as remote service does not ' | ||
1235 | 629 | 'supports unit specific replies', level=DEBUG) | ||
1236 | 630 | rsp = CephBrokerRsp(rdata['broker_rsp']) | ||
1237 | 631 | if not rsp.exit_code: | ||
1238 | 632 | return True | ||
1239 | 633 | |||
1240 | 634 | return False | ||
1241 | 635 | |||
1242 | 636 | |||
1243 | 637 | def get_broker_rsp_key(): | ||
1244 | 638 | """Return broker response key for this unit | ||
1245 | 639 | |||
1246 | 640 | This is the key that ceph is going to use to pass request status | ||
1247 | 641 | information back to this unit | ||
1248 | 642 | """ | ||
1249 | 643 | return 'broker-rsp-' + local_unit().replace('/', '-') | ||
1250 | 644 | |||
1251 | 645 | |||
1252 | 646 | def send_request_if_needed(request): | ||
1253 | 647 | """Send broker request if an equivalent request has not already been sent | ||
1254 | 648 | |||
1255 | 649 | @param request: A CephBrokerRq object | ||
1256 | 650 | """ | ||
1257 | 651 | if is_request_sent(request): | ||
1258 | 652 | log('Request already sent but not complete, not sending new request', | ||
1259 | 653 | level=DEBUG) | ||
1260 | 654 | else: | ||
1261 | 655 | for rid in relation_ids('ceph'): | ||
1262 | 656 | log('Sending request {}'.format(request.request_id), level=DEBUG) | ||
1263 | 657 | relation_set(relation_id=rid, broker_req=request.request) | ||
1264 | 445 | 658 | ||
1265 | === modified file 'hooks/charmhelpers/core/host.py' | |||
1266 | --- hooks/charmhelpers/core/host.py 2015-08-19 13:48:32 +0000 | |||
1267 | +++ hooks/charmhelpers/core/host.py 2015-09-22 06:12:36 +0000 | |||
1268 | @@ -63,12 +63,10 @@ | |||
1269 | 63 | return service_result | 63 | return service_result |
1270 | 64 | 64 | ||
1271 | 65 | 65 | ||
1273 | 66 | def service_pause(service_name, init_dir=None): | 66 | def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"): |
1274 | 67 | """Pause a system service. | 67 | """Pause a system service. |
1275 | 68 | 68 | ||
1276 | 69 | Stop it, and prevent it from starting again at boot.""" | 69 | Stop it, and prevent it from starting again at boot.""" |
1277 | 70 | if init_dir is None: | ||
1278 | 71 | init_dir = "/etc/init" | ||
1279 | 72 | stopped = service_stop(service_name) | 70 | stopped = service_stop(service_name) |
1280 | 73 | # XXX: Support systemd too | 71 | # XXX: Support systemd too |
1281 | 74 | override_path = os.path.join( | 72 | override_path = os.path.join( |
1282 | @@ -78,7 +76,8 @@ | |||
1283 | 78 | return stopped | 76 | return stopped |
1284 | 79 | 77 | ||
1285 | 80 | 78 | ||
1287 | 81 | def service_resume(service_name, init_dir=None): | 79 | def service_resume(service_name, init_dir="/etc/init", |
1288 | 80 | initd_dir="/etc/init.d"): | ||
1289 | 82 | """Resume a system service. | 81 | """Resume a system service. |
1290 | 83 | 82 | ||
1291 | 84 | Reenable starting again at boot. Start the service""" | 83 | Reenable starting again at boot. Start the service""" |
1292 | 85 | 84 | ||
1293 | === added file 'hooks/charmhelpers/core/kernel.py' | |||
1294 | --- hooks/charmhelpers/core/kernel.py 1970-01-01 00:00:00 +0000 | |||
1295 | +++ hooks/charmhelpers/core/kernel.py 2015-09-22 06:12:36 +0000 | |||
1296 | @@ -0,0 +1,68 @@ | |||
1297 | 1 | #!/usr/bin/env python | ||
1298 | 2 | # -*- coding: utf-8 -*- | ||
1299 | 3 | |||
1300 | 4 | # Copyright 2014-2015 Canonical Limited. | ||
1301 | 5 | # | ||
1302 | 6 | # This file is part of charm-helpers. | ||
1303 | 7 | # | ||
1304 | 8 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1305 | 9 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1306 | 10 | # published by the Free Software Foundation. | ||
1307 | 11 | # | ||
1308 | 12 | # charm-helpers is distributed in the hope that it will be useful, | ||
1309 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1310 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1311 | 15 | # GNU Lesser General Public License for more details. | ||
1312 | 16 | # | ||
1313 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
1314 | 18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1315 | 19 | |||
1316 | 20 | __author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" | ||
1317 | 21 | |||
1318 | 22 | from charmhelpers.core.hookenv import ( | ||
1319 | 23 | log, | ||
1320 | 24 | INFO | ||
1321 | 25 | ) | ||
1322 | 26 | |||
1323 | 27 | from subprocess import check_call, check_output | ||
1324 | 28 | import re | ||
1325 | 29 | |||
1326 | 30 | |||
1327 | 31 | def modprobe(module, persist=True): | ||
1328 | 32 | """Load a kernel module and configure for auto-load on reboot.""" | ||
1329 | 33 | cmd = ['modprobe', module] | ||
1330 | 34 | |||
1331 | 35 | log('Loading kernel module %s' % module, level=INFO) | ||
1332 | 36 | |||
1333 | 37 | check_call(cmd) | ||
1334 | 38 | if persist: | ||
1335 | 39 | with open('/etc/modules', 'r+') as modules: | ||
1336 | 40 | if module not in modules.read(): | ||
1337 | 41 | modules.write(module) | ||
1338 | 42 | |||
1339 | 43 | |||
1340 | 44 | def rmmod(module, force=False): | ||
1341 | 45 | """Remove a module from the linux kernel""" | ||
1342 | 46 | cmd = ['rmmod'] | ||
1343 | 47 | if force: | ||
1344 | 48 | cmd.append('-f') | ||
1345 | 49 | cmd.append(module) | ||
1346 | 50 | log('Removing kernel module %s' % module, level=INFO) | ||
1347 | 51 | return check_call(cmd) | ||
1348 | 52 | |||
1349 | 53 | |||
1350 | 54 | def lsmod(): | ||
1351 | 55 | """Shows what kernel modules are currently loaded""" | ||
1352 | 56 | return check_output(['lsmod'], | ||
1353 | 57 | universal_newlines=True) | ||
1354 | 58 | |||
1355 | 59 | |||
1356 | 60 | def is_module_loaded(module): | ||
1357 | 61 | """Checks if a kernel module is already loaded""" | ||
1358 | 62 | matches = re.findall('^%s[ ]+' % module, lsmod(), re.M) | ||
1359 | 63 | return len(matches) > 0 | ||
1360 | 64 | |||
1361 | 65 | |||
1362 | 66 | def update_initramfs(version='all'): | ||
1363 | 67 | """Updates an initramfs image""" | ||
1364 | 68 | return check_call(["update-initramfs", "-k", version, "-u"]) | ||
1365 | 0 | 69 | ||
1366 | === modified file 'hooks/charmhelpers/core/strutils.py' | |||
1367 | --- hooks/charmhelpers/core/strutils.py 2015-04-14 01:15:40 +0000 | |||
1368 | +++ hooks/charmhelpers/core/strutils.py 2015-09-22 06:12:36 +0000 | |||
1369 | @@ -18,6 +18,7 @@ | |||
1370 | 18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
1371 | 19 | 19 | ||
1372 | 20 | import six | 20 | import six |
1373 | 21 | import re | ||
1374 | 21 | 22 | ||
1375 | 22 | 23 | ||
1376 | 23 | def bool_from_string(value): | 24 | def bool_from_string(value): |
1377 | @@ -40,3 +41,32 @@ | |||
1378 | 40 | 41 | ||
1379 | 41 | msg = "Unable to interpret string value '%s' as boolean" % (value) | 42 | msg = "Unable to interpret string value '%s' as boolean" % (value) |
1380 | 42 | raise ValueError(msg) | 43 | raise ValueError(msg) |
1381 | 44 | |||
1382 | 45 | |||
1383 | 46 | def bytes_from_string(value): | ||
1384 | 47 | """Interpret human readable string value as bytes. | ||
1385 | 48 | |||
1386 | 49 | Returns int | ||
1387 | 50 | """ | ||
1388 | 51 | BYTE_POWER = { | ||
1389 | 52 | 'K': 1, | ||
1390 | 53 | 'KB': 1, | ||
1391 | 54 | 'M': 2, | ||
1392 | 55 | 'MB': 2, | ||
1393 | 56 | 'G': 3, | ||
1394 | 57 | 'GB': 3, | ||
1395 | 58 | 'T': 4, | ||
1396 | 59 | 'TB': 4, | ||
1397 | 60 | 'P': 5, | ||
1398 | 61 | 'PB': 5, | ||
1399 | 62 | } | ||
1400 | 63 | if isinstance(value, six.string_types): | ||
1401 | 64 | value = six.text_type(value) | ||
1402 | 65 | else: | ||
1403 | 66 | msg = "Unable to interpret non-string value '%s' as boolean" % (value) | ||
1404 | 67 | raise ValueError(msg) | ||
1405 | 68 | matches = re.match("([0-9]+)([a-zA-Z]+)", value) | ||
1406 | 69 | if not matches: | ||
1407 | 70 | msg = "Unable to interpret string value '%s' as bytes" % (value) | ||
1408 | 71 | raise ValueError(msg) | ||
1409 | 72 | return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) | ||
1410 | 43 | 73 | ||
1411 | === modified file 'hooks/nova_cc_utils.py' | |||
1412 | --- hooks/nova_cc_utils.py 2015-07-22 12:01:24 +0000 | |||
1413 | +++ hooks/nova_cc_utils.py 2015-09-22 06:12:36 +0000 | |||
1414 | @@ -724,7 +724,10 @@ | |||
1415 | 724 | def ssh_known_host_key(host, unit=None, user=None): | 724 | def ssh_known_host_key(host, unit=None, user=None): |
1416 | 725 | cmd = ['ssh-keygen', '-f', known_hosts(unit, user), '-H', '-F', host] | 725 | cmd = ['ssh-keygen', '-f', known_hosts(unit, user), '-H', '-F', host] |
1417 | 726 | try: | 726 | try: |
1419 | 727 | return subprocess.check_output(cmd).strip() | 727 | # The first line of output is like '# Host xx found: line 1 type RSA', |
1420 | 728 | # which should be excluded. | ||
1421 | 729 | output = subprocess.check_output(cmd).strip() | ||
1422 | 730 | return output.split('\n')[1] | ||
1423 | 728 | except subprocess.CalledProcessError: | 731 | except subprocess.CalledProcessError: |
1424 | 729 | return None | 732 | return None |
1425 | 730 | 733 | ||
1426 | @@ -735,6 +738,16 @@ | |||
1427 | 735 | subprocess.check_call(cmd) | 738 | subprocess.check_call(cmd) |
1428 | 736 | 739 | ||
1429 | 737 | 740 | ||
1430 | 741 | def is_same_key(key_1, key_2): | ||
1431 | 742 | # The key format get will be like '|1|2rUumCavEXWVaVyB5uMl6m85pZo=|Cp' | ||
1432 | 743 | # 'EL6l7VTY37T/fg/ihhNb/GPgs= ssh-rsa AAAAB', we only need to compare | ||
1433 | 744 | # the part start with 'ssh-rsa' followed with '= ', because the hash | ||
1434 | 745 | # value in the beginning will change each time. | ||
1435 | 746 | k_1 = key_1.split('= ')[1] | ||
1436 | 747 | k_2 = key_2.split('= ')[1] | ||
1437 | 748 | return k_1 == k_2 | ||
1438 | 749 | |||
1439 | 750 | |||
1440 | 738 | def add_known_host(host, unit=None, user=None): | 751 | def add_known_host(host, unit=None, user=None): |
1441 | 739 | '''Add variations of host to a known hosts file.''' | 752 | '''Add variations of host to a known hosts file.''' |
1442 | 740 | cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host] | 753 | cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host] |
1443 | @@ -745,8 +758,8 @@ | |||
1444 | 745 | raise e | 758 | raise e |
1445 | 746 | 759 | ||
1446 | 747 | current_key = ssh_known_host_key(host, unit, user) | 760 | current_key = ssh_known_host_key(host, unit, user) |
1449 | 748 | if current_key: | 761 | if current_key and remote_key: |
1450 | 749 | if remote_key == current_key: | 762 | if is_same_key(remote_key, current_key): |
1451 | 750 | log('Known host key for compute host %s up to date.' % host) | 763 | log('Known host key for compute host %s up to date.' % host) |
1452 | 751 | return | 764 | return |
1453 | 752 | else: | 765 | else: |
1454 | @@ -787,8 +800,7 @@ | |||
1455 | 787 | hosts.append(hn.split('.')[0]) | 800 | hosts.append(hn.split('.')[0]) |
1456 | 788 | 801 | ||
1457 | 789 | for host in list(set(hosts)): | 802 | for host in list(set(hosts)): |
1460 | 790 | if not ssh_known_host_key(host, unit, user): | 803 | add_known_host(host, unit, user) |
1459 | 791 | add_known_host(host, unit, user) | ||
1461 | 792 | 804 | ||
1462 | 793 | if not ssh_authorized_key_exists(public_key, unit, user): | 805 | if not ssh_authorized_key_exists(public_key, unit, user): |
1463 | 794 | log('Saving SSH authorized key for compute host at %s.' % | 806 | log('Saving SSH authorized key for compute host at %s.' % |
1464 | 795 | 807 | ||
1465 | === modified file 'tests/charmhelpers/contrib/amulet/deployment.py' | |||
1466 | --- tests/charmhelpers/contrib/amulet/deployment.py 2015-03-31 11:39:19 +0000 | |||
1467 | +++ tests/charmhelpers/contrib/amulet/deployment.py 2015-09-22 06:12:36 +0000 | |||
1468 | @@ -51,7 +51,8 @@ | |||
1469 | 51 | if 'units' not in this_service: | 51 | if 'units' not in this_service: |
1470 | 52 | this_service['units'] = 1 | 52 | this_service['units'] = 1 |
1471 | 53 | 53 | ||
1473 | 54 | self.d.add(this_service['name'], units=this_service['units']) | 54 | self.d.add(this_service['name'], units=this_service['units'], |
1474 | 55 | constraints=this_service.get('constraints')) | ||
1475 | 55 | 56 | ||
1476 | 56 | for svc in other_services: | 57 | for svc in other_services: |
1477 | 57 | if 'location' in svc: | 58 | if 'location' in svc: |
1478 | @@ -64,7 +65,8 @@ | |||
1479 | 64 | if 'units' not in svc: | 65 | if 'units' not in svc: |
1480 | 65 | svc['units'] = 1 | 66 | svc['units'] = 1 |
1481 | 66 | 67 | ||
1483 | 67 | self.d.add(svc['name'], charm=branch_location, units=svc['units']) | 68 | self.d.add(svc['name'], charm=branch_location, units=svc['units'], |
1484 | 69 | constraints=svc.get('constraints')) | ||
1485 | 68 | 70 | ||
1486 | 69 | def _add_relations(self, relations): | 71 | def _add_relations(self, relations): |
1487 | 70 | """Add all of the relations for the services.""" | 72 | """Add all of the relations for the services.""" |
1488 | 71 | 73 | ||
1489 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' | |||
1490 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-08-18 17:34:35 +0000 | |||
1491 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-22 06:12:36 +0000 | |||
1492 | @@ -114,7 +114,7 @@ | |||
1493 | 114 | # /!\ DEPRECATION WARNING (beisner): | 114 | # /!\ DEPRECATION WARNING (beisner): |
1494 | 115 | # New and existing tests should be rewritten to use | 115 | # New and existing tests should be rewritten to use |
1495 | 116 | # validate_services_by_name() as it is aware of init systems. | 116 | # validate_services_by_name() as it is aware of init systems. |
1497 | 117 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | 117 | self.log.warn('DEPRECATION WARNING: use ' |
1498 | 118 | 'validate_services_by_name instead of validate_services ' | 118 | 'validate_services_by_name instead of validate_services ' |
1499 | 119 | 'due to init system differences.') | 119 | 'due to init system differences.') |
1500 | 120 | 120 | ||
1501 | @@ -269,33 +269,52 @@ | |||
1502 | 269 | """Get last modification time of directory.""" | 269 | """Get last modification time of directory.""" |
1503 | 270 | return sentry_unit.directory_stat(directory)['mtime'] | 270 | return sentry_unit.directory_stat(directory)['mtime'] |
1504 | 271 | 271 | ||
1523 | 272 | def _get_proc_start_time(self, sentry_unit, service, pgrep_full=False): | 272 | def _get_proc_start_time(self, sentry_unit, service, pgrep_full=None): |
1524 | 273 | """Get process' start time. | 273 | """Get start time of a process based on the last modification time |
1525 | 274 | 274 | of the /proc/pid directory. | |
1526 | 275 | Determine start time of the process based on the last modification | 275 | |
1527 | 276 | time of the /proc/pid directory. If pgrep_full is True, the process | 276 | :sentry_unit: The sentry unit to check for the service on |
1528 | 277 | name is matched against the full command line. | 277 | :service: service name to look for in process table |
1529 | 278 | """ | 278 | :pgrep_full: [Deprecated] Use full command line search mode with pgrep |
1530 | 279 | if pgrep_full: | 279 | :returns: epoch time of service process start |
1531 | 280 | cmd = 'pgrep -o -f {}'.format(service) | 280 | :param commands: list of bash commands |
1532 | 281 | else: | 281 | :param sentry_units: list of sentry unit pointers |
1533 | 282 | cmd = 'pgrep -o {}'.format(service) | 282 | :returns: None if successful; Failure message otherwise |
1534 | 283 | cmd = cmd + ' | grep -v pgrep || exit 0' | 283 | """ |
1535 | 284 | cmd_out = sentry_unit.run(cmd) | 284 | if pgrep_full is not None: |
1536 | 285 | self.log.debug('CMDout: ' + str(cmd_out)) | 285 | # /!\ DEPRECATION WARNING (beisner): |
1537 | 286 | if cmd_out[0]: | 286 | # No longer implemented, as pidof is now used instead of pgrep. |
1538 | 287 | self.log.debug('Pid for %s %s' % (service, str(cmd_out[0]))) | 287 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 |
1539 | 288 | proc_dir = '/proc/{}'.format(cmd_out[0].strip()) | 288 | self.log.warn('DEPRECATION WARNING: pgrep_full bool is no ' |
1540 | 289 | return self._get_dir_mtime(sentry_unit, proc_dir) | 289 | 'longer implemented re: lp 1474030.') |
1541 | 290 | |||
1542 | 291 | pid_list = self.get_process_id_list(sentry_unit, service) | ||
1543 | 292 | pid = pid_list[0] | ||
1544 | 293 | proc_dir = '/proc/{}'.format(pid) | ||
1545 | 294 | self.log.debug('Pid for {} on {}: {}'.format( | ||
1546 | 295 | service, sentry_unit.info['unit_name'], pid)) | ||
1547 | 296 | |||
1548 | 297 | return self._get_dir_mtime(sentry_unit, proc_dir) | ||
1549 | 290 | 298 | ||
1550 | 291 | def service_restarted(self, sentry_unit, service, filename, | 299 | def service_restarted(self, sentry_unit, service, filename, |
1552 | 292 | pgrep_full=False, sleep_time=20): | 300 | pgrep_full=None, sleep_time=20): |
1553 | 293 | """Check if service was restarted. | 301 | """Check if service was restarted. |
1554 | 294 | 302 | ||
1555 | 295 | Compare a service's start time vs a file's last modification time | 303 | Compare a service's start time vs a file's last modification time |
1556 | 296 | (such as a config file for that service) to determine if the service | 304 | (such as a config file for that service) to determine if the service |
1557 | 297 | has been restarted. | 305 | has been restarted. |
1558 | 298 | """ | 306 | """ |
1559 | 307 | # /!\ DEPRECATION WARNING (beisner): | ||
1560 | 308 | # This method is prone to races in that no before-time is known. | ||
1561 | 309 | # Use validate_service_config_changed instead. | ||
1562 | 310 | |||
1563 | 311 | # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now | ||
1564 | 312 | # used instead of pgrep. pgrep_full is still passed through to ensure | ||
1565 | 313 | # deprecation WARNS. lp1474030 | ||
1566 | 314 | self.log.warn('DEPRECATION WARNING: use ' | ||
1567 | 315 | 'validate_service_config_changed instead of ' | ||
1568 | 316 | 'service_restarted due to known races.') | ||
1569 | 317 | |||
1570 | 299 | time.sleep(sleep_time) | 318 | time.sleep(sleep_time) |
1571 | 300 | if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >= | 319 | if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >= |
1572 | 301 | self._get_file_mtime(sentry_unit, filename)): | 320 | self._get_file_mtime(sentry_unit, filename)): |
1573 | @@ -304,15 +323,15 @@ | |||
1574 | 304 | return False | 323 | return False |
1575 | 305 | 324 | ||
1576 | 306 | def service_restarted_since(self, sentry_unit, mtime, service, | 325 | def service_restarted_since(self, sentry_unit, mtime, service, |
1579 | 307 | pgrep_full=False, sleep_time=20, | 326 | pgrep_full=None, sleep_time=20, |
1580 | 308 | retry_count=2): | 327 | retry_count=2, retry_sleep_time=30): |
1581 | 309 | """Check if service was been started after a given time. | 328 | """Check if service was been started after a given time. |
1582 | 310 | 329 | ||
1583 | 311 | Args: | 330 | Args: |
1584 | 312 | sentry_unit (sentry): The sentry unit to check for the service on | 331 | sentry_unit (sentry): The sentry unit to check for the service on |
1585 | 313 | mtime (float): The epoch time to check against | 332 | mtime (float): The epoch time to check against |
1586 | 314 | service (string): service name to look for in process table | 333 | service (string): service name to look for in process table |
1588 | 315 | pgrep_full (boolean): Use full command line search mode with pgrep | 334 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
1589 | 316 | sleep_time (int): Seconds to sleep before looking for process | 335 | sleep_time (int): Seconds to sleep before looking for process |
1590 | 317 | retry_count (int): If service is not found, how many times to retry | 336 | retry_count (int): If service is not found, how many times to retry |
1591 | 318 | 337 | ||
1592 | @@ -321,30 +340,44 @@ | |||
1593 | 321 | False if service is older than mtime or if service was | 340 | False if service is older than mtime or if service was |
1594 | 322 | not found. | 341 | not found. |
1595 | 323 | """ | 342 | """ |
1597 | 324 | self.log.debug('Checking %s restarted since %s' % (service, mtime)) | 343 | # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now |
1598 | 344 | # used instead of pgrep. pgrep_full is still passed through to ensure | ||
1599 | 345 | # deprecation WARNS. lp1474030 | ||
1600 | 346 | |||
1601 | 347 | unit_name = sentry_unit.info['unit_name'] | ||
1602 | 348 | self.log.debug('Checking that %s service restarted since %s on ' | ||
1603 | 349 | '%s' % (service, mtime, unit_name)) | ||
1604 | 325 | time.sleep(sleep_time) | 350 | time.sleep(sleep_time) |
1614 | 326 | proc_start_time = self._get_proc_start_time(sentry_unit, service, | 351 | proc_start_time = None |
1615 | 327 | pgrep_full) | 352 | tries = 0 |
1616 | 328 | while retry_count > 0 and not proc_start_time: | 353 | while tries <= retry_count and not proc_start_time: |
1617 | 329 | self.log.debug('No pid file found for service %s, will retry %i ' | 354 | try: |
1618 | 330 | 'more times' % (service, retry_count)) | 355 | proc_start_time = self._get_proc_start_time(sentry_unit, |
1619 | 331 | time.sleep(30) | 356 | service, |
1620 | 332 | proc_start_time = self._get_proc_start_time(sentry_unit, service, | 357 | pgrep_full) |
1621 | 333 | pgrep_full) | 358 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
1622 | 334 | retry_count = retry_count - 1 | 359 | 'OK'.format(tries, service, unit_name)) |
1623 | 360 | except IOError: | ||
1624 | 361 | # NOTE(beisner) - race avoidance, proc may not exist yet. | ||
1625 | 362 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 | ||
1626 | 363 | self.log.debug('Attempt {} to get {} proc start time on {} ' | ||
1627 | 364 | 'failed'.format(tries, service, unit_name)) | ||
1628 | 365 | time.sleep(retry_sleep_time) | ||
1629 | 366 | tries += 1 | ||
1630 | 335 | 367 | ||
1631 | 336 | if not proc_start_time: | 368 | if not proc_start_time: |
1632 | 337 | self.log.warn('No proc start time found, assuming service did ' | 369 | self.log.warn('No proc start time found, assuming service did ' |
1633 | 338 | 'not start') | 370 | 'not start') |
1634 | 339 | return False | 371 | return False |
1635 | 340 | if proc_start_time >= mtime: | 372 | if proc_start_time >= mtime: |
1638 | 341 | self.log.debug('proc start time is newer than provided mtime' | 373 | self.log.debug('Proc start time is newer than provided mtime' |
1639 | 342 | '(%s >= %s)' % (proc_start_time, mtime)) | 374 | '(%s >= %s) on %s (OK)' % (proc_start_time, |
1640 | 375 | mtime, unit_name)) | ||
1641 | 343 | return True | 376 | return True |
1642 | 344 | else: | 377 | else: |
1646 | 345 | self.log.warn('proc start time (%s) is older than provided mtime ' | 378 | self.log.warn('Proc start time (%s) is older than provided mtime ' |
1647 | 346 | '(%s), service did not restart' % (proc_start_time, | 379 | '(%s) on %s, service did not ' |
1648 | 347 | mtime)) | 380 | 'restart' % (proc_start_time, mtime, unit_name)) |
1649 | 348 | return False | 381 | return False |
1650 | 349 | 382 | ||
1651 | 350 | def config_updated_since(self, sentry_unit, filename, mtime, | 383 | def config_updated_since(self, sentry_unit, filename, mtime, |
1652 | @@ -374,8 +407,9 @@ | |||
1653 | 374 | return False | 407 | return False |
1654 | 375 | 408 | ||
1655 | 376 | def validate_service_config_changed(self, sentry_unit, mtime, service, | 409 | def validate_service_config_changed(self, sentry_unit, mtime, service, |
1658 | 377 | filename, pgrep_full=False, | 410 | filename, pgrep_full=None, |
1659 | 378 | sleep_time=20, retry_count=2): | 411 | sleep_time=20, retry_count=2, |
1660 | 412 | retry_sleep_time=30): | ||
1661 | 379 | """Check service and file were updated after mtime | 413 | """Check service and file were updated after mtime |
1662 | 380 | 414 | ||
1663 | 381 | Args: | 415 | Args: |
1664 | @@ -383,9 +417,10 @@ | |||
1665 | 383 | mtime (float): The epoch time to check against | 417 | mtime (float): The epoch time to check against |
1666 | 384 | service (string): service name to look for in process table | 418 | service (string): service name to look for in process table |
1667 | 385 | filename (string): The file to check mtime of | 419 | filename (string): The file to check mtime of |
1670 | 386 | pgrep_full (boolean): Use full command line search mode with pgrep | 420 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
1671 | 387 | sleep_time (int): Seconds to sleep before looking for process | 421 | sleep_time (int): Initial sleep in seconds to pass to test helpers |
1672 | 388 | retry_count (int): If service is not found, how many times to retry | 422 | retry_count (int): If service is not found, how many times to retry |
1673 | 423 | retry_sleep_time (int): Time in seconds to wait between retries | ||
1674 | 389 | 424 | ||
1675 | 390 | Typical Usage: | 425 | Typical Usage: |
1676 | 391 | u = OpenStackAmuletUtils(ERROR) | 426 | u = OpenStackAmuletUtils(ERROR) |
1677 | @@ -402,15 +437,25 @@ | |||
1678 | 402 | mtime, False if service is older than mtime or if service was | 437 | mtime, False if service is older than mtime or if service was |
1679 | 403 | not found or if filename was modified before mtime. | 438 | not found or if filename was modified before mtime. |
1680 | 404 | """ | 439 | """ |
1690 | 405 | self.log.debug('Checking %s restarted since %s' % (service, mtime)) | 440 | |
1691 | 406 | time.sleep(sleep_time) | 441 | # NOTE(beisner) pgrep_full is no longer implemented, as pidof is now |
1692 | 407 | service_restart = self.service_restarted_since(sentry_unit, mtime, | 442 | # used instead of pgrep. pgrep_full is still passed through to ensure |
1693 | 408 | service, | 443 | # deprecation WARNS. lp1474030 |
1694 | 409 | pgrep_full=pgrep_full, | 444 | |
1695 | 410 | sleep_time=0, | 445 | service_restart = self.service_restarted_since( |
1696 | 411 | retry_count=retry_count) | 446 | sentry_unit, mtime, |
1697 | 412 | config_update = self.config_updated_since(sentry_unit, filename, mtime, | 447 | service, |
1698 | 413 | sleep_time=0) | 448 | pgrep_full=pgrep_full, |
1699 | 449 | sleep_time=sleep_time, | ||
1700 | 450 | retry_count=retry_count, | ||
1701 | 451 | retry_sleep_time=retry_sleep_time) | ||
1702 | 452 | |||
1703 | 453 | config_update = self.config_updated_since( | ||
1704 | 454 | sentry_unit, | ||
1705 | 455 | filename, | ||
1706 | 456 | mtime, | ||
1707 | 457 | sleep_time=0) | ||
1708 | 458 | |||
1709 | 414 | return service_restart and config_update | 459 | return service_restart and config_update |
1710 | 415 | 460 | ||
1711 | 416 | def get_sentry_time(self, sentry_unit): | 461 | def get_sentry_time(self, sentry_unit): |
1712 | @@ -428,7 +473,6 @@ | |||
1713 | 428 | """Return a list of all Ubuntu releases in order of release.""" | 473 | """Return a list of all Ubuntu releases in order of release.""" |
1714 | 429 | _d = distro_info.UbuntuDistroInfo() | 474 | _d = distro_info.UbuntuDistroInfo() |
1715 | 430 | _release_list = _d.all | 475 | _release_list = _d.all |
1716 | 431 | self.log.debug('Ubuntu release list: {}'.format(_release_list)) | ||
1717 | 432 | return _release_list | 476 | return _release_list |
1718 | 433 | 477 | ||
1719 | 434 | def file_to_url(self, file_rel_path): | 478 | def file_to_url(self, file_rel_path): |
1720 | 435 | 479 | ||
1721 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' | |||
1722 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:18:38 +0000 | |||
1723 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-22 06:12:36 +0000 | |||
1724 | @@ -27,6 +27,7 @@ | |||
1725 | 27 | import heatclient.v1.client as heat_client | 27 | import heatclient.v1.client as heat_client |
1726 | 28 | import keystoneclient.v2_0 as keystone_client | 28 | import keystoneclient.v2_0 as keystone_client |
1727 | 29 | import novaclient.v1_1.client as nova_client | 29 | import novaclient.v1_1.client as nova_client |
1728 | 30 | import pika | ||
1729 | 30 | import swiftclient | 31 | import swiftclient |
1730 | 31 | 32 | ||
1731 | 32 | from charmhelpers.contrib.amulet.utils import ( | 33 | from charmhelpers.contrib.amulet.utils import ( |
1732 | @@ -602,3 +603,361 @@ | |||
1733 | 602 | self.log.debug('Ceph {} samples (OK): ' | 603 | self.log.debug('Ceph {} samples (OK): ' |
1734 | 603 | '{}'.format(sample_type, samples)) | 604 | '{}'.format(sample_type, samples)) |
1735 | 604 | return None | 605 | return None |
1736 | 606 | |||
1737 | 607 | # rabbitmq/amqp specific helpers: | ||
1738 | 608 | def add_rmq_test_user(self, sentry_units, | ||
1739 | 609 | username="testuser1", password="changeme"): | ||
1740 | 610 | """Add a test user via the first rmq juju unit, check connection as | ||
1741 | 611 | the new user against all sentry units. | ||
1742 | 612 | |||
1743 | 613 | :param sentry_units: list of sentry unit pointers | ||
1744 | 614 | :param username: amqp user name, default to testuser1 | ||
1745 | 615 | :param password: amqp user password | ||
1746 | 616 | :returns: None if successful. Raise on error. | ||
1747 | 617 | """ | ||
1748 | 618 | self.log.debug('Adding rmq user ({})...'.format(username)) | ||
1749 | 619 | |||
1750 | 620 | # Check that user does not already exist | ||
1751 | 621 | cmd_user_list = 'rabbitmqctl list_users' | ||
1752 | 622 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
1753 | 623 | if username in output: | ||
1754 | 624 | self.log.warning('User ({}) already exists, returning ' | ||
1755 | 625 | 'gracefully.'.format(username)) | ||
1756 | 626 | return | ||
1757 | 627 | |||
1758 | 628 | perms = '".*" ".*" ".*"' | ||
1759 | 629 | cmds = ['rabbitmqctl add_user {} {}'.format(username, password), | ||
1760 | 630 | 'rabbitmqctl set_permissions {} {}'.format(username, perms)] | ||
1761 | 631 | |||
1762 | 632 | # Add user via first unit | ||
1763 | 633 | for cmd in cmds: | ||
1764 | 634 | output, _ = self.run_cmd_unit(sentry_units[0], cmd) | ||
1765 | 635 | |||
1766 | 636 | # Check connection against the other sentry_units | ||
1767 | 637 | self.log.debug('Checking user connect against units...') | ||
1768 | 638 | for sentry_unit in sentry_units: | ||
1769 | 639 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=False, | ||
1770 | 640 | username=username, | ||
1771 | 641 | password=password) | ||
1772 | 642 | connection.close() | ||
1773 | 643 | |||
1774 | 644 | def delete_rmq_test_user(self, sentry_units, username="testuser1"): | ||
1775 | 645 | """Delete a rabbitmq user via the first rmq juju unit. | ||
1776 | 646 | |||
1777 | 647 | :param sentry_units: list of sentry unit pointers | ||
1778 | 648 | :param username: amqp user name, default to testuser1 | ||
1779 | 649 | :param password: amqp user password | ||
1780 | 650 | :returns: None if successful or no such user. | ||
1781 | 651 | """ | ||
1782 | 652 | self.log.debug('Deleting rmq user ({})...'.format(username)) | ||
1783 | 653 | |||
1784 | 654 | # Check that the user exists | ||
1785 | 655 | cmd_user_list = 'rabbitmqctl list_users' | ||
1786 | 656 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
1787 | 657 | |||
1788 | 658 | if username not in output: | ||
1789 | 659 | self.log.warning('User ({}) does not exist, returning ' | ||
1790 | 660 | 'gracefully.'.format(username)) | ||
1791 | 661 | return | ||
1792 | 662 | |||
1793 | 663 | # Delete the user | ||
1794 | 664 | cmd_user_del = 'rabbitmqctl delete_user {}'.format(username) | ||
1795 | 665 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_del) | ||
1796 | 666 | |||
1797 | 667 | def get_rmq_cluster_status(self, sentry_unit): | ||
1798 | 668 | """Execute rabbitmq cluster status command on a unit and return | ||
1799 | 669 | the full output. | ||
1800 | 670 | |||
1801 | 671 | :param unit: sentry unit | ||
1802 | 672 | :returns: String containing console output of cluster status command | ||
1803 | 673 | """ | ||
1804 | 674 | cmd = 'rabbitmqctl cluster_status' | ||
1805 | 675 | output, _ = self.run_cmd_unit(sentry_unit, cmd) | ||
1806 | 676 | self.log.debug('{} cluster_status:\n{}'.format( | ||
1807 | 677 | sentry_unit.info['unit_name'], output)) | ||
1808 | 678 | return str(output) | ||
1809 | 679 | |||
1810 | 680 | def get_rmq_cluster_running_nodes(self, sentry_unit): | ||
1811 | 681 | """Parse rabbitmqctl cluster_status output string, return list of | ||
1812 | 682 | running rabbitmq cluster nodes. | ||
1813 | 683 | |||
1814 | 684 | :param unit: sentry unit | ||
1815 | 685 | :returns: List containing node names of running nodes | ||
1816 | 686 | """ | ||
1817 | 687 | # NOTE(beisner): rabbitmqctl cluster_status output is not | ||
1818 | 688 | # json-parsable, do string chop foo, then json.loads that. | ||
1819 | 689 | str_stat = self.get_rmq_cluster_status(sentry_unit) | ||
1820 | 690 | if 'running_nodes' in str_stat: | ||
1821 | 691 | pos_start = str_stat.find("{running_nodes,") + 15 | ||
1822 | 692 | pos_end = str_stat.find("]},", pos_start) + 1 | ||
1823 | 693 | str_run_nodes = str_stat[pos_start:pos_end].replace("'", '"') | ||
1824 | 694 | run_nodes = json.loads(str_run_nodes) | ||
1825 | 695 | return run_nodes | ||
1826 | 696 | else: | ||
1827 | 697 | return [] | ||
1828 | 698 | |||
1829 | 699 | def validate_rmq_cluster_running_nodes(self, sentry_units): | ||
1830 | 700 | """Check that all rmq unit hostnames are represented in the | ||
1831 | 701 | cluster_status output of all units. | ||
1832 | 702 | |||
1833 | 703 | :param host_names: dict of juju unit names to host names | ||
1834 | 704 | :param units: list of sentry unit pointers (all rmq units) | ||
1835 | 705 | :returns: None if successful, otherwise return error message | ||
1836 | 706 | """ | ||
1837 | 707 | host_names = self.get_unit_hostnames(sentry_units) | ||
1838 | 708 | errors = [] | ||
1839 | 709 | |||
1840 | 710 | # Query every unit for cluster_status running nodes | ||
1841 | 711 | for query_unit in sentry_units: | ||
1842 | 712 | query_unit_name = query_unit.info['unit_name'] | ||
1843 | 713 | running_nodes = self.get_rmq_cluster_running_nodes(query_unit) | ||
1844 | 714 | |||
1845 | 715 | # Confirm that every unit is represented in the queried unit's | ||
1846 | 716 | # cluster_status running nodes output. | ||
1847 | 717 | for validate_unit in sentry_units: | ||
1848 | 718 | val_host_name = host_names[validate_unit.info['unit_name']] | ||
1849 | 719 | val_node_name = 'rabbit@{}'.format(val_host_name) | ||
1850 | 720 | |||
1851 | 721 | if val_node_name not in running_nodes: | ||
1852 | 722 | errors.append('Cluster member check failed on {}: {} not ' | ||
1853 | 723 | 'in {}\n'.format(query_unit_name, | ||
1854 | 724 | val_node_name, | ||
1855 | 725 | running_nodes)) | ||
1856 | 726 | if errors: | ||
1857 | 727 | return ''.join(errors) | ||
1858 | 728 | |||
1859 | 729 | def rmq_ssl_is_enabled_on_unit(self, sentry_unit, port=None): | ||
1860 | 730 | """Check a single juju rmq unit for ssl and port in the config file.""" | ||
1861 | 731 | host = sentry_unit.info['public-address'] | ||
1862 | 732 | unit_name = sentry_unit.info['unit_name'] | ||
1863 | 733 | |||
1864 | 734 | conf_file = '/etc/rabbitmq/rabbitmq.config' | ||
1865 | 735 | conf_contents = str(self.file_contents_safe(sentry_unit, | ||
1866 | 736 | conf_file, max_wait=16)) | ||
1867 | 737 | # Checks | ||
1868 | 738 | conf_ssl = 'ssl' in conf_contents | ||
1869 | 739 | conf_port = str(port) in conf_contents | ||
1870 | 740 | |||
1871 | 741 | # Port explicitly checked in config | ||
1872 | 742 | if port and conf_port and conf_ssl: | ||
1873 | 743 | self.log.debug('SSL is enabled @{}:{} ' | ||
1874 | 744 | '({})'.format(host, port, unit_name)) | ||
1875 | 745 | return True | ||
1876 | 746 | elif port and not conf_port and conf_ssl: | ||
1877 | 747 | self.log.debug('SSL is enabled @{} but not on port {} ' | ||
1878 | 748 | '({})'.format(host, port, unit_name)) | ||
1879 | 749 | return False | ||
1880 | 750 | # Port not checked (useful when checking that ssl is disabled) | ||
1881 | 751 | elif not port and conf_ssl: | ||
1882 | 752 | self.log.debug('SSL is enabled @{}:{} ' | ||
1883 | 753 | '({})'.format(host, port, unit_name)) | ||
1884 | 754 | return True | ||
1885 | 755 | elif not port and not conf_ssl: | ||
1886 | 756 | self.log.debug('SSL not enabled @{}:{} ' | ||
1887 | 757 | '({})'.format(host, port, unit_name)) | ||
1888 | 758 | return False | ||
1889 | 759 | else: | ||
1890 | 760 | msg = ('Unknown condition when checking SSL status @{}:{} ' | ||
1891 | 761 | '({})'.format(host, port, unit_name)) | ||
1892 | 762 | amulet.raise_status(amulet.FAIL, msg) | ||
1893 | 763 | |||
1894 | 764 | def validate_rmq_ssl_enabled_units(self, sentry_units, port=None): | ||
1895 | 765 | """Check that ssl is enabled on rmq juju sentry units. | ||
1896 | 766 | |||
1897 | 767 | :param sentry_units: list of all rmq sentry units | ||
1898 | 768 | :param port: optional ssl port override to validate | ||
1899 | 769 | :returns: None if successful, otherwise return error message | ||
1900 | 770 | """ | ||
1901 | 771 | for sentry_unit in sentry_units: | ||
1902 | 772 | if not self.rmq_ssl_is_enabled_on_unit(sentry_unit, port=port): | ||
1903 | 773 | return ('Unexpected condition: ssl is disabled on unit ' | ||
1904 | 774 | '({})'.format(sentry_unit.info['unit_name'])) | ||
1905 | 775 | return None | ||
1906 | 776 | |||
1907 | 777 | def validate_rmq_ssl_disabled_units(self, sentry_units): | ||
1908 | 778 | """Check that ssl is enabled on listed rmq juju sentry units. | ||
1909 | 779 | |||
1910 | 780 | :param sentry_units: list of all rmq sentry units | ||
1911 | 781 | :returns: True if successful. Raise on error. | ||
1912 | 782 | """ | ||
1913 | 783 | for sentry_unit in sentry_units: | ||
1914 | 784 | if self.rmq_ssl_is_enabled_on_unit(sentry_unit): | ||
1915 | 785 | return ('Unexpected condition: ssl is enabled on unit ' | ||
1916 | 786 | '({})'.format(sentry_unit.info['unit_name'])) | ||
1917 | 787 | return None | ||
1918 | 788 | |||
1919 | 789 | def configure_rmq_ssl_on(self, sentry_units, deployment, | ||
1920 | 790 | port=None, max_wait=60): | ||
1921 | 791 | """Turn ssl charm config option on, with optional non-default | ||
1922 | 792 | ssl port specification. Confirm that it is enabled on every | ||
1923 | 793 | unit. | ||
1924 | 794 | |||
1925 | 795 | :param sentry_units: list of sentry units | ||
1926 | 796 | :param deployment: amulet deployment object pointer | ||
1927 | 797 | :param port: amqp port, use defaults if None | ||
1928 | 798 | :param max_wait: maximum time to wait in seconds to confirm | ||
1929 | 799 | :returns: None if successful. Raise on error. | ||
1930 | 800 | """ | ||
1931 | 801 | self.log.debug('Setting ssl charm config option: on') | ||
1932 | 802 | |||
1933 | 803 | # Enable RMQ SSL | ||
1934 | 804 | config = {'ssl': 'on'} | ||
1935 | 805 | if port: | ||
1936 | 806 | config['ssl_port'] = port | ||
1937 | 807 | |||
1938 | 808 | deployment.configure('rabbitmq-server', config) | ||
1939 | 809 | |||
1940 | 810 | # Confirm | ||
1941 | 811 | tries = 0 | ||
1942 | 812 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
1943 | 813 | while ret and tries < (max_wait / 4): | ||
1944 | 814 | time.sleep(4) | ||
1945 | 815 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
1946 | 816 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
1947 | 817 | tries += 1 | ||
1948 | 818 | |||
1949 | 819 | if ret: | ||
1950 | 820 | amulet.raise_status(amulet.FAIL, ret) | ||
1951 | 821 | |||
1952 | 822 | def configure_rmq_ssl_off(self, sentry_units, deployment, max_wait=60): | ||
1953 | 823 | """Turn ssl charm config option off, confirm that it is disabled | ||
1954 | 824 | on every unit. | ||
1955 | 825 | |||
1956 | 826 | :param sentry_units: list of sentry units | ||
1957 | 827 | :param deployment: amulet deployment object pointer | ||
1958 | 828 | :param max_wait: maximum time to wait in seconds to confirm | ||
1959 | 829 | :returns: None if successful. Raise on error. | ||
1960 | 830 | """ | ||
1961 | 831 | self.log.debug('Setting ssl charm config option: off') | ||
1962 | 832 | |||
1963 | 833 | # Disable RMQ SSL | ||
1964 | 834 | config = {'ssl': 'off'} | ||
1965 | 835 | deployment.configure('rabbitmq-server', config) | ||
1966 | 836 | |||
1967 | 837 | # Confirm | ||
1968 | 838 | tries = 0 | ||
1969 | 839 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
1970 | 840 | while ret and tries < (max_wait / 4): | ||
1971 | 841 | time.sleep(4) | ||
1972 | 842 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
1973 | 843 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
1974 | 844 | tries += 1 | ||
1975 | 845 | |||
1976 | 846 | if ret: | ||
1977 | 847 | amulet.raise_status(amulet.FAIL, ret) | ||
1978 | 848 | |||
1979 | 849 | def connect_amqp_by_unit(self, sentry_unit, ssl=False, | ||
1980 | 850 | port=None, fatal=True, | ||
1981 | 851 | username="testuser1", password="changeme"): | ||
1982 | 852 | """Establish and return a pika amqp connection to the rabbitmq service | ||
1983 | 853 | running on a rmq juju unit. | ||
1984 | 854 | |||
1985 | 855 | :param sentry_unit: sentry unit pointer | ||
1986 | 856 | :param ssl: boolean, default to False | ||
1987 | 857 | :param port: amqp port, use defaults if None | ||
1988 | 858 | :param fatal: boolean, default to True (raises on connect error) | ||
1989 | 859 | :param username: amqp user name, default to testuser1 | ||
1990 | 860 | :param password: amqp user password | ||
1991 | 861 | :returns: pika amqp connection pointer or None if failed and non-fatal | ||
1992 | 862 | """ | ||
1993 | 863 | host = sentry_unit.info['public-address'] | ||
1994 | 864 | unit_name = sentry_unit.info['unit_name'] | ||
1995 | 865 | |||
1996 | 866 | # Default port logic if port is not specified | ||
1997 | 867 | if ssl and not port: | ||
1998 | 868 | port = 5671 | ||
1999 | 869 | elif not ssl and not port: | ||
2000 | 870 | port = 5672 | ||
2001 | 871 | |||
2002 | 872 | self.log.debug('Connecting to amqp on {}:{} ({}) as ' | ||
2003 | 873 | '{}...'.format(host, port, unit_name, username)) | ||
2004 | 874 | |||
2005 | 875 | try: | ||
2006 | 876 | credentials = pika.PlainCredentials(username, password) | ||
2007 | 877 | parameters = pika.ConnectionParameters(host=host, port=port, | ||
2008 | 878 | credentials=credentials, | ||
2009 | 879 | ssl=ssl, | ||
2010 | 880 | connection_attempts=3, | ||
2011 | 881 | retry_delay=5, | ||
2012 | 882 | socket_timeout=1) | ||
2013 | 883 | connection = pika.BlockingConnection(parameters) | ||
2014 | 884 | assert connection.server_properties['product'] == 'RabbitMQ' | ||
2015 | 885 | self.log.debug('Connect OK') | ||
2016 | 886 | return connection | ||
2017 | 887 | except Exception as e: | ||
2018 | 888 | msg = ('amqp connection failed to {}:{} as ' | ||
2019 | 889 | '{} ({})'.format(host, port, username, str(e))) | ||
2020 | 890 | if fatal: | ||
2021 | 891 | amulet.raise_status(amulet.FAIL, msg) | ||
2022 | 892 | else: | ||
2023 | 893 | self.log.warn(msg) | ||
2024 | 894 | return None | ||
2025 | 895 | |||
2026 | 896 | def publish_amqp_message_by_unit(self, sentry_unit, message, | ||
2027 | 897 | queue="test", ssl=False, | ||
2028 | 898 | username="testuser1", | ||
2029 | 899 | password="changeme", | ||
2030 | 900 | port=None): | ||
2031 | 901 | """Publish an amqp message to a rmq juju unit. | ||
2032 | 902 | |||
2033 | 903 | :param sentry_unit: sentry unit pointer | ||
2034 | 904 | :param message: amqp message string | ||
2035 | 905 | :param queue: message queue, default to test | ||
2036 | 906 | :param username: amqp user name, default to testuser1 | ||
2037 | 907 | :param password: amqp user password | ||
2038 | 908 | :param ssl: boolean, default to False | ||
2039 | 909 | :param port: amqp port, use defaults if None | ||
2040 | 910 | :returns: None. Raises exception if publish failed. | ||
2041 | 911 | """ | ||
2042 | 912 | self.log.debug('Publishing message to {} queue:\n{}'.format(queue, | ||
2043 | 913 | message)) | ||
2044 | 914 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
2045 | 915 | port=port, | ||
2046 | 916 | username=username, | ||
2047 | 917 | password=password) | ||
2048 | 918 | |||
2049 | 919 | # NOTE(beisner): extra debug here re: pika hang potential: | ||
2050 | 920 | # https://github.com/pika/pika/issues/297 | ||
2051 | 921 | # https://groups.google.com/forum/#!topic/rabbitmq-users/Ja0iyfF0Szw | ||
2052 | 922 | self.log.debug('Defining channel...') | ||
2053 | 923 | channel = connection.channel() | ||
2054 | 924 | self.log.debug('Declaring queue...') | ||
2055 | 925 | channel.queue_declare(queue=queue, auto_delete=False, durable=True) | ||
2056 | 926 | self.log.debug('Publishing message...') | ||
2057 | 927 | channel.basic_publish(exchange='', routing_key=queue, body=message) | ||
2058 | 928 | self.log.debug('Closing channel...') | ||
2059 | 929 | channel.close() | ||
2060 | 930 | self.log.debug('Closing connection...') | ||
2061 | 931 | connection.close() | ||
2062 | 932 | |||
2063 | 933 | def get_amqp_message_by_unit(self, sentry_unit, queue="test", | ||
2064 | 934 | username="testuser1", | ||
2065 | 935 | password="changeme", | ||
2066 | 936 | ssl=False, port=None): | ||
2067 | 937 | """Get an amqp message from a rmq juju unit. | ||
2068 | 938 | |||
2069 | 939 | :param sentry_unit: sentry unit pointer | ||
2070 | 940 | :param queue: message queue, default to test | ||
2071 | 941 | :param username: amqp user name, default to testuser1 | ||
2072 | 942 | :param password: amqp user password | ||
2073 | 943 | :param ssl: boolean, default to False | ||
2074 | 944 | :param port: amqp port, use defaults if None | ||
2075 | 945 | :returns: amqp message body as string. Raise if get fails. | ||
2076 | 946 | """ | ||
2077 | 947 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
2078 | 948 | port=port, | ||
2079 | 949 | username=username, | ||
2080 | 950 | password=password) | ||
2081 | 951 | channel = connection.channel() | ||
2082 | 952 | method_frame, _, body = channel.basic_get(queue) | ||
2083 | 953 | |||
2084 | 954 | if method_frame: | ||
2085 | 955 | self.log.debug('Retreived message from {} queue:\n{}'.format(queue, | ||
2086 | 956 | body)) | ||
2087 | 957 | channel.basic_ack(method_frame.delivery_tag) | ||
2088 | 958 | channel.close() | ||
2089 | 959 | connection.close() | ||
2090 | 960 | return body | ||
2091 | 961 | else: | ||
2092 | 962 | msg = 'No message retrieved.' | ||
2093 | 963 | amulet.raise_status(amulet.FAIL, msg) | ||
2094 | 605 | 964 | ||
2095 | === modified file 'unit_tests/test_nova_cc_utils.py' | |||
2096 | --- unit_tests/test_nova_cc_utils.py 2015-06-10 15:48:34 +0000 | |||
2097 | +++ unit_tests/test_nova_cc_utils.py 2015-09-22 06:12:36 +0000 | |||
2098 | @@ -416,8 +416,8 @@ | |||
2099 | 416 | @patch.object(utils, 'ssh_known_host_key') | 416 | @patch.object(utils, 'ssh_known_host_key') |
2100 | 417 | @patch('subprocess.check_output') | 417 | @patch('subprocess.check_output') |
2101 | 418 | def test_add_known_host_exists(self, check_output, host_key, rm): | 418 | def test_add_known_host_exists(self, check_output, host_key, rm): |
2104 | 419 | check_output.return_value = 'fookey' | 419 | check_output.return_value = '|1|= fookey' |
2105 | 420 | host_key.return_value = 'fookey' | 420 | host_key.return_value = '|1|= fookey' |
2106 | 421 | with patch_open() as (_open, _file): | 421 | with patch_open() as (_open, _file): |
2107 | 422 | utils.add_known_host('foohost') | 422 | utils.add_known_host('foohost') |
2108 | 423 | self.assertFalse(rm.called) | 423 | self.assertFalse(rm.called) |
2109 | @@ -429,8 +429,8 @@ | |||
2110 | 429 | @patch('subprocess.check_output') | 429 | @patch('subprocess.check_output') |
2111 | 430 | def test_add_known_host_exists_outdated( | 430 | def test_add_known_host_exists_outdated( |
2112 | 431 | self, check_output, host_key, rm, known_hosts): | 431 | self, check_output, host_key, rm, known_hosts): |
2115 | 432 | check_output.return_value = 'fookey' | 432 | check_output.return_value = '|1|= fookey' |
2116 | 433 | host_key.return_value = 'fookey_old' | 433 | host_key.return_value = '|1|= fookey_old' |
2117 | 434 | with patch_open() as (_open, _file): | 434 | with patch_open() as (_open, _file): |
2118 | 435 | utils.add_known_host('foohost', None, None) | 435 | utils.add_known_host('foohost', None, None) |
2119 | 436 | rm.assert_called_with('foohost', None, None) | 436 | rm.assert_called_with('foohost', None, None) |
2120 | @@ -441,13 +441,13 @@ | |||
2121 | 441 | @patch('subprocess.check_output') | 441 | @patch('subprocess.check_output') |
2122 | 442 | def test_add_known_host_exists_added( | 442 | def test_add_known_host_exists_added( |
2123 | 443 | self, check_output, host_key, rm, known_hosts): | 443 | self, check_output, host_key, rm, known_hosts): |
2125 | 444 | check_output.return_value = 'fookey' | 444 | check_output.return_value = '|1|= fookey' |
2126 | 445 | host_key.return_value = None | 445 | host_key.return_value = None |
2127 | 446 | with patch_open() as (_open, _file): | 446 | with patch_open() as (_open, _file): |
2128 | 447 | _file.write = MagicMock() | 447 | _file.write = MagicMock() |
2129 | 448 | utils.add_known_host('foohost') | 448 | utils.add_known_host('foohost') |
2130 | 449 | self.assertFalse(rm.called) | 449 | self.assertFalse(rm.called) |
2132 | 450 | _file.write.assert_called_with('fookey\n') | 450 | _file.write.assert_called_with('|1|= fookey\n') |
2133 | 451 | 451 | ||
2134 | 452 | @patch('__builtin__.open') | 452 | @patch('__builtin__.open') |
2135 | 453 | @patch('os.mkdir') | 453 | @patch('os.mkdir') |
charm_lint_check #7609 nova-cloud- controller- next for xianghui mp267132
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/7609/