Merge lp:~hopem/charms/trusty/glance/lp1499643 into lp:~openstack-charmers-archive/charms/trusty/glance/next
- Trusty Tahr (14.04)
- lp1499643
- Merge into next
Proposed by
Edward Hope-Morley
Status: | Merged |
---|---|
Merged at revision: | 143 |
Proposed branch: | lp:~hopem/charms/trusty/glance/lp1499643 |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/glance/next |
Diff against target: |
1408 lines (+874/-79) 14 files modified
charmhelpers/contrib/network/ip.py (+5/-3) charmhelpers/contrib/openstack/amulet/deployment.py (+23/-9) charmhelpers/contrib/openstack/amulet/utils.py (+359/-0) charmhelpers/contrib/openstack/context.py (+52/-7) charmhelpers/contrib/openstack/templating.py (+30/-2) charmhelpers/contrib/openstack/utils.py (+232/-2) charmhelpers/contrib/storage/linux/ceph.py (+2/-11) charmhelpers/core/hookenv.py (+32/-0) charmhelpers/core/host.py (+32/-16) charmhelpers/core/hugepage.py (+8/-1) charmhelpers/core/strutils.py (+30/-0) tests/charmhelpers/contrib/amulet/deployment.py (+4/-2) tests/charmhelpers/contrib/amulet/utils.py (+56/-16) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+9/-10) |
To merge this branch: | bzr merge lp:~hopem/charms/trusty/glance/lp1499643 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+272409@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #9942 glance-next for hopem mp272409
UNIT FAIL: unit-test failed
UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full unit test output: http://
Build: http://
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #6771 glance-next for hopem mp272409
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Revision history for this message
Liam Young (gnuoy) wrote : | # |
Unrelated Amulet fail. I'll tweak the unit test and merge time. Approved.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'charmhelpers/contrib/network/ip.py' | |||
2 | --- charmhelpers/contrib/network/ip.py 2015-09-03 09:41:01 +0000 | |||
3 | +++ charmhelpers/contrib/network/ip.py 2015-09-25 14:42:27 +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 'charmhelpers/contrib/openstack/amulet/deployment.py' | |||
33 | --- charmhelpers/contrib/openstack/amulet/deployment.py 2015-08-18 17:34:34 +0000 | |||
34 | +++ charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-25 14:42:27 +0000 | |||
35 | @@ -44,20 +44,31 @@ | |||
36 | 44 | Determine if the local branch being tested is derived from its | 44 | Determine if the local branch being tested is derived from its |
37 | 45 | stable or next (dev) branch, and based on this, use the corresonding | 45 | stable or next (dev) branch, and based on this, use the corresonding |
38 | 46 | stable or next branches for the other_services.""" | 46 | stable or next branches for the other_services.""" |
39 | 47 | |||
40 | 48 | # Charms outside the lp:~openstack-charmers namespace | ||
41 | 47 | base_charms = ['mysql', 'mongodb', 'nrpe'] | 49 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
42 | 48 | 50 | ||
43 | 51 | # Force these charms to current series even when using an older series. | ||
44 | 52 | # ie. Use trusty/nrpe even when series is precise, as the P charm | ||
45 | 53 | # does not possess the necessary external master config and hooks. | ||
46 | 54 | force_series_current = ['nrpe'] | ||
47 | 55 | |||
48 | 49 | if self.series in ['precise', 'trusty']: | 56 | if self.series in ['precise', 'trusty']: |
49 | 50 | base_series = self.series | 57 | base_series = self.series |
50 | 51 | else: | 58 | else: |
51 | 52 | base_series = self.current_next | 59 | base_series = self.current_next |
52 | 53 | 60 | ||
55 | 54 | if self.stable: | 61 | for svc in other_services: |
56 | 55 | for svc in other_services: | 62 | if svc['name'] in force_series_current: |
57 | 63 | base_series = self.current_next | ||
58 | 64 | # If a location has been explicitly set, use it | ||
59 | 65 | if svc.get('location'): | ||
60 | 66 | continue | ||
61 | 67 | if self.stable: | ||
62 | 56 | temp = 'lp:charms/{}/{}' | 68 | temp = 'lp:charms/{}/{}' |
63 | 57 | svc['location'] = temp.format(base_series, | 69 | svc['location'] = temp.format(base_series, |
64 | 58 | svc['name']) | 70 | svc['name']) |
67 | 59 | else: | 71 | else: |
66 | 60 | for svc in other_services: | ||
68 | 61 | if svc['name'] in base_charms: | 72 | if svc['name'] in base_charms: |
69 | 62 | temp = 'lp:charms/{}/{}' | 73 | temp = 'lp:charms/{}/{}' |
70 | 63 | svc['location'] = temp.format(base_series, | 74 | svc['location'] = temp.format(base_series, |
71 | @@ -66,6 +77,7 @@ | |||
72 | 66 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' | 77 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
73 | 67 | svc['location'] = temp.format(self.current_next, | 78 | svc['location'] = temp.format(self.current_next, |
74 | 68 | svc['name']) | 79 | svc['name']) |
75 | 80 | |||
76 | 69 | return other_services | 81 | return other_services |
77 | 70 | 82 | ||
78 | 71 | def _add_services(self, this_service, other_services): | 83 | def _add_services(self, this_service, other_services): |
79 | @@ -77,21 +89,23 @@ | |||
80 | 77 | 89 | ||
81 | 78 | services = other_services | 90 | services = other_services |
82 | 79 | services.append(this_service) | 91 | services.append(this_service) |
83 | 92 | |||
84 | 93 | # Charms which should use the source config option | ||
85 | 80 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | 94 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
86 | 81 | 'ceph-osd', 'ceph-radosgw'] | 95 | 'ceph-osd', 'ceph-radosgw'] |
90 | 82 | # Most OpenStack subordinate charms do not expose an origin option | 96 | |
91 | 83 | # as that is controlled by the principle. | 97 | # Charms which can not use openstack-origin, ie. many subordinates |
92 | 84 | ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] | 98 | no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] |
93 | 85 | 99 | ||
94 | 86 | if self.openstack: | 100 | if self.openstack: |
95 | 87 | for svc in services: | 101 | for svc in services: |
97 | 88 | if svc['name'] not in use_source + ignore: | 102 | if svc['name'] not in use_source + no_origin: |
98 | 89 | config = {'openstack-origin': self.openstack} | 103 | config = {'openstack-origin': self.openstack} |
99 | 90 | self.d.configure(svc['name'], config) | 104 | self.d.configure(svc['name'], config) |
100 | 91 | 105 | ||
101 | 92 | if self.source: | 106 | if self.source: |
102 | 93 | for svc in services: | 107 | for svc in services: |
104 | 94 | if svc['name'] in use_source and svc['name'] not in ignore: | 108 | if svc['name'] in use_source and svc['name'] not in no_origin: |
105 | 95 | config = {'source': self.source} | 109 | config = {'source': self.source} |
106 | 96 | self.d.configure(svc['name'], config) | 110 | self.d.configure(svc['name'], config) |
107 | 97 | 111 | ||
108 | 98 | 112 | ||
109 | === modified file 'charmhelpers/contrib/openstack/amulet/utils.py' | |||
110 | --- charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:17:23 +0000 | |||
111 | +++ charmhelpers/contrib/openstack/amulet/utils.py 2015-09-25 14:42:27 +0000 | |||
112 | @@ -27,6 +27,7 @@ | |||
113 | 27 | import heatclient.v1.client as heat_client | 27 | import heatclient.v1.client as heat_client |
114 | 28 | import keystoneclient.v2_0 as keystone_client | 28 | import keystoneclient.v2_0 as keystone_client |
115 | 29 | import novaclient.v1_1.client as nova_client | 29 | import novaclient.v1_1.client as nova_client |
116 | 30 | import pika | ||
117 | 30 | import swiftclient | 31 | import swiftclient |
118 | 31 | 32 | ||
119 | 32 | from charmhelpers.contrib.amulet.utils import ( | 33 | from charmhelpers.contrib.amulet.utils import ( |
120 | @@ -602,3 +603,361 @@ | |||
121 | 602 | self.log.debug('Ceph {} samples (OK): ' | 603 | self.log.debug('Ceph {} samples (OK): ' |
122 | 603 | '{}'.format(sample_type, samples)) | 604 | '{}'.format(sample_type, samples)) |
123 | 604 | return None | 605 | return None |
124 | 606 | |||
125 | 607 | # rabbitmq/amqp specific helpers: | ||
126 | 608 | def add_rmq_test_user(self, sentry_units, | ||
127 | 609 | username="testuser1", password="changeme"): | ||
128 | 610 | """Add a test user via the first rmq juju unit, check connection as | ||
129 | 611 | the new user against all sentry units. | ||
130 | 612 | |||
131 | 613 | :param sentry_units: list of sentry unit pointers | ||
132 | 614 | :param username: amqp user name, default to testuser1 | ||
133 | 615 | :param password: amqp user password | ||
134 | 616 | :returns: None if successful. Raise on error. | ||
135 | 617 | """ | ||
136 | 618 | self.log.debug('Adding rmq user ({})...'.format(username)) | ||
137 | 619 | |||
138 | 620 | # Check that user does not already exist | ||
139 | 621 | cmd_user_list = 'rabbitmqctl list_users' | ||
140 | 622 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
141 | 623 | if username in output: | ||
142 | 624 | self.log.warning('User ({}) already exists, returning ' | ||
143 | 625 | 'gracefully.'.format(username)) | ||
144 | 626 | return | ||
145 | 627 | |||
146 | 628 | perms = '".*" ".*" ".*"' | ||
147 | 629 | cmds = ['rabbitmqctl add_user {} {}'.format(username, password), | ||
148 | 630 | 'rabbitmqctl set_permissions {} {}'.format(username, perms)] | ||
149 | 631 | |||
150 | 632 | # Add user via first unit | ||
151 | 633 | for cmd in cmds: | ||
152 | 634 | output, _ = self.run_cmd_unit(sentry_units[0], cmd) | ||
153 | 635 | |||
154 | 636 | # Check connection against the other sentry_units | ||
155 | 637 | self.log.debug('Checking user connect against units...') | ||
156 | 638 | for sentry_unit in sentry_units: | ||
157 | 639 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=False, | ||
158 | 640 | username=username, | ||
159 | 641 | password=password) | ||
160 | 642 | connection.close() | ||
161 | 643 | |||
162 | 644 | def delete_rmq_test_user(self, sentry_units, username="testuser1"): | ||
163 | 645 | """Delete a rabbitmq user via the first rmq juju unit. | ||
164 | 646 | |||
165 | 647 | :param sentry_units: list of sentry unit pointers | ||
166 | 648 | :param username: amqp user name, default to testuser1 | ||
167 | 649 | :param password: amqp user password | ||
168 | 650 | :returns: None if successful or no such user. | ||
169 | 651 | """ | ||
170 | 652 | self.log.debug('Deleting rmq user ({})...'.format(username)) | ||
171 | 653 | |||
172 | 654 | # Check that the user exists | ||
173 | 655 | cmd_user_list = 'rabbitmqctl list_users' | ||
174 | 656 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_list) | ||
175 | 657 | |||
176 | 658 | if username not in output: | ||
177 | 659 | self.log.warning('User ({}) does not exist, returning ' | ||
178 | 660 | 'gracefully.'.format(username)) | ||
179 | 661 | return | ||
180 | 662 | |||
181 | 663 | # Delete the user | ||
182 | 664 | cmd_user_del = 'rabbitmqctl delete_user {}'.format(username) | ||
183 | 665 | output, _ = self.run_cmd_unit(sentry_units[0], cmd_user_del) | ||
184 | 666 | |||
185 | 667 | def get_rmq_cluster_status(self, sentry_unit): | ||
186 | 668 | """Execute rabbitmq cluster status command on a unit and return | ||
187 | 669 | the full output. | ||
188 | 670 | |||
189 | 671 | :param unit: sentry unit | ||
190 | 672 | :returns: String containing console output of cluster status command | ||
191 | 673 | """ | ||
192 | 674 | cmd = 'rabbitmqctl cluster_status' | ||
193 | 675 | output, _ = self.run_cmd_unit(sentry_unit, cmd) | ||
194 | 676 | self.log.debug('{} cluster_status:\n{}'.format( | ||
195 | 677 | sentry_unit.info['unit_name'], output)) | ||
196 | 678 | return str(output) | ||
197 | 679 | |||
198 | 680 | def get_rmq_cluster_running_nodes(self, sentry_unit): | ||
199 | 681 | """Parse rabbitmqctl cluster_status output string, return list of | ||
200 | 682 | running rabbitmq cluster nodes. | ||
201 | 683 | |||
202 | 684 | :param unit: sentry unit | ||
203 | 685 | :returns: List containing node names of running nodes | ||
204 | 686 | """ | ||
205 | 687 | # NOTE(beisner): rabbitmqctl cluster_status output is not | ||
206 | 688 | # json-parsable, do string chop foo, then json.loads that. | ||
207 | 689 | str_stat = self.get_rmq_cluster_status(sentry_unit) | ||
208 | 690 | if 'running_nodes' in str_stat: | ||
209 | 691 | pos_start = str_stat.find("{running_nodes,") + 15 | ||
210 | 692 | pos_end = str_stat.find("]},", pos_start) + 1 | ||
211 | 693 | str_run_nodes = str_stat[pos_start:pos_end].replace("'", '"') | ||
212 | 694 | run_nodes = json.loads(str_run_nodes) | ||
213 | 695 | return run_nodes | ||
214 | 696 | else: | ||
215 | 697 | return [] | ||
216 | 698 | |||
217 | 699 | def validate_rmq_cluster_running_nodes(self, sentry_units): | ||
218 | 700 | """Check that all rmq unit hostnames are represented in the | ||
219 | 701 | cluster_status output of all units. | ||
220 | 702 | |||
221 | 703 | :param host_names: dict of juju unit names to host names | ||
222 | 704 | :param units: list of sentry unit pointers (all rmq units) | ||
223 | 705 | :returns: None if successful, otherwise return error message | ||
224 | 706 | """ | ||
225 | 707 | host_names = self.get_unit_hostnames(sentry_units) | ||
226 | 708 | errors = [] | ||
227 | 709 | |||
228 | 710 | # Query every unit for cluster_status running nodes | ||
229 | 711 | for query_unit in sentry_units: | ||
230 | 712 | query_unit_name = query_unit.info['unit_name'] | ||
231 | 713 | running_nodes = self.get_rmq_cluster_running_nodes(query_unit) | ||
232 | 714 | |||
233 | 715 | # Confirm that every unit is represented in the queried unit's | ||
234 | 716 | # cluster_status running nodes output. | ||
235 | 717 | for validate_unit in sentry_units: | ||
236 | 718 | val_host_name = host_names[validate_unit.info['unit_name']] | ||
237 | 719 | val_node_name = 'rabbit@{}'.format(val_host_name) | ||
238 | 720 | |||
239 | 721 | if val_node_name not in running_nodes: | ||
240 | 722 | errors.append('Cluster member check failed on {}: {} not ' | ||
241 | 723 | 'in {}\n'.format(query_unit_name, | ||
242 | 724 | val_node_name, | ||
243 | 725 | running_nodes)) | ||
244 | 726 | if errors: | ||
245 | 727 | return ''.join(errors) | ||
246 | 728 | |||
247 | 729 | def rmq_ssl_is_enabled_on_unit(self, sentry_unit, port=None): | ||
248 | 730 | """Check a single juju rmq unit for ssl and port in the config file.""" | ||
249 | 731 | host = sentry_unit.info['public-address'] | ||
250 | 732 | unit_name = sentry_unit.info['unit_name'] | ||
251 | 733 | |||
252 | 734 | conf_file = '/etc/rabbitmq/rabbitmq.config' | ||
253 | 735 | conf_contents = str(self.file_contents_safe(sentry_unit, | ||
254 | 736 | conf_file, max_wait=16)) | ||
255 | 737 | # Checks | ||
256 | 738 | conf_ssl = 'ssl' in conf_contents | ||
257 | 739 | conf_port = str(port) in conf_contents | ||
258 | 740 | |||
259 | 741 | # Port explicitly checked in config | ||
260 | 742 | if port and conf_port and conf_ssl: | ||
261 | 743 | self.log.debug('SSL is enabled @{}:{} ' | ||
262 | 744 | '({})'.format(host, port, unit_name)) | ||
263 | 745 | return True | ||
264 | 746 | elif port and not conf_port and conf_ssl: | ||
265 | 747 | self.log.debug('SSL is enabled @{} but not on port {} ' | ||
266 | 748 | '({})'.format(host, port, unit_name)) | ||
267 | 749 | return False | ||
268 | 750 | # Port not checked (useful when checking that ssl is disabled) | ||
269 | 751 | elif not port and conf_ssl: | ||
270 | 752 | self.log.debug('SSL is enabled @{}:{} ' | ||
271 | 753 | '({})'.format(host, port, unit_name)) | ||
272 | 754 | return True | ||
273 | 755 | elif not port and not conf_ssl: | ||
274 | 756 | self.log.debug('SSL not enabled @{}:{} ' | ||
275 | 757 | '({})'.format(host, port, unit_name)) | ||
276 | 758 | return False | ||
277 | 759 | else: | ||
278 | 760 | msg = ('Unknown condition when checking SSL status @{}:{} ' | ||
279 | 761 | '({})'.format(host, port, unit_name)) | ||
280 | 762 | amulet.raise_status(amulet.FAIL, msg) | ||
281 | 763 | |||
282 | 764 | def validate_rmq_ssl_enabled_units(self, sentry_units, port=None): | ||
283 | 765 | """Check that ssl is enabled on rmq juju sentry units. | ||
284 | 766 | |||
285 | 767 | :param sentry_units: list of all rmq sentry units | ||
286 | 768 | :param port: optional ssl port override to validate | ||
287 | 769 | :returns: None if successful, otherwise return error message | ||
288 | 770 | """ | ||
289 | 771 | for sentry_unit in sentry_units: | ||
290 | 772 | if not self.rmq_ssl_is_enabled_on_unit(sentry_unit, port=port): | ||
291 | 773 | return ('Unexpected condition: ssl is disabled on unit ' | ||
292 | 774 | '({})'.format(sentry_unit.info['unit_name'])) | ||
293 | 775 | return None | ||
294 | 776 | |||
295 | 777 | def validate_rmq_ssl_disabled_units(self, sentry_units): | ||
296 | 778 | """Check that ssl is enabled on listed rmq juju sentry units. | ||
297 | 779 | |||
298 | 780 | :param sentry_units: list of all rmq sentry units | ||
299 | 781 | :returns: True if successful. Raise on error. | ||
300 | 782 | """ | ||
301 | 783 | for sentry_unit in sentry_units: | ||
302 | 784 | if self.rmq_ssl_is_enabled_on_unit(sentry_unit): | ||
303 | 785 | return ('Unexpected condition: ssl is enabled on unit ' | ||
304 | 786 | '({})'.format(sentry_unit.info['unit_name'])) | ||
305 | 787 | return None | ||
306 | 788 | |||
307 | 789 | def configure_rmq_ssl_on(self, sentry_units, deployment, | ||
308 | 790 | port=None, max_wait=60): | ||
309 | 791 | """Turn ssl charm config option on, with optional non-default | ||
310 | 792 | ssl port specification. Confirm that it is enabled on every | ||
311 | 793 | unit. | ||
312 | 794 | |||
313 | 795 | :param sentry_units: list of sentry units | ||
314 | 796 | :param deployment: amulet deployment object pointer | ||
315 | 797 | :param port: amqp port, use defaults if None | ||
316 | 798 | :param max_wait: maximum time to wait in seconds to confirm | ||
317 | 799 | :returns: None if successful. Raise on error. | ||
318 | 800 | """ | ||
319 | 801 | self.log.debug('Setting ssl charm config option: on') | ||
320 | 802 | |||
321 | 803 | # Enable RMQ SSL | ||
322 | 804 | config = {'ssl': 'on'} | ||
323 | 805 | if port: | ||
324 | 806 | config['ssl_port'] = port | ||
325 | 807 | |||
326 | 808 | deployment.configure('rabbitmq-server', config) | ||
327 | 809 | |||
328 | 810 | # Confirm | ||
329 | 811 | tries = 0 | ||
330 | 812 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
331 | 813 | while ret and tries < (max_wait / 4): | ||
332 | 814 | time.sleep(4) | ||
333 | 815 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
334 | 816 | ret = self.validate_rmq_ssl_enabled_units(sentry_units, port=port) | ||
335 | 817 | tries += 1 | ||
336 | 818 | |||
337 | 819 | if ret: | ||
338 | 820 | amulet.raise_status(amulet.FAIL, ret) | ||
339 | 821 | |||
340 | 822 | def configure_rmq_ssl_off(self, sentry_units, deployment, max_wait=60): | ||
341 | 823 | """Turn ssl charm config option off, confirm that it is disabled | ||
342 | 824 | on every unit. | ||
343 | 825 | |||
344 | 826 | :param sentry_units: list of sentry units | ||
345 | 827 | :param deployment: amulet deployment object pointer | ||
346 | 828 | :param max_wait: maximum time to wait in seconds to confirm | ||
347 | 829 | :returns: None if successful. Raise on error. | ||
348 | 830 | """ | ||
349 | 831 | self.log.debug('Setting ssl charm config option: off') | ||
350 | 832 | |||
351 | 833 | # Disable RMQ SSL | ||
352 | 834 | config = {'ssl': 'off'} | ||
353 | 835 | deployment.configure('rabbitmq-server', config) | ||
354 | 836 | |||
355 | 837 | # Confirm | ||
356 | 838 | tries = 0 | ||
357 | 839 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
358 | 840 | while ret and tries < (max_wait / 4): | ||
359 | 841 | time.sleep(4) | ||
360 | 842 | self.log.debug('Attempt {}: {}'.format(tries, ret)) | ||
361 | 843 | ret = self.validate_rmq_ssl_disabled_units(sentry_units) | ||
362 | 844 | tries += 1 | ||
363 | 845 | |||
364 | 846 | if ret: | ||
365 | 847 | amulet.raise_status(amulet.FAIL, ret) | ||
366 | 848 | |||
367 | 849 | def connect_amqp_by_unit(self, sentry_unit, ssl=False, | ||
368 | 850 | port=None, fatal=True, | ||
369 | 851 | username="testuser1", password="changeme"): | ||
370 | 852 | """Establish and return a pika amqp connection to the rabbitmq service | ||
371 | 853 | running on a rmq juju unit. | ||
372 | 854 | |||
373 | 855 | :param sentry_unit: sentry unit pointer | ||
374 | 856 | :param ssl: boolean, default to False | ||
375 | 857 | :param port: amqp port, use defaults if None | ||
376 | 858 | :param fatal: boolean, default to True (raises on connect error) | ||
377 | 859 | :param username: amqp user name, default to testuser1 | ||
378 | 860 | :param password: amqp user password | ||
379 | 861 | :returns: pika amqp connection pointer or None if failed and non-fatal | ||
380 | 862 | """ | ||
381 | 863 | host = sentry_unit.info['public-address'] | ||
382 | 864 | unit_name = sentry_unit.info['unit_name'] | ||
383 | 865 | |||
384 | 866 | # Default port logic if port is not specified | ||
385 | 867 | if ssl and not port: | ||
386 | 868 | port = 5671 | ||
387 | 869 | elif not ssl and not port: | ||
388 | 870 | port = 5672 | ||
389 | 871 | |||
390 | 872 | self.log.debug('Connecting to amqp on {}:{} ({}) as ' | ||
391 | 873 | '{}...'.format(host, port, unit_name, username)) | ||
392 | 874 | |||
393 | 875 | try: | ||
394 | 876 | credentials = pika.PlainCredentials(username, password) | ||
395 | 877 | parameters = pika.ConnectionParameters(host=host, port=port, | ||
396 | 878 | credentials=credentials, | ||
397 | 879 | ssl=ssl, | ||
398 | 880 | connection_attempts=3, | ||
399 | 881 | retry_delay=5, | ||
400 | 882 | socket_timeout=1) | ||
401 | 883 | connection = pika.BlockingConnection(parameters) | ||
402 | 884 | assert connection.server_properties['product'] == 'RabbitMQ' | ||
403 | 885 | self.log.debug('Connect OK') | ||
404 | 886 | return connection | ||
405 | 887 | except Exception as e: | ||
406 | 888 | msg = ('amqp connection failed to {}:{} as ' | ||
407 | 889 | '{} ({})'.format(host, port, username, str(e))) | ||
408 | 890 | if fatal: | ||
409 | 891 | amulet.raise_status(amulet.FAIL, msg) | ||
410 | 892 | else: | ||
411 | 893 | self.log.warn(msg) | ||
412 | 894 | return None | ||
413 | 895 | |||
414 | 896 | def publish_amqp_message_by_unit(self, sentry_unit, message, | ||
415 | 897 | queue="test", ssl=False, | ||
416 | 898 | username="testuser1", | ||
417 | 899 | password="changeme", | ||
418 | 900 | port=None): | ||
419 | 901 | """Publish an amqp message to a rmq juju unit. | ||
420 | 902 | |||
421 | 903 | :param sentry_unit: sentry unit pointer | ||
422 | 904 | :param message: amqp message string | ||
423 | 905 | :param queue: message queue, default to test | ||
424 | 906 | :param username: amqp user name, default to testuser1 | ||
425 | 907 | :param password: amqp user password | ||
426 | 908 | :param ssl: boolean, default to False | ||
427 | 909 | :param port: amqp port, use defaults if None | ||
428 | 910 | :returns: None. Raises exception if publish failed. | ||
429 | 911 | """ | ||
430 | 912 | self.log.debug('Publishing message to {} queue:\n{}'.format(queue, | ||
431 | 913 | message)) | ||
432 | 914 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
433 | 915 | port=port, | ||
434 | 916 | username=username, | ||
435 | 917 | password=password) | ||
436 | 918 | |||
437 | 919 | # NOTE(beisner): extra debug here re: pika hang potential: | ||
438 | 920 | # https://github.com/pika/pika/issues/297 | ||
439 | 921 | # https://groups.google.com/forum/#!topic/rabbitmq-users/Ja0iyfF0Szw | ||
440 | 922 | self.log.debug('Defining channel...') | ||
441 | 923 | channel = connection.channel() | ||
442 | 924 | self.log.debug('Declaring queue...') | ||
443 | 925 | channel.queue_declare(queue=queue, auto_delete=False, durable=True) | ||
444 | 926 | self.log.debug('Publishing message...') | ||
445 | 927 | channel.basic_publish(exchange='', routing_key=queue, body=message) | ||
446 | 928 | self.log.debug('Closing channel...') | ||
447 | 929 | channel.close() | ||
448 | 930 | self.log.debug('Closing connection...') | ||
449 | 931 | connection.close() | ||
450 | 932 | |||
451 | 933 | def get_amqp_message_by_unit(self, sentry_unit, queue="test", | ||
452 | 934 | username="testuser1", | ||
453 | 935 | password="changeme", | ||
454 | 936 | ssl=False, port=None): | ||
455 | 937 | """Get an amqp message from a rmq juju unit. | ||
456 | 938 | |||
457 | 939 | :param sentry_unit: sentry unit pointer | ||
458 | 940 | :param queue: message queue, default to test | ||
459 | 941 | :param username: amqp user name, default to testuser1 | ||
460 | 942 | :param password: amqp user password | ||
461 | 943 | :param ssl: boolean, default to False | ||
462 | 944 | :param port: amqp port, use defaults if None | ||
463 | 945 | :returns: amqp message body as string. Raise if get fails. | ||
464 | 946 | """ | ||
465 | 947 | connection = self.connect_amqp_by_unit(sentry_unit, ssl=ssl, | ||
466 | 948 | port=port, | ||
467 | 949 | username=username, | ||
468 | 950 | password=password) | ||
469 | 951 | channel = connection.channel() | ||
470 | 952 | method_frame, _, body = channel.basic_get(queue) | ||
471 | 953 | |||
472 | 954 | if method_frame: | ||
473 | 955 | self.log.debug('Retreived message from {} queue:\n{}'.format(queue, | ||
474 | 956 | body)) | ||
475 | 957 | channel.basic_ack(method_frame.delivery_tag) | ||
476 | 958 | channel.close() | ||
477 | 959 | connection.close() | ||
478 | 960 | return body | ||
479 | 961 | else: | ||
480 | 962 | msg = 'No message retrieved.' | ||
481 | 963 | amulet.raise_status(amulet.FAIL, msg) | ||
482 | 605 | 964 | ||
483 | === modified file 'charmhelpers/contrib/openstack/context.py' | |||
484 | --- charmhelpers/contrib/openstack/context.py 2015-09-12 06:27:17 +0000 | |||
485 | +++ charmhelpers/contrib/openstack/context.py 2015-09-25 14:42:27 +0000 | |||
486 | @@ -194,10 +194,50 @@ | |||
487 | 194 | class OSContextGenerator(object): | 194 | class OSContextGenerator(object): |
488 | 195 | """Base class for all context generators.""" | 195 | """Base class for all context generators.""" |
489 | 196 | interfaces = [] | 196 | interfaces = [] |
490 | 197 | related = False | ||
491 | 198 | complete = False | ||
492 | 199 | missing_data = [] | ||
493 | 197 | 200 | ||
494 | 198 | def __call__(self): | 201 | def __call__(self): |
495 | 199 | raise NotImplementedError | 202 | raise NotImplementedError |
496 | 200 | 203 | ||
497 | 204 | def context_complete(self, ctxt): | ||
498 | 205 | """Check for missing data for the required context data. | ||
499 | 206 | Set self.missing_data if it exists and return False. | ||
500 | 207 | Set self.complete if no missing data and return True. | ||
501 | 208 | """ | ||
502 | 209 | # Fresh start | ||
503 | 210 | self.complete = False | ||
504 | 211 | self.missing_data = [] | ||
505 | 212 | for k, v in six.iteritems(ctxt): | ||
506 | 213 | if v is None or v == '': | ||
507 | 214 | if k not in self.missing_data: | ||
508 | 215 | self.missing_data.append(k) | ||
509 | 216 | |||
510 | 217 | if self.missing_data: | ||
511 | 218 | self.complete = False | ||
512 | 219 | log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO) | ||
513 | 220 | else: | ||
514 | 221 | self.complete = True | ||
515 | 222 | return self.complete | ||
516 | 223 | |||
517 | 224 | def get_related(self): | ||
518 | 225 | """Check if any of the context interfaces have relation ids. | ||
519 | 226 | Set self.related and return True if one of the interfaces | ||
520 | 227 | has relation ids. | ||
521 | 228 | """ | ||
522 | 229 | # Fresh start | ||
523 | 230 | self.related = False | ||
524 | 231 | try: | ||
525 | 232 | for interface in self.interfaces: | ||
526 | 233 | if relation_ids(interface): | ||
527 | 234 | self.related = True | ||
528 | 235 | return self.related | ||
529 | 236 | except AttributeError as e: | ||
530 | 237 | log("{} {}" | ||
531 | 238 | "".format(self, e), 'INFO') | ||
532 | 239 | return self.related | ||
533 | 240 | |||
534 | 201 | 241 | ||
535 | 202 | class SharedDBContext(OSContextGenerator): | 242 | class SharedDBContext(OSContextGenerator): |
536 | 203 | interfaces = ['shared-db'] | 243 | interfaces = ['shared-db'] |
537 | @@ -213,6 +253,7 @@ | |||
538 | 213 | self.database = database | 253 | self.database = database |
539 | 214 | self.user = user | 254 | self.user = user |
540 | 215 | self.ssl_dir = ssl_dir | 255 | self.ssl_dir = ssl_dir |
541 | 256 | self.rel_name = self.interfaces[0] | ||
542 | 216 | 257 | ||
543 | 217 | def __call__(self): | 258 | def __call__(self): |
544 | 218 | self.database = self.database or config('database') | 259 | self.database = self.database or config('database') |
545 | @@ -246,6 +287,7 @@ | |||
546 | 246 | password_setting = self.relation_prefix + '_password' | 287 | password_setting = self.relation_prefix + '_password' |
547 | 247 | 288 | ||
548 | 248 | for rid in relation_ids(self.interfaces[0]): | 289 | for rid in relation_ids(self.interfaces[0]): |
549 | 290 | self.related = True | ||
550 | 249 | for unit in related_units(rid): | 291 | for unit in related_units(rid): |
551 | 250 | rdata = relation_get(rid=rid, unit=unit) | 292 | rdata = relation_get(rid=rid, unit=unit) |
552 | 251 | host = rdata.get('db_host') | 293 | host = rdata.get('db_host') |
553 | @@ -257,7 +299,7 @@ | |||
554 | 257 | 'database_password': rdata.get(password_setting), | 299 | 'database_password': rdata.get(password_setting), |
555 | 258 | 'database_type': 'mysql' | 300 | 'database_type': 'mysql' |
556 | 259 | } | 301 | } |
558 | 260 | if context_complete(ctxt): | 302 | if self.context_complete(ctxt): |
559 | 261 | db_ssl(rdata, ctxt, self.ssl_dir) | 303 | db_ssl(rdata, ctxt, self.ssl_dir) |
560 | 262 | return ctxt | 304 | return ctxt |
561 | 263 | return {} | 305 | return {} |
562 | @@ -278,6 +320,7 @@ | |||
563 | 278 | 320 | ||
564 | 279 | ctxt = {} | 321 | ctxt = {} |
565 | 280 | for rid in relation_ids(self.interfaces[0]): | 322 | for rid in relation_ids(self.interfaces[0]): |
566 | 323 | self.related = True | ||
567 | 281 | for unit in related_units(rid): | 324 | for unit in related_units(rid): |
568 | 282 | rel_host = relation_get('host', rid=rid, unit=unit) | 325 | rel_host = relation_get('host', rid=rid, unit=unit) |
569 | 283 | rel_user = relation_get('user', rid=rid, unit=unit) | 326 | rel_user = relation_get('user', rid=rid, unit=unit) |
570 | @@ -287,7 +330,7 @@ | |||
571 | 287 | 'database_user': rel_user, | 330 | 'database_user': rel_user, |
572 | 288 | 'database_password': rel_passwd, | 331 | 'database_password': rel_passwd, |
573 | 289 | 'database_type': 'postgresql'} | 332 | 'database_type': 'postgresql'} |
575 | 290 | if context_complete(ctxt): | 333 | if self.context_complete(ctxt): |
576 | 291 | return ctxt | 334 | return ctxt |
577 | 292 | 335 | ||
578 | 293 | return {} | 336 | return {} |
579 | @@ -348,6 +391,7 @@ | |||
580 | 348 | ctxt['signing_dir'] = cachedir | 391 | ctxt['signing_dir'] = cachedir |
581 | 349 | 392 | ||
582 | 350 | for rid in relation_ids(self.rel_name): | 393 | for rid in relation_ids(self.rel_name): |
583 | 394 | self.related = True | ||
584 | 351 | for unit in related_units(rid): | 395 | for unit in related_units(rid): |
585 | 352 | rdata = relation_get(rid=rid, unit=unit) | 396 | rdata = relation_get(rid=rid, unit=unit) |
586 | 353 | serv_host = rdata.get('service_host') | 397 | serv_host = rdata.get('service_host') |
587 | @@ -366,7 +410,7 @@ | |||
588 | 366 | 'service_protocol': svc_protocol, | 410 | 'service_protocol': svc_protocol, |
589 | 367 | 'auth_protocol': auth_protocol}) | 411 | 'auth_protocol': auth_protocol}) |
590 | 368 | 412 | ||
592 | 369 | if context_complete(ctxt): | 413 | if self.context_complete(ctxt): |
593 | 370 | # NOTE(jamespage) this is required for >= icehouse | 414 | # NOTE(jamespage) this is required for >= icehouse |
594 | 371 | # so a missing value just indicates keystone needs | 415 | # so a missing value just indicates keystone needs |
595 | 372 | # upgrading | 416 | # upgrading |
596 | @@ -405,6 +449,7 @@ | |||
597 | 405 | ctxt = {} | 449 | ctxt = {} |
598 | 406 | for rid in relation_ids(self.rel_name): | 450 | for rid in relation_ids(self.rel_name): |
599 | 407 | ha_vip_only = False | 451 | ha_vip_only = False |
600 | 452 | self.related = True | ||
601 | 408 | for unit in related_units(rid): | 453 | for unit in related_units(rid): |
602 | 409 | if relation_get('clustered', rid=rid, unit=unit): | 454 | if relation_get('clustered', rid=rid, unit=unit): |
603 | 410 | ctxt['clustered'] = True | 455 | ctxt['clustered'] = True |
604 | @@ -437,7 +482,7 @@ | |||
605 | 437 | ha_vip_only = relation_get('ha-vip-only', | 482 | ha_vip_only = relation_get('ha-vip-only', |
606 | 438 | rid=rid, unit=unit) is not None | 483 | rid=rid, unit=unit) is not None |
607 | 439 | 484 | ||
609 | 440 | if context_complete(ctxt): | 485 | if self.context_complete(ctxt): |
610 | 441 | if 'rabbit_ssl_ca' in ctxt: | 486 | if 'rabbit_ssl_ca' in ctxt: |
611 | 442 | if not self.ssl_dir: | 487 | if not self.ssl_dir: |
612 | 443 | log("Charm not setup for ssl support but ssl ca " | 488 | log("Charm not setup for ssl support but ssl ca " |
613 | @@ -469,7 +514,7 @@ | |||
614 | 469 | ctxt['oslo_messaging_flags'] = config_flags_parser( | 514 | ctxt['oslo_messaging_flags'] = config_flags_parser( |
615 | 470 | oslo_messaging_flags) | 515 | oslo_messaging_flags) |
616 | 471 | 516 | ||
618 | 472 | if not context_complete(ctxt): | 517 | if not self.complete: |
619 | 473 | return {} | 518 | return {} |
620 | 474 | 519 | ||
621 | 475 | return ctxt | 520 | return ctxt |
622 | @@ -507,7 +552,7 @@ | |||
623 | 507 | if not os.path.isdir('/etc/ceph'): | 552 | if not os.path.isdir('/etc/ceph'): |
624 | 508 | os.mkdir('/etc/ceph') | 553 | os.mkdir('/etc/ceph') |
625 | 509 | 554 | ||
627 | 510 | if not context_complete(ctxt): | 555 | if not self.context_complete(ctxt): |
628 | 511 | return {} | 556 | return {} |
629 | 512 | 557 | ||
630 | 513 | ensure_packages(['ceph-common']) | 558 | ensure_packages(['ceph-common']) |
631 | @@ -1366,6 +1411,6 @@ | |||
632 | 1366 | 'auth_protocol': | 1411 | 'auth_protocol': |
633 | 1367 | rdata.get('auth_protocol') or 'http', | 1412 | rdata.get('auth_protocol') or 'http', |
634 | 1368 | } | 1413 | } |
636 | 1369 | if context_complete(ctxt): | 1414 | if self.context_complete(ctxt): |
637 | 1370 | return ctxt | 1415 | return ctxt |
638 | 1371 | return {} | 1416 | return {} |
639 | 1372 | 1417 | ||
640 | === modified file 'charmhelpers/contrib/openstack/templating.py' | |||
641 | --- charmhelpers/contrib/openstack/templating.py 2015-07-29 10:47:33 +0000 | |||
642 | +++ charmhelpers/contrib/openstack/templating.py 2015-09-25 14:42:27 +0000 | |||
643 | @@ -18,7 +18,7 @@ | |||
644 | 18 | 18 | ||
645 | 19 | import six | 19 | import six |
646 | 20 | 20 | ||
648 | 21 | from charmhelpers.fetch import apt_install | 21 | from charmhelpers.fetch import apt_install, apt_update |
649 | 22 | from charmhelpers.core.hookenv import ( | 22 | from charmhelpers.core.hookenv import ( |
650 | 23 | log, | 23 | log, |
651 | 24 | ERROR, | 24 | ERROR, |
652 | @@ -29,6 +29,7 @@ | |||
653 | 29 | try: | 29 | try: |
654 | 30 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions | 30 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
655 | 31 | except ImportError: | 31 | except ImportError: |
656 | 32 | apt_update(fatal=True) | ||
657 | 32 | apt_install('python-jinja2', fatal=True) | 33 | apt_install('python-jinja2', fatal=True) |
658 | 33 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions | 34 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
659 | 34 | 35 | ||
660 | @@ -112,7 +113,7 @@ | |||
661 | 112 | 113 | ||
662 | 113 | def complete_contexts(self): | 114 | def complete_contexts(self): |
663 | 114 | ''' | 115 | ''' |
665 | 115 | Return a list of interfaces that have atisfied contexts. | 116 | Return a list of interfaces that have satisfied contexts. |
666 | 116 | ''' | 117 | ''' |
667 | 117 | if self._complete_contexts: | 118 | if self._complete_contexts: |
668 | 118 | return self._complete_contexts | 119 | return self._complete_contexts |
669 | @@ -293,3 +294,30 @@ | |||
670 | 293 | [interfaces.extend(i.complete_contexts()) | 294 | [interfaces.extend(i.complete_contexts()) |
671 | 294 | for i in six.itervalues(self.templates)] | 295 | for i in six.itervalues(self.templates)] |
672 | 295 | return interfaces | 296 | return interfaces |
673 | 297 | |||
674 | 298 | def get_incomplete_context_data(self, interfaces): | ||
675 | 299 | ''' | ||
676 | 300 | Return dictionary of relation status of interfaces and any missing | ||
677 | 301 | required context data. Example: | ||
678 | 302 | {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True}, | ||
679 | 303 | 'zeromq-configuration': {'related': False}} | ||
680 | 304 | ''' | ||
681 | 305 | incomplete_context_data = {} | ||
682 | 306 | |||
683 | 307 | for i in six.itervalues(self.templates): | ||
684 | 308 | for context in i.contexts: | ||
685 | 309 | for interface in interfaces: | ||
686 | 310 | related = False | ||
687 | 311 | if interface in context.interfaces: | ||
688 | 312 | related = context.get_related() | ||
689 | 313 | missing_data = context.missing_data | ||
690 | 314 | if missing_data: | ||
691 | 315 | incomplete_context_data[interface] = {'missing_data': missing_data} | ||
692 | 316 | if related: | ||
693 | 317 | if incomplete_context_data.get(interface): | ||
694 | 318 | incomplete_context_data[interface].update({'related': True}) | ||
695 | 319 | else: | ||
696 | 320 | incomplete_context_data[interface] = {'related': True} | ||
697 | 321 | else: | ||
698 | 322 | incomplete_context_data[interface] = {'related': False} | ||
699 | 323 | return incomplete_context_data | ||
700 | 296 | 324 | ||
701 | === modified file 'charmhelpers/contrib/openstack/utils.py' | |||
702 | --- charmhelpers/contrib/openstack/utils.py 2015-09-03 09:41:01 +0000 | |||
703 | +++ charmhelpers/contrib/openstack/utils.py 2015-09-25 14:42:27 +0000 | |||
704 | @@ -25,6 +25,7 @@ | |||
705 | 25 | import re | 25 | import re |
706 | 26 | 26 | ||
707 | 27 | import six | 27 | import six |
708 | 28 | import traceback | ||
709 | 28 | import yaml | 29 | import yaml |
710 | 29 | 30 | ||
711 | 30 | from charmhelpers.contrib.network import ip | 31 | from charmhelpers.contrib.network import ip |
712 | @@ -34,12 +35,16 @@ | |||
713 | 34 | ) | 35 | ) |
714 | 35 | 36 | ||
715 | 36 | from charmhelpers.core.hookenv import ( | 37 | from charmhelpers.core.hookenv import ( |
716 | 38 | action_fail, | ||
717 | 39 | action_set, | ||
718 | 37 | config, | 40 | config, |
719 | 38 | log as juju_log, | 41 | log as juju_log, |
720 | 39 | charm_dir, | 42 | charm_dir, |
721 | 40 | INFO, | 43 | INFO, |
722 | 41 | relation_ids, | 44 | relation_ids, |
724 | 42 | relation_set | 45 | relation_set, |
725 | 46 | status_set, | ||
726 | 47 | hook_name | ||
727 | 43 | ) | 48 | ) |
728 | 44 | 49 | ||
729 | 45 | from charmhelpers.contrib.storage.linux.lvm import ( | 50 | from charmhelpers.contrib.storage.linux.lvm import ( |
730 | @@ -49,7 +54,8 @@ | |||
731 | 49 | ) | 54 | ) |
732 | 50 | 55 | ||
733 | 51 | from charmhelpers.contrib.network.ip import ( | 56 | from charmhelpers.contrib.network.ip import ( |
735 | 52 | get_ipv6_addr | 57 | get_ipv6_addr, |
736 | 58 | is_ipv6, | ||
737 | 53 | ) | 59 | ) |
738 | 54 | 60 | ||
739 | 55 | from charmhelpers.contrib.python.packages import ( | 61 | from charmhelpers.contrib.python.packages import ( |
740 | @@ -114,6 +120,7 @@ | |||
741 | 114 | ('2.2.1', 'kilo'), | 120 | ('2.2.1', 'kilo'), |
742 | 115 | ('2.2.2', 'kilo'), | 121 | ('2.2.2', 'kilo'), |
743 | 116 | ('2.3.0', 'liberty'), | 122 | ('2.3.0', 'liberty'), |
744 | 123 | ('2.4.0', 'liberty'), | ||
745 | 117 | ]) | 124 | ]) |
746 | 118 | 125 | ||
747 | 119 | # >= Liberty version->codename mapping | 126 | # >= Liberty version->codename mapping |
748 | @@ -142,6 +149,9 @@ | |||
749 | 142 | 'glance-common': OrderedDict([ | 149 | 'glance-common': OrderedDict([ |
750 | 143 | ('11.0.0', 'liberty'), | 150 | ('11.0.0', 'liberty'), |
751 | 144 | ]), | 151 | ]), |
752 | 152 | 'openstack-dashboard': OrderedDict([ | ||
753 | 153 | ('8.0.0', 'liberty'), | ||
754 | 154 | ]), | ||
755 | 145 | } | 155 | } |
756 | 146 | 156 | ||
757 | 147 | DEFAULT_LOOPBACK_SIZE = '5G' | 157 | DEFAULT_LOOPBACK_SIZE = '5G' |
758 | @@ -510,6 +520,12 @@ | |||
759 | 510 | relation_prefix=None): | 520 | relation_prefix=None): |
760 | 511 | hosts = get_ipv6_addr(dynamic_only=False) | 521 | hosts = get_ipv6_addr(dynamic_only=False) |
761 | 512 | 522 | ||
762 | 523 | if config('vip'): | ||
763 | 524 | vips = config('vip').split() | ||
764 | 525 | for vip in vips: | ||
765 | 526 | if vip and is_ipv6(vip): | ||
766 | 527 | hosts.append(vip) | ||
767 | 528 | |||
768 | 513 | kwargs = {'database': database, | 529 | kwargs = {'database': database, |
769 | 514 | 'username': database_user, | 530 | 'username': database_user, |
770 | 515 | 'hostname': json.dumps(hosts)} | 531 | 'hostname': json.dumps(hosts)} |
771 | @@ -745,3 +761,217 @@ | |||
772 | 745 | return projects[key] | 761 | return projects[key] |
773 | 746 | 762 | ||
774 | 747 | return None | 763 | return None |
775 | 764 | |||
776 | 765 | |||
777 | 766 | def os_workload_status(configs, required_interfaces, charm_func=None): | ||
778 | 767 | """ | ||
779 | 768 | Decorator to set workload status based on complete contexts | ||
780 | 769 | """ | ||
781 | 770 | def wrap(f): | ||
782 | 771 | @wraps(f) | ||
783 | 772 | def wrapped_f(*args, **kwargs): | ||
784 | 773 | # Run the original function first | ||
785 | 774 | f(*args, **kwargs) | ||
786 | 775 | # Set workload status now that contexts have been | ||
787 | 776 | # acted on | ||
788 | 777 | set_os_workload_status(configs, required_interfaces, charm_func) | ||
789 | 778 | return wrapped_f | ||
790 | 779 | return wrap | ||
791 | 780 | |||
792 | 781 | |||
793 | 782 | def set_os_workload_status(configs, required_interfaces, charm_func=None): | ||
794 | 783 | """ | ||
795 | 784 | Set workload status based on complete contexts. | ||
796 | 785 | status-set missing or incomplete contexts | ||
797 | 786 | and juju-log details of missing required data. | ||
798 | 787 | charm_func is a charm specific function to run checking | ||
799 | 788 | for charm specific requirements such as a VIP setting. | ||
800 | 789 | """ | ||
801 | 790 | incomplete_rel_data = incomplete_relation_data(configs, required_interfaces) | ||
802 | 791 | state = 'active' | ||
803 | 792 | missing_relations = [] | ||
804 | 793 | incomplete_relations = [] | ||
805 | 794 | message = None | ||
806 | 795 | charm_state = None | ||
807 | 796 | charm_message = None | ||
808 | 797 | |||
809 | 798 | for generic_interface in incomplete_rel_data.keys(): | ||
810 | 799 | related_interface = None | ||
811 | 800 | missing_data = {} | ||
812 | 801 | # Related or not? | ||
813 | 802 | for interface in incomplete_rel_data[generic_interface]: | ||
814 | 803 | if incomplete_rel_data[generic_interface][interface].get('related'): | ||
815 | 804 | related_interface = interface | ||
816 | 805 | missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data') | ||
817 | 806 | # No relation ID for the generic_interface | ||
818 | 807 | if not related_interface: | ||
819 | 808 | juju_log("{} relation is missing and must be related for " | ||
820 | 809 | "functionality. ".format(generic_interface), 'WARN') | ||
821 | 810 | state = 'blocked' | ||
822 | 811 | if generic_interface not in missing_relations: | ||
823 | 812 | missing_relations.append(generic_interface) | ||
824 | 813 | else: | ||
825 | 814 | # Relation ID exists but no related unit | ||
826 | 815 | if not missing_data: | ||
827 | 816 | # Edge case relation ID exists but departing | ||
828 | 817 | if ('departed' in hook_name() or 'broken' in hook_name()) \ | ||
829 | 818 | and related_interface in hook_name(): | ||
830 | 819 | state = 'blocked' | ||
831 | 820 | if generic_interface not in missing_relations: | ||
832 | 821 | missing_relations.append(generic_interface) | ||
833 | 822 | juju_log("{} relation's interface, {}, " | ||
834 | 823 | "relationship is departed or broken " | ||
835 | 824 | "and is required for functionality." | ||
836 | 825 | "".format(generic_interface, related_interface), "WARN") | ||
837 | 826 | # Normal case relation ID exists but no related unit | ||
838 | 827 | # (joining) | ||
839 | 828 | else: | ||
840 | 829 | juju_log("{} relations's interface, {}, is related but has " | ||
841 | 830 | "no units in the relation." | ||
842 | 831 | "".format(generic_interface, related_interface), "INFO") | ||
843 | 832 | # Related unit exists and data missing on the relation | ||
844 | 833 | else: | ||
845 | 834 | juju_log("{} relation's interface, {}, is related awaiting " | ||
846 | 835 | "the following data from the relationship: {}. " | ||
847 | 836 | "".format(generic_interface, related_interface, | ||
848 | 837 | ", ".join(missing_data)), "INFO") | ||
849 | 838 | if state != 'blocked': | ||
850 | 839 | state = 'waiting' | ||
851 | 840 | if generic_interface not in incomplete_relations \ | ||
852 | 841 | and generic_interface not in missing_relations: | ||
853 | 842 | incomplete_relations.append(generic_interface) | ||
854 | 843 | |||
855 | 844 | if missing_relations: | ||
856 | 845 | message = "Missing relations: {}".format(", ".join(missing_relations)) | ||
857 | 846 | if incomplete_relations: | ||
858 | 847 | message += "; incomplete relations: {}" \ | ||
859 | 848 | "".format(", ".join(incomplete_relations)) | ||
860 | 849 | state = 'blocked' | ||
861 | 850 | elif incomplete_relations: | ||
862 | 851 | message = "Incomplete relations: {}" \ | ||
863 | 852 | "".format(", ".join(incomplete_relations)) | ||
864 | 853 | state = 'waiting' | ||
865 | 854 | |||
866 | 855 | # Run charm specific checks | ||
867 | 856 | if charm_func: | ||
868 | 857 | charm_state, charm_message = charm_func(configs) | ||
869 | 858 | if charm_state != 'active' and charm_state != 'unknown': | ||
870 | 859 | state = workload_state_compare(state, charm_state) | ||
871 | 860 | if message: | ||
872 | 861 | message = "{} {}".format(message, charm_message) | ||
873 | 862 | else: | ||
874 | 863 | message = charm_message | ||
875 | 864 | |||
876 | 865 | # Set to active if all requirements have been met | ||
877 | 866 | if state == 'active': | ||
878 | 867 | message = "Unit is ready" | ||
879 | 868 | juju_log(message, "INFO") | ||
880 | 869 | |||
881 | 870 | status_set(state, message) | ||
882 | 871 | |||
883 | 872 | |||
884 | 873 | def workload_state_compare(current_workload_state, workload_state): | ||
885 | 874 | """ Return highest priority of two states""" | ||
886 | 875 | hierarchy = {'unknown': -1, | ||
887 | 876 | 'active': 0, | ||
888 | 877 | 'maintenance': 1, | ||
889 | 878 | 'waiting': 2, | ||
890 | 879 | 'blocked': 3, | ||
891 | 880 | } | ||
892 | 881 | |||
893 | 882 | if hierarchy.get(workload_state) is None: | ||
894 | 883 | workload_state = 'unknown' | ||
895 | 884 | if hierarchy.get(current_workload_state) is None: | ||
896 | 885 | current_workload_state = 'unknown' | ||
897 | 886 | |||
898 | 887 | # Set workload_state based on hierarchy of statuses | ||
899 | 888 | if hierarchy.get(current_workload_state) > hierarchy.get(workload_state): | ||
900 | 889 | return current_workload_state | ||
901 | 890 | else: | ||
902 | 891 | return workload_state | ||
903 | 892 | |||
904 | 893 | |||
905 | 894 | def incomplete_relation_data(configs, required_interfaces): | ||
906 | 895 | """ | ||
907 | 896 | Check complete contexts against required_interfaces | ||
908 | 897 | Return dictionary of incomplete relation data. | ||
909 | 898 | |||
910 | 899 | configs is an OSConfigRenderer object with configs registered | ||
911 | 900 | |||
912 | 901 | required_interfaces is a dictionary of required general interfaces | ||
913 | 902 | with dictionary values of possible specific interfaces. | ||
914 | 903 | Example: | ||
915 | 904 | required_interfaces = {'database': ['shared-db', 'pgsql-db']} | ||
916 | 905 | |||
917 | 906 | The interface is said to be satisfied if anyone of the interfaces in the | ||
918 | 907 | list has a complete context. | ||
919 | 908 | |||
920 | 909 | Return dictionary of incomplete or missing required contexts with relation | ||
921 | 910 | status of interfaces and any missing data points. Example: | ||
922 | 911 | {'message': | ||
923 | 912 | {'amqp': {'missing_data': ['rabbitmq_password'], 'related': True}, | ||
924 | 913 | 'zeromq-configuration': {'related': False}}, | ||
925 | 914 | 'identity': | ||
926 | 915 | {'identity-service': {'related': False}}, | ||
927 | 916 | 'database': | ||
928 | 917 | {'pgsql-db': {'related': False}, | ||
929 | 918 | 'shared-db': {'related': True}}} | ||
930 | 919 | """ | ||
931 | 920 | complete_ctxts = configs.complete_contexts() | ||
932 | 921 | incomplete_relations = [] | ||
933 | 922 | for svc_type in required_interfaces.keys(): | ||
934 | 923 | # Avoid duplicates | ||
935 | 924 | found_ctxt = False | ||
936 | 925 | for interface in required_interfaces[svc_type]: | ||
937 | 926 | if interface in complete_ctxts: | ||
938 | 927 | found_ctxt = True | ||
939 | 928 | if not found_ctxt: | ||
940 | 929 | incomplete_relations.append(svc_type) | ||
941 | 930 | incomplete_context_data = {} | ||
942 | 931 | for i in incomplete_relations: | ||
943 | 932 | incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i]) | ||
944 | 933 | return incomplete_context_data | ||
945 | 934 | |||
946 | 935 | |||
947 | 936 | def do_action_openstack_upgrade(package, upgrade_callback, configs): | ||
948 | 937 | """Perform action-managed OpenStack upgrade. | ||
949 | 938 | |||
950 | 939 | Upgrades packages to the configured openstack-origin version and sets | ||
951 | 940 | the corresponding action status as a result. | ||
952 | 941 | |||
953 | 942 | If the charm was installed from source we cannot upgrade it. | ||
954 | 943 | For backwards compatibility a config flag (action-managed-upgrade) must | ||
955 | 944 | be set for this code to run, otherwise a full service level upgrade will | ||
956 | 945 | fire on config-changed. | ||
957 | 946 | |||
958 | 947 | @param package: package name for determining if upgrade available | ||
959 | 948 | @param upgrade_callback: function callback to charm's upgrade function | ||
960 | 949 | @param configs: templating object derived from OSConfigRenderer class | ||
961 | 950 | |||
962 | 951 | @return: True if upgrade successful; False if upgrade failed or skipped | ||
963 | 952 | """ | ||
964 | 953 | ret = False | ||
965 | 954 | |||
966 | 955 | if git_install_requested(): | ||
967 | 956 | action_set({'outcome': 'installed from source, skipped upgrade.'}) | ||
968 | 957 | else: | ||
969 | 958 | if openstack_upgrade_available(package): | ||
970 | 959 | if config('action-managed-upgrade'): | ||
971 | 960 | juju_log('Upgrading OpenStack release') | ||
972 | 961 | |||
973 | 962 | try: | ||
974 | 963 | upgrade_callback(configs=configs) | ||
975 | 964 | action_set({'outcome': 'success, upgrade completed.'}) | ||
976 | 965 | ret = True | ||
977 | 966 | except: | ||
978 | 967 | action_set({'outcome': 'upgrade failed, see traceback.'}) | ||
979 | 968 | action_set({'traceback': traceback.format_exc()}) | ||
980 | 969 | action_fail('do_openstack_upgrade resulted in an ' | ||
981 | 970 | 'unexpected error') | ||
982 | 971 | else: | ||
983 | 972 | action_set({'outcome': 'action-managed-upgrade config is ' | ||
984 | 973 | 'False, skipped upgrade.'}) | ||
985 | 974 | else: | ||
986 | 975 | action_set({'outcome': 'no upgrade available.'}) | ||
987 | 976 | |||
988 | 977 | return ret | ||
989 | 748 | 978 | ||
990 | === modified file 'charmhelpers/contrib/storage/linux/ceph.py' | |||
991 | --- charmhelpers/contrib/storage/linux/ceph.py 2015-09-10 09:30:59 +0000 | |||
992 | +++ charmhelpers/contrib/storage/linux/ceph.py 2015-09-25 14:42:27 +0000 | |||
993 | @@ -59,6 +59,8 @@ | |||
994 | 59 | apt_install, | 59 | apt_install, |
995 | 60 | ) | 60 | ) |
996 | 61 | 61 | ||
997 | 62 | from charmhelpers.core.kernel import modprobe | ||
998 | 63 | |||
999 | 62 | KEYRING = '/etc/ceph/ceph.client.{}.keyring' | 64 | KEYRING = '/etc/ceph/ceph.client.{}.keyring' |
1000 | 63 | KEYFILE = '/etc/ceph/ceph.client.{}.key' | 65 | KEYFILE = '/etc/ceph/ceph.client.{}.key' |
1001 | 64 | 66 | ||
1002 | @@ -291,17 +293,6 @@ | |||
1003 | 291 | os.chown(data_src_dst, uid, gid) | 293 | os.chown(data_src_dst, uid, gid) |
1004 | 292 | 294 | ||
1005 | 293 | 295 | ||
1006 | 294 | # TODO: re-use | ||
1007 | 295 | def modprobe(module): | ||
1008 | 296 | """Load a kernel module and configure for auto-load on reboot.""" | ||
1009 | 297 | log('Loading kernel module', level=INFO) | ||
1010 | 298 | cmd = ['modprobe', module] | ||
1011 | 299 | check_call(cmd) | ||
1012 | 300 | with open('/etc/modules', 'r+') as modules: | ||
1013 | 301 | if module not in modules.read(): | ||
1014 | 302 | modules.write(module) | ||
1015 | 303 | |||
1016 | 304 | |||
1017 | 305 | def copy_files(src, dst, symlinks=False, ignore=None): | 296 | def copy_files(src, dst, symlinks=False, ignore=None): |
1018 | 306 | """Copy files from src to dst.""" | 297 | """Copy files from src to dst.""" |
1019 | 307 | for item in os.listdir(src): | 298 | for item in os.listdir(src): |
1020 | 308 | 299 | ||
1021 | === modified file 'charmhelpers/core/hookenv.py' | |||
1022 | --- charmhelpers/core/hookenv.py 2015-09-03 09:41:01 +0000 | |||
1023 | +++ charmhelpers/core/hookenv.py 2015-09-25 14:42:27 +0000 | |||
1024 | @@ -623,6 +623,38 @@ | |||
1025 | 623 | return unit_get('private-address') | 623 | return unit_get('private-address') |
1026 | 624 | 624 | ||
1027 | 625 | 625 | ||
1028 | 626 | @cached | ||
1029 | 627 | def storage_get(attribute="", storage_id=""): | ||
1030 | 628 | """Get storage attributes""" | ||
1031 | 629 | _args = ['storage-get', '--format=json'] | ||
1032 | 630 | if storage_id: | ||
1033 | 631 | _args.extend(('-s', storage_id)) | ||
1034 | 632 | if attribute: | ||
1035 | 633 | _args.append(attribute) | ||
1036 | 634 | try: | ||
1037 | 635 | return json.loads(subprocess.check_output(_args).decode('UTF-8')) | ||
1038 | 636 | except ValueError: | ||
1039 | 637 | return None | ||
1040 | 638 | |||
1041 | 639 | |||
1042 | 640 | @cached | ||
1043 | 641 | def storage_list(storage_name=""): | ||
1044 | 642 | """List the storage IDs for the unit""" | ||
1045 | 643 | _args = ['storage-list', '--format=json'] | ||
1046 | 644 | if storage_name: | ||
1047 | 645 | _args.append(storage_name) | ||
1048 | 646 | try: | ||
1049 | 647 | return json.loads(subprocess.check_output(_args).decode('UTF-8')) | ||
1050 | 648 | except ValueError: | ||
1051 | 649 | return None | ||
1052 | 650 | except OSError as e: | ||
1053 | 651 | import errno | ||
1054 | 652 | if e.errno == errno.ENOENT: | ||
1055 | 653 | # storage-list does not exist | ||
1056 | 654 | return [] | ||
1057 | 655 | raise | ||
1058 | 656 | |||
1059 | 657 | |||
1060 | 626 | class UnregisteredHookError(Exception): | 658 | class UnregisteredHookError(Exception): |
1061 | 627 | """Raised when an undefined hook is called""" | 659 | """Raised when an undefined hook is called""" |
1062 | 628 | pass | 660 | pass |
1063 | 629 | 661 | ||
1064 | === modified file 'charmhelpers/core/host.py' | |||
1065 | --- charmhelpers/core/host.py 2015-08-19 13:49:22 +0000 | |||
1066 | +++ charmhelpers/core/host.py 2015-09-25 14:42:27 +0000 | |||
1067 | @@ -63,32 +63,48 @@ | |||
1068 | 63 | return service_result | 63 | return service_result |
1069 | 64 | 64 | ||
1070 | 65 | 65 | ||
1072 | 66 | def service_pause(service_name, init_dir=None): | 66 | def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"): |
1073 | 67 | """Pause a system service. | 67 | """Pause a system service. |
1074 | 68 | 68 | ||
1075 | 69 | Stop it, and prevent it from starting again at boot.""" | 69 | Stop it, and prevent it from starting again at boot.""" |
1076 | 70 | if init_dir is None: | ||
1077 | 71 | init_dir = "/etc/init" | ||
1078 | 72 | stopped = service_stop(service_name) | 70 | stopped = service_stop(service_name) |
1084 | 73 | # XXX: Support systemd too | 71 | upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) |
1085 | 74 | override_path = os.path.join( | 72 | sysv_file = os.path.join(initd_dir, service_name) |
1086 | 75 | init_dir, '{}.override'.format(service_name)) | 73 | if os.path.exists(upstart_file): |
1087 | 76 | with open(override_path, 'w') as fh: | 74 | override_path = os.path.join( |
1088 | 77 | fh.write("manual\n") | 75 | init_dir, '{}.override'.format(service_name)) |
1089 | 76 | with open(override_path, 'w') as fh: | ||
1090 | 77 | fh.write("manual\n") | ||
1091 | 78 | elif os.path.exists(sysv_file): | ||
1092 | 79 | subprocess.check_call(["update-rc.d", service_name, "disable"]) | ||
1093 | 80 | else: | ||
1094 | 81 | # XXX: Support SystemD too | ||
1095 | 82 | raise ValueError( | ||
1096 | 83 | "Unable to detect {0} as either Upstart {1} or SysV {2}".format( | ||
1097 | 84 | service_name, upstart_file, sysv_file)) | ||
1098 | 78 | return stopped | 85 | return stopped |
1099 | 79 | 86 | ||
1100 | 80 | 87 | ||
1102 | 81 | def service_resume(service_name, init_dir=None): | 88 | def service_resume(service_name, init_dir="/etc/init", |
1103 | 89 | initd_dir="/etc/init.d"): | ||
1104 | 82 | """Resume a system service. | 90 | """Resume a system service. |
1105 | 83 | 91 | ||
1106 | 84 | Reenable starting again at boot. Start the service""" | 92 | Reenable starting again at boot. Start the service""" |
1114 | 85 | # XXX: Support systemd too | 93 | upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) |
1115 | 86 | if init_dir is None: | 94 | sysv_file = os.path.join(initd_dir, service_name) |
1116 | 87 | init_dir = "/etc/init" | 95 | if os.path.exists(upstart_file): |
1117 | 88 | override_path = os.path.join( | 96 | override_path = os.path.join( |
1118 | 89 | init_dir, '{}.override'.format(service_name)) | 97 | init_dir, '{}.override'.format(service_name)) |
1119 | 90 | if os.path.exists(override_path): | 98 | if os.path.exists(override_path): |
1120 | 91 | os.unlink(override_path) | 99 | os.unlink(override_path) |
1121 | 100 | elif os.path.exists(sysv_file): | ||
1122 | 101 | subprocess.check_call(["update-rc.d", service_name, "enable"]) | ||
1123 | 102 | else: | ||
1124 | 103 | # XXX: Support SystemD too | ||
1125 | 104 | raise ValueError( | ||
1126 | 105 | "Unable to detect {0} as either Upstart {1} or SysV {2}".format( | ||
1127 | 106 | service_name, upstart_file, sysv_file)) | ||
1128 | 107 | |||
1129 | 92 | started = service_start(service_name) | 108 | started = service_start(service_name) |
1130 | 93 | return started | 109 | return started |
1131 | 94 | 110 | ||
1132 | 95 | 111 | ||
1133 | === modified file 'charmhelpers/core/hugepage.py' | |||
1134 | --- charmhelpers/core/hugepage.py 2015-08-19 13:49:22 +0000 | |||
1135 | +++ charmhelpers/core/hugepage.py 2015-09-25 14:42:27 +0000 | |||
1136 | @@ -25,11 +25,13 @@ | |||
1137 | 25 | fstab_mount, | 25 | fstab_mount, |
1138 | 26 | mkdir, | 26 | mkdir, |
1139 | 27 | ) | 27 | ) |
1140 | 28 | from charmhelpers.core.strutils import bytes_from_string | ||
1141 | 29 | from subprocess import check_output | ||
1142 | 28 | 30 | ||
1143 | 29 | 31 | ||
1144 | 30 | def hugepage_support(user, group='hugetlb', nr_hugepages=256, | 32 | def hugepage_support(user, group='hugetlb', nr_hugepages=256, |
1145 | 31 | max_map_count=65536, mnt_point='/run/hugepages/kvm', | 33 | max_map_count=65536, mnt_point='/run/hugepages/kvm', |
1147 | 32 | pagesize='2MB', mount=True): | 34 | pagesize='2MB', mount=True, set_shmmax=False): |
1148 | 33 | """Enable hugepages on system. | 35 | """Enable hugepages on system. |
1149 | 34 | 36 | ||
1150 | 35 | Args: | 37 | Args: |
1151 | @@ -49,6 +51,11 @@ | |||
1152 | 49 | 'vm.max_map_count': max_map_count, | 51 | 'vm.max_map_count': max_map_count, |
1153 | 50 | 'vm.hugetlb_shm_group': gid, | 52 | 'vm.hugetlb_shm_group': gid, |
1154 | 51 | } | 53 | } |
1155 | 54 | if set_shmmax: | ||
1156 | 55 | shmmax_current = int(check_output(['sysctl', '-n', 'kernel.shmmax'])) | ||
1157 | 56 | shmmax_minsize = bytes_from_string(pagesize) * nr_hugepages | ||
1158 | 57 | if shmmax_minsize > shmmax_current: | ||
1159 | 58 | sysctl_settings['kernel.shmmax'] = shmmax_minsize | ||
1160 | 52 | sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf') | 59 | sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf') |
1161 | 53 | mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False) | 60 | mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False) |
1162 | 54 | lfstab = fstab.Fstab() | 61 | lfstab = fstab.Fstab() |
1163 | 55 | 62 | ||
1164 | === modified file 'charmhelpers/core/strutils.py' | |||
1165 | --- charmhelpers/core/strutils.py 2015-04-16 19:53:49 +0000 | |||
1166 | +++ charmhelpers/core/strutils.py 2015-09-25 14:42:27 +0000 | |||
1167 | @@ -18,6 +18,7 @@ | |||
1168 | 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/>. |
1169 | 19 | 19 | ||
1170 | 20 | import six | 20 | import six |
1171 | 21 | import re | ||
1172 | 21 | 22 | ||
1173 | 22 | 23 | ||
1174 | 23 | def bool_from_string(value): | 24 | def bool_from_string(value): |
1175 | @@ -40,3 +41,32 @@ | |||
1176 | 40 | 41 | ||
1177 | 41 | msg = "Unable to interpret string value '%s' as boolean" % (value) | 42 | msg = "Unable to interpret string value '%s' as boolean" % (value) |
1178 | 42 | raise ValueError(msg) | 43 | raise ValueError(msg) |
1179 | 44 | |||
1180 | 45 | |||
1181 | 46 | def bytes_from_string(value): | ||
1182 | 47 | """Interpret human readable string value as bytes. | ||
1183 | 48 | |||
1184 | 49 | Returns int | ||
1185 | 50 | """ | ||
1186 | 51 | BYTE_POWER = { | ||
1187 | 52 | 'K': 1, | ||
1188 | 53 | 'KB': 1, | ||
1189 | 54 | 'M': 2, | ||
1190 | 55 | 'MB': 2, | ||
1191 | 56 | 'G': 3, | ||
1192 | 57 | 'GB': 3, | ||
1193 | 58 | 'T': 4, | ||
1194 | 59 | 'TB': 4, | ||
1195 | 60 | 'P': 5, | ||
1196 | 61 | 'PB': 5, | ||
1197 | 62 | } | ||
1198 | 63 | if isinstance(value, six.string_types): | ||
1199 | 64 | value = six.text_type(value) | ||
1200 | 65 | else: | ||
1201 | 66 | msg = "Unable to interpret non-string value '%s' as boolean" % (value) | ||
1202 | 67 | raise ValueError(msg) | ||
1203 | 68 | matches = re.match("([0-9]+)([a-zA-Z]+)", value) | ||
1204 | 69 | if not matches: | ||
1205 | 70 | msg = "Unable to interpret string value '%s' as bytes" % (value) | ||
1206 | 71 | raise ValueError(msg) | ||
1207 | 72 | return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) | ||
1208 | 43 | 73 | ||
1209 | === modified file 'tests/charmhelpers/contrib/amulet/deployment.py' | |||
1210 | --- tests/charmhelpers/contrib/amulet/deployment.py 2015-03-20 17:15:02 +0000 | |||
1211 | +++ tests/charmhelpers/contrib/amulet/deployment.py 2015-09-25 14:42:27 +0000 | |||
1212 | @@ -51,7 +51,8 @@ | |||
1213 | 51 | if 'units' not in this_service: | 51 | if 'units' not in this_service: |
1214 | 52 | this_service['units'] = 1 | 52 | this_service['units'] = 1 |
1215 | 53 | 53 | ||
1217 | 54 | self.d.add(this_service['name'], units=this_service['units']) | 54 | self.d.add(this_service['name'], units=this_service['units'], |
1218 | 55 | constraints=this_service.get('constraints')) | ||
1219 | 55 | 56 | ||
1220 | 56 | for svc in other_services: | 57 | for svc in other_services: |
1221 | 57 | if 'location' in svc: | 58 | if 'location' in svc: |
1222 | @@ -64,7 +65,8 @@ | |||
1223 | 64 | if 'units' not in svc: | 65 | if 'units' not in svc: |
1224 | 65 | svc['units'] = 1 | 66 | svc['units'] = 1 |
1225 | 66 | 67 | ||
1227 | 67 | self.d.add(svc['name'], charm=branch_location, units=svc['units']) | 68 | self.d.add(svc['name'], charm=branch_location, units=svc['units'], |
1228 | 69 | constraints=svc.get('constraints')) | ||
1229 | 68 | 70 | ||
1230 | 69 | def _add_relations(self, relations): | 71 | def _add_relations(self, relations): |
1231 | 70 | """Add all of the relations for the services.""" | 72 | """Add all of the relations for the services.""" |
1232 | 71 | 73 | ||
1233 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' | |||
1234 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-09-10 09:30:59 +0000 | |||
1235 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-25 14:42:27 +0000 | |||
1236 | @@ -326,7 +326,7 @@ | |||
1237 | 326 | 326 | ||
1238 | 327 | def service_restarted_since(self, sentry_unit, mtime, service, | 327 | def service_restarted_since(self, sentry_unit, mtime, service, |
1239 | 328 | pgrep_full=None, sleep_time=20, | 328 | pgrep_full=None, sleep_time=20, |
1241 | 329 | retry_count=2, retry_sleep_time=30): | 329 | retry_count=30, retry_sleep_time=10): |
1242 | 330 | """Check if service was been started after a given time. | 330 | """Check if service was been started after a given time. |
1243 | 331 | 331 | ||
1244 | 332 | Args: | 332 | Args: |
1245 | @@ -334,8 +334,9 @@ | |||
1246 | 334 | mtime (float): The epoch time to check against | 334 | mtime (float): The epoch time to check against |
1247 | 335 | service (string): service name to look for in process table | 335 | service (string): service name to look for in process table |
1248 | 336 | pgrep_full: [Deprecated] Use full command line search mode with pgrep | 336 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
1251 | 337 | sleep_time (int): Seconds to sleep before looking for process | 337 | sleep_time (int): Initial sleep time (s) before looking for file |
1252 | 338 | retry_count (int): If service is not found, how many times to retry | 338 | retry_sleep_time (int): Time (s) to sleep between retries |
1253 | 339 | retry_count (int): If file is not found, how many times to retry | ||
1254 | 339 | 340 | ||
1255 | 340 | Returns: | 341 | Returns: |
1256 | 341 | bool: True if service found and its start time it newer than mtime, | 342 | bool: True if service found and its start time it newer than mtime, |
1257 | @@ -359,11 +360,12 @@ | |||
1258 | 359 | pgrep_full) | 360 | pgrep_full) |
1259 | 360 | self.log.debug('Attempt {} to get {} proc start time on {} ' | 361 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
1260 | 361 | 'OK'.format(tries, service, unit_name)) | 362 | 'OK'.format(tries, service, unit_name)) |
1262 | 362 | except IOError: | 363 | except IOError as e: |
1263 | 363 | # NOTE(beisner) - race avoidance, proc may not exist yet. | 364 | # NOTE(beisner) - race avoidance, proc may not exist yet. |
1264 | 364 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 | 365 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 |
1265 | 365 | self.log.debug('Attempt {} to get {} proc start time on {} ' | 366 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
1267 | 366 | 'failed'.format(tries, service, unit_name)) | 367 | 'failed\n{}'.format(tries, service, |
1268 | 368 | unit_name, e)) | ||
1269 | 367 | time.sleep(retry_sleep_time) | 369 | time.sleep(retry_sleep_time) |
1270 | 368 | tries += 1 | 370 | tries += 1 |
1271 | 369 | 371 | ||
1272 | @@ -383,35 +385,62 @@ | |||
1273 | 383 | return False | 385 | return False |
1274 | 384 | 386 | ||
1275 | 385 | def config_updated_since(self, sentry_unit, filename, mtime, | 387 | def config_updated_since(self, sentry_unit, filename, mtime, |
1277 | 386 | sleep_time=20): | 388 | sleep_time=20, retry_count=30, |
1278 | 389 | retry_sleep_time=10): | ||
1279 | 387 | """Check if file was modified after a given time. | 390 | """Check if file was modified after a given time. |
1280 | 388 | 391 | ||
1281 | 389 | Args: | 392 | Args: |
1282 | 390 | sentry_unit (sentry): The sentry unit to check the file mtime on | 393 | sentry_unit (sentry): The sentry unit to check the file mtime on |
1283 | 391 | filename (string): The file to check mtime of | 394 | filename (string): The file to check mtime of |
1284 | 392 | mtime (float): The epoch time to check against | 395 | mtime (float): The epoch time to check against |
1286 | 393 | sleep_time (int): Seconds to sleep before looking for process | 396 | sleep_time (int): Initial sleep time (s) before looking for file |
1287 | 397 | retry_sleep_time (int): Time (s) to sleep between retries | ||
1288 | 398 | retry_count (int): If file is not found, how many times to retry | ||
1289 | 394 | 399 | ||
1290 | 395 | Returns: | 400 | Returns: |
1291 | 396 | bool: True if file was modified more recently than mtime, False if | 401 | bool: True if file was modified more recently than mtime, False if |
1293 | 397 | file was modified before mtime, | 402 | file was modified before mtime, or if file not found. |
1294 | 398 | """ | 403 | """ |
1296 | 399 | self.log.debug('Checking %s updated since %s' % (filename, mtime)) | 404 | unit_name = sentry_unit.info['unit_name'] |
1297 | 405 | self.log.debug('Checking that %s updated since %s on ' | ||
1298 | 406 | '%s' % (filename, mtime, unit_name)) | ||
1299 | 400 | time.sleep(sleep_time) | 407 | time.sleep(sleep_time) |
1301 | 401 | file_mtime = self._get_file_mtime(sentry_unit, filename) | 408 | file_mtime = None |
1302 | 409 | tries = 0 | ||
1303 | 410 | while tries <= retry_count and not file_mtime: | ||
1304 | 411 | try: | ||
1305 | 412 | file_mtime = self._get_file_mtime(sentry_unit, filename) | ||
1306 | 413 | self.log.debug('Attempt {} to get {} file mtime on {} ' | ||
1307 | 414 | 'OK'.format(tries, filename, unit_name)) | ||
1308 | 415 | except IOError as e: | ||
1309 | 416 | # NOTE(beisner) - race avoidance, file may not exist yet. | ||
1310 | 417 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 | ||
1311 | 418 | self.log.debug('Attempt {} to get {} file mtime on {} ' | ||
1312 | 419 | 'failed\n{}'.format(tries, filename, | ||
1313 | 420 | unit_name, e)) | ||
1314 | 421 | time.sleep(retry_sleep_time) | ||
1315 | 422 | tries += 1 | ||
1316 | 423 | |||
1317 | 424 | if not file_mtime: | ||
1318 | 425 | self.log.warn('Could not determine file mtime, assuming ' | ||
1319 | 426 | 'file does not exist') | ||
1320 | 427 | return False | ||
1321 | 428 | |||
1322 | 402 | if file_mtime >= mtime: | 429 | if file_mtime >= mtime: |
1323 | 403 | self.log.debug('File mtime is newer than provided mtime ' | 430 | self.log.debug('File mtime is newer than provided mtime ' |
1325 | 404 | '(%s >= %s)' % (file_mtime, mtime)) | 431 | '(%s >= %s) on %s (OK)' % (file_mtime, |
1326 | 432 | mtime, unit_name)) | ||
1327 | 405 | return True | 433 | return True |
1328 | 406 | else: | 434 | else: |
1331 | 407 | self.log.warn('File mtime %s is older than provided mtime %s' | 435 | self.log.warn('File mtime is older than provided mtime' |
1332 | 408 | % (file_mtime, mtime)) | 436 | '(%s < on %s) on %s' % (file_mtime, |
1333 | 437 | mtime, unit_name)) | ||
1334 | 409 | return False | 438 | return False |
1335 | 410 | 439 | ||
1336 | 411 | def validate_service_config_changed(self, sentry_unit, mtime, service, | 440 | def validate_service_config_changed(self, sentry_unit, mtime, service, |
1337 | 412 | filename, pgrep_full=None, | 441 | filename, pgrep_full=None, |
1340 | 413 | sleep_time=20, retry_count=2, | 442 | sleep_time=20, retry_count=30, |
1341 | 414 | retry_sleep_time=30): | 443 | retry_sleep_time=10): |
1342 | 415 | """Check service and file were updated after mtime | 444 | """Check service and file were updated after mtime |
1343 | 416 | 445 | ||
1344 | 417 | Args: | 446 | Args: |
1345 | @@ -456,7 +485,9 @@ | |||
1346 | 456 | sentry_unit, | 485 | sentry_unit, |
1347 | 457 | filename, | 486 | filename, |
1348 | 458 | mtime, | 487 | mtime, |
1350 | 459 | sleep_time=0) | 488 | sleep_time=sleep_time, |
1351 | 489 | retry_count=retry_count, | ||
1352 | 490 | retry_sleep_time=retry_sleep_time) | ||
1353 | 460 | 491 | ||
1354 | 461 | return service_restart and config_update | 492 | return service_restart and config_update |
1355 | 462 | 493 | ||
1356 | @@ -776,3 +807,12 @@ | |||
1357 | 776 | output = _check_output(command, universal_newlines=True) | 807 | output = _check_output(command, universal_newlines=True) |
1358 | 777 | data = json.loads(output) | 808 | data = json.loads(output) |
1359 | 778 | return data.get(u"status") == "completed" | 809 | return data.get(u"status") == "completed" |
1360 | 810 | |||
1361 | 811 | def status_get(self, unit): | ||
1362 | 812 | """Return the current service status of this unit.""" | ||
1363 | 813 | raw_status, return_code = unit.run( | ||
1364 | 814 | "status-get --format=json --include-data") | ||
1365 | 815 | if return_code != 0: | ||
1366 | 816 | return ("unknown", "") | ||
1367 | 817 | status = json.loads(raw_status) | ||
1368 | 818 | return (status["status"], status["message"]) | ||
1369 | 779 | 819 | ||
1370 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
1371 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-10 09:30:59 +0000 | |||
1372 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-25 14:42:27 +0000 | |||
1373 | @@ -58,19 +58,17 @@ | |||
1374 | 58 | else: | 58 | else: |
1375 | 59 | base_series = self.current_next | 59 | base_series = self.current_next |
1376 | 60 | 60 | ||
1382 | 61 | if self.stable: | 61 | for svc in other_services: |
1383 | 62 | for svc in other_services: | 62 | if svc['name'] in force_series_current: |
1384 | 63 | if svc['name'] in force_series_current: | 63 | base_series = self.current_next |
1385 | 64 | base_series = self.current_next | 64 | # If a location has been explicitly set, use it |
1386 | 65 | 65 | if svc.get('location'): | |
1387 | 66 | continue | ||
1388 | 67 | if self.stable: | ||
1389 | 66 | temp = 'lp:charms/{}/{}' | 68 | temp = 'lp:charms/{}/{}' |
1390 | 67 | svc['location'] = temp.format(base_series, | 69 | svc['location'] = temp.format(base_series, |
1391 | 68 | svc['name']) | 70 | svc['name']) |
1397 | 69 | else: | 71 | else: |
1393 | 70 | for svc in other_services: | ||
1394 | 71 | if svc['name'] in force_series_current: | ||
1395 | 72 | base_series = self.current_next | ||
1396 | 73 | |||
1398 | 74 | if svc['name'] in base_charms: | 72 | if svc['name'] in base_charms: |
1399 | 75 | temp = 'lp:charms/{}/{}' | 73 | temp = 'lp:charms/{}/{}' |
1400 | 76 | svc['location'] = temp.format(base_series, | 74 | svc['location'] = temp.format(base_series, |
1401 | @@ -79,6 +77,7 @@ | |||
1402 | 79 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' | 77 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
1403 | 80 | svc['location'] = temp.format(self.current_next, | 78 | svc['location'] = temp.format(self.current_next, |
1404 | 81 | svc['name']) | 79 | svc['name']) |
1405 | 80 | |||
1406 | 82 | return other_services | 81 | return other_services |
1407 | 83 | 82 | ||
1408 | 84 | def _add_services(self, this_service, other_services): | 83 | def _add_services(self, this_service, other_services): |
charm_lint_check #10762 glance-next for hopem mp272409
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/10762/