Merge lp:~james-page/charms/trusty/heat/lp1531102-trunk into lp:~openstack-charmers-archive/charms/trusty/heat/trunk
- Trusty Tahr (14.04)
- lp1531102-trunk
- Merge into trunk
Proposed by
James Page
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 48 | ||||
Proposed branch: | lp:~james-page/charms/trusty/heat/lp1531102-trunk | ||||
Merge into: | lp:~openstack-charmers-archive/charms/trusty/heat/trunk | ||||
Diff against target: |
1024 lines (+506/-61) 13 files modified
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1) hooks/charmhelpers/contrib/openstack/amulet/utils.py (+26/-4) hooks/charmhelpers/contrib/openstack/context.py (+40/-13) hooks/charmhelpers/contrib/openstack/neutron.py (+17/-3) hooks/charmhelpers/contrib/openstack/templates/ceph.conf (+6/-0) hooks/charmhelpers/contrib/openstack/utils.py (+30/-18) hooks/charmhelpers/core/hookenv.py (+32/-0) hooks/charmhelpers/core/host.py (+12/-1) hooks/charmhelpers/core/hugepage.py (+2/-0) hooks/charmhelpers/core/kernel.py (+68/-0) tests/charmhelpers/contrib/amulet/utils.py (+47/-16) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1) tests/charmhelpers/contrib/openstack/amulet/utils.py (+26/-4) |
||||
To merge this branch: | bzr merge lp:~james-page/charms/trusty/heat/lp1531102-trunk | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+281869@code.launchpad.net |
Commit message
Resync helpers
Description of the change
Resync stable helpers to resolve issues with liberty point release detection.
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_lint_check #16745 heat for james-page mp281869
LINT OK: passed
Build: http://
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #8565 heat for james-page mp281869
AMULET OK: passed
Build: http://
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
2 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-22 13:20:40 +0000 | |||
3 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2016-01-07 14:27:41 +0000 | |||
4 | @@ -14,12 +14,18 @@ | |||
5 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
6 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
7 | 16 | 16 | ||
8 | 17 | import logging | ||
9 | 18 | import re | ||
10 | 19 | import sys | ||
11 | 17 | import six | 20 | import six |
12 | 18 | from collections import OrderedDict | 21 | from collections import OrderedDict |
13 | 19 | from charmhelpers.contrib.amulet.deployment import ( | 22 | from charmhelpers.contrib.amulet.deployment import ( |
14 | 20 | AmuletDeployment | 23 | AmuletDeployment |
15 | 21 | ) | 24 | ) |
16 | 22 | 25 | ||
17 | 26 | DEBUG = logging.DEBUG | ||
18 | 27 | ERROR = logging.ERROR | ||
19 | 28 | |||
20 | 23 | 29 | ||
21 | 24 | class OpenStackAmuletDeployment(AmuletDeployment): | 30 | class OpenStackAmuletDeployment(AmuletDeployment): |
22 | 25 | """OpenStack amulet deployment. | 31 | """OpenStack amulet deployment. |
23 | @@ -28,9 +34,12 @@ | |||
24 | 28 | that is specifically for use by OpenStack charms. | 34 | that is specifically for use by OpenStack charms. |
25 | 29 | """ | 35 | """ |
26 | 30 | 36 | ||
28 | 31 | def __init__(self, series=None, openstack=None, source=None, stable=True): | 37 | def __init__(self, series=None, openstack=None, source=None, |
29 | 38 | stable=True, log_level=DEBUG): | ||
30 | 32 | """Initialize the deployment environment.""" | 39 | """Initialize the deployment environment.""" |
31 | 33 | super(OpenStackAmuletDeployment, self).__init__(series) | 40 | super(OpenStackAmuletDeployment, self).__init__(series) |
32 | 41 | self.log = self.get_logger(level=log_level) | ||
33 | 42 | self.log.info('OpenStackAmuletDeployment: init') | ||
34 | 34 | self.openstack = openstack | 43 | self.openstack = openstack |
35 | 35 | self.source = source | 44 | self.source = source |
36 | 36 | self.stable = stable | 45 | self.stable = stable |
37 | @@ -38,6 +47,22 @@ | |||
38 | 38 | # out. | 47 | # out. |
39 | 39 | self.current_next = "trusty" | 48 | self.current_next = "trusty" |
40 | 40 | 49 | ||
41 | 50 | def get_logger(self, name="deployment-logger", level=logging.DEBUG): | ||
42 | 51 | """Get a logger object that will log to stdout.""" | ||
43 | 52 | log = logging | ||
44 | 53 | logger = log.getLogger(name) | ||
45 | 54 | fmt = log.Formatter("%(asctime)s %(funcName)s " | ||
46 | 55 | "%(levelname)s: %(message)s") | ||
47 | 56 | |||
48 | 57 | handler = log.StreamHandler(stream=sys.stdout) | ||
49 | 58 | handler.setLevel(level) | ||
50 | 59 | handler.setFormatter(fmt) | ||
51 | 60 | |||
52 | 61 | logger.addHandler(handler) | ||
53 | 62 | logger.setLevel(level) | ||
54 | 63 | |||
55 | 64 | return logger | ||
56 | 65 | |||
57 | 41 | def _determine_branch_locations(self, other_services): | 66 | def _determine_branch_locations(self, other_services): |
58 | 42 | """Determine the branch locations for the other services. | 67 | """Determine the branch locations for the other services. |
59 | 43 | 68 | ||
60 | @@ -45,6 +70,8 @@ | |||
61 | 45 | stable or next (dev) branch, and based on this, use the corresonding | 70 | stable or next (dev) branch, and based on this, use the corresonding |
62 | 46 | stable or next branches for the other_services.""" | 71 | stable or next branches for the other_services.""" |
63 | 47 | 72 | ||
64 | 73 | self.log.info('OpenStackAmuletDeployment: determine branch locations') | ||
65 | 74 | |||
66 | 48 | # Charms outside the lp:~openstack-charmers namespace | 75 | # Charms outside the lp:~openstack-charmers namespace |
67 | 49 | base_charms = ['mysql', 'mongodb', 'nrpe'] | 76 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
68 | 50 | 77 | ||
69 | @@ -82,6 +109,8 @@ | |||
70 | 82 | 109 | ||
71 | 83 | def _add_services(self, this_service, other_services): | 110 | def _add_services(self, this_service, other_services): |
72 | 84 | """Add services to the deployment and set openstack-origin/source.""" | 111 | """Add services to the deployment and set openstack-origin/source.""" |
73 | 112 | self.log.info('OpenStackAmuletDeployment: adding services') | ||
74 | 113 | |||
75 | 85 | other_services = self._determine_branch_locations(other_services) | 114 | other_services = self._determine_branch_locations(other_services) |
76 | 86 | 115 | ||
77 | 87 | super(OpenStackAmuletDeployment, self)._add_services(this_service, | 116 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
78 | @@ -111,9 +140,79 @@ | |||
79 | 111 | 140 | ||
80 | 112 | def _configure_services(self, configs): | 141 | def _configure_services(self, configs): |
81 | 113 | """Configure all of the services.""" | 142 | """Configure all of the services.""" |
82 | 143 | self.log.info('OpenStackAmuletDeployment: configure services') | ||
83 | 114 | for service, config in six.iteritems(configs): | 144 | for service, config in six.iteritems(configs): |
84 | 115 | self.d.configure(service, config) | 145 | self.d.configure(service, config) |
85 | 116 | 146 | ||
86 | 147 | def _auto_wait_for_status(self, message=None, exclude_services=None, | ||
87 | 148 | include_only=None, timeout=1800): | ||
88 | 149 | """Wait for all units to have a specific extended status, except | ||
89 | 150 | for any defined as excluded. Unless specified via message, any | ||
90 | 151 | status containing any case of 'ready' will be considered a match. | ||
91 | 152 | |||
92 | 153 | Examples of message usage: | ||
93 | 154 | |||
94 | 155 | Wait for all unit status to CONTAIN any case of 'ready' or 'ok': | ||
95 | 156 | message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) | ||
96 | 157 | |||
97 | 158 | Wait for all units to reach this status (exact match): | ||
98 | 159 | message = re.compile('^Unit is ready and clustered$') | ||
99 | 160 | |||
100 | 161 | Wait for all units to reach any one of these (exact match): | ||
101 | 162 | message = re.compile('Unit is ready|OK|Ready') | ||
102 | 163 | |||
103 | 164 | Wait for at least one unit to reach this status (exact match): | ||
104 | 165 | message = {'ready'} | ||
105 | 166 | |||
106 | 167 | See Amulet's sentry.wait_for_messages() for message usage detail. | ||
107 | 168 | https://github.com/juju/amulet/blob/master/amulet/sentry.py | ||
108 | 169 | |||
109 | 170 | :param message: Expected status match | ||
110 | 171 | :param exclude_services: List of juju service names to ignore, | ||
111 | 172 | not to be used in conjuction with include_only. | ||
112 | 173 | :param include_only: List of juju service names to exclusively check, | ||
113 | 174 | not to be used in conjuction with exclude_services. | ||
114 | 175 | :param timeout: Maximum time in seconds to wait for status match | ||
115 | 176 | :returns: None. Raises if timeout is hit. | ||
116 | 177 | """ | ||
117 | 178 | self.log.info('Waiting for extended status on units...') | ||
118 | 179 | |||
119 | 180 | all_services = self.d.services.keys() | ||
120 | 181 | |||
121 | 182 | if exclude_services and include_only: | ||
122 | 183 | raise ValueError('exclude_services can not be used ' | ||
123 | 184 | 'with include_only') | ||
124 | 185 | |||
125 | 186 | if message: | ||
126 | 187 | if isinstance(message, re._pattern_type): | ||
127 | 188 | match = message.pattern | ||
128 | 189 | else: | ||
129 | 190 | match = message | ||
130 | 191 | |||
131 | 192 | self.log.debug('Custom extended status wait match: ' | ||
132 | 193 | '{}'.format(match)) | ||
133 | 194 | else: | ||
134 | 195 | self.log.debug('Default extended status wait match: contains ' | ||
135 | 196 | 'READY (case-insensitive)') | ||
136 | 197 | message = re.compile('.*ready.*', re.IGNORECASE) | ||
137 | 198 | |||
138 | 199 | if exclude_services: | ||
139 | 200 | self.log.debug('Excluding services from extended status match: ' | ||
140 | 201 | '{}'.format(exclude_services)) | ||
141 | 202 | else: | ||
142 | 203 | exclude_services = [] | ||
143 | 204 | |||
144 | 205 | if include_only: | ||
145 | 206 | services = include_only | ||
146 | 207 | else: | ||
147 | 208 | services = list(set(all_services) - set(exclude_services)) | ||
148 | 209 | |||
149 | 210 | self.log.debug('Waiting up to {}s for extended status on services: ' | ||
150 | 211 | '{}'.format(timeout, services)) | ||
151 | 212 | service_messages = {service: message for service in services} | ||
152 | 213 | self.d.sentry.wait_for_messages(service_messages, timeout=timeout) | ||
153 | 214 | self.log.info('OK') | ||
154 | 215 | |||
155 | 117 | def _get_openstack_release(self): | 216 | def _get_openstack_release(self): |
156 | 118 | """Get openstack release. | 217 | """Get openstack release. |
157 | 119 | 218 | ||
158 | 120 | 219 | ||
159 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' | |||
160 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-22 13:20:40 +0000 | |||
161 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2016-01-07 14:27:41 +0000 | |||
162 | @@ -18,6 +18,7 @@ | |||
163 | 18 | import json | 18 | import json |
164 | 19 | import logging | 19 | import logging |
165 | 20 | import os | 20 | import os |
166 | 21 | import re | ||
167 | 21 | import six | 22 | import six |
168 | 22 | import time | 23 | import time |
169 | 23 | import urllib | 24 | import urllib |
170 | @@ -604,7 +605,22 @@ | |||
171 | 604 | '{}'.format(sample_type, samples)) | 605 | '{}'.format(sample_type, samples)) |
172 | 605 | return None | 606 | return None |
173 | 606 | 607 | ||
175 | 607 | # rabbitmq/amqp specific helpers: | 608 | # rabbitmq/amqp specific helpers: |
176 | 609 | |||
177 | 610 | def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): | ||
178 | 611 | """Wait for rmq units extended status to show cluster readiness, | ||
179 | 612 | after an optional initial sleep period. Initial sleep is likely | ||
180 | 613 | necessary to be effective following a config change, as status | ||
181 | 614 | message may not instantly update to non-ready.""" | ||
182 | 615 | |||
183 | 616 | if init_sleep: | ||
184 | 617 | time.sleep(init_sleep) | ||
185 | 618 | |||
186 | 619 | message = re.compile('^Unit is ready and clustered$') | ||
187 | 620 | deployment._auto_wait_for_status(message=message, | ||
188 | 621 | timeout=timeout, | ||
189 | 622 | include_only=['rabbitmq-server']) | ||
190 | 623 | |||
191 | 608 | def add_rmq_test_user(self, sentry_units, | 624 | def add_rmq_test_user(self, sentry_units, |
192 | 609 | username="testuser1", password="changeme"): | 625 | username="testuser1", password="changeme"): |
193 | 610 | """Add a test user via the first rmq juju unit, check connection as | 626 | """Add a test user via the first rmq juju unit, check connection as |
194 | @@ -752,7 +768,7 @@ | |||
195 | 752 | self.log.debug('SSL is enabled @{}:{} ' | 768 | self.log.debug('SSL is enabled @{}:{} ' |
196 | 753 | '({})'.format(host, port, unit_name)) | 769 | '({})'.format(host, port, unit_name)) |
197 | 754 | return True | 770 | return True |
199 | 755 | elif not port and not conf_ssl: | 771 | elif not conf_ssl: |
200 | 756 | self.log.debug('SSL not enabled @{}:{} ' | 772 | self.log.debug('SSL not enabled @{}:{} ' |
201 | 757 | '({})'.format(host, port, unit_name)) | 773 | '({})'.format(host, port, unit_name)) |
202 | 758 | return False | 774 | return False |
203 | @@ -805,7 +821,10 @@ | |||
204 | 805 | if port: | 821 | if port: |
205 | 806 | config['ssl_port'] = port | 822 | config['ssl_port'] = port |
206 | 807 | 823 | ||
208 | 808 | deployment.configure('rabbitmq-server', config) | 824 | deployment.d.configure('rabbitmq-server', config) |
209 | 825 | |||
210 | 826 | # Wait for unit status | ||
211 | 827 | self.rmq_wait_for_cluster(deployment) | ||
212 | 809 | 828 | ||
213 | 810 | # Confirm | 829 | # Confirm |
214 | 811 | tries = 0 | 830 | tries = 0 |
215 | @@ -832,7 +851,10 @@ | |||
216 | 832 | 851 | ||
217 | 833 | # Disable RMQ SSL | 852 | # Disable RMQ SSL |
218 | 834 | config = {'ssl': 'off'} | 853 | config = {'ssl': 'off'} |
220 | 835 | deployment.configure('rabbitmq-server', config) | 854 | deployment.d.configure('rabbitmq-server', config) |
221 | 855 | |||
222 | 856 | # Wait for unit status | ||
223 | 857 | self.rmq_wait_for_cluster(deployment) | ||
224 | 836 | 858 | ||
225 | 837 | # Confirm | 859 | # Confirm |
226 | 838 | tries = 0 | 860 | tries = 0 |
227 | 839 | 861 | ||
228 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' | |||
229 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-10-22 13:20:40 +0000 | |||
230 | +++ hooks/charmhelpers/contrib/openstack/context.py 2016-01-07 14:27:41 +0000 | |||
231 | @@ -14,6 +14,7 @@ | |||
232 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
233 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
234 | 16 | 16 | ||
235 | 17 | import glob | ||
236 | 17 | import json | 18 | import json |
237 | 18 | import os | 19 | import os |
238 | 19 | import re | 20 | import re |
239 | @@ -951,6 +952,19 @@ | |||
240 | 951 | 'config': config} | 952 | 'config': config} |
241 | 952 | return ovs_ctxt | 953 | return ovs_ctxt |
242 | 953 | 954 | ||
243 | 955 | def midonet_ctxt(self): | ||
244 | 956 | driver = neutron_plugin_attribute(self.plugin, 'driver', | ||
245 | 957 | self.network_manager) | ||
246 | 958 | midonet_config = neutron_plugin_attribute(self.plugin, 'config', | ||
247 | 959 | self.network_manager) | ||
248 | 960 | mido_ctxt = {'core_plugin': driver, | ||
249 | 961 | 'neutron_plugin': 'midonet', | ||
250 | 962 | 'neutron_security_groups': self.neutron_security_groups, | ||
251 | 963 | 'local_ip': unit_private_ip(), | ||
252 | 964 | 'config': midonet_config} | ||
253 | 965 | |||
254 | 966 | return mido_ctxt | ||
255 | 967 | |||
256 | 954 | def __call__(self): | 968 | def __call__(self): |
257 | 955 | if self.network_manager not in ['quantum', 'neutron']: | 969 | if self.network_manager not in ['quantum', 'neutron']: |
258 | 956 | return {} | 970 | return {} |
259 | @@ -972,6 +986,8 @@ | |||
260 | 972 | ctxt.update(self.nuage_ctxt()) | 986 | ctxt.update(self.nuage_ctxt()) |
261 | 973 | elif self.plugin == 'plumgrid': | 987 | elif self.plugin == 'plumgrid': |
262 | 974 | ctxt.update(self.pg_ctxt()) | 988 | ctxt.update(self.pg_ctxt()) |
263 | 989 | elif self.plugin == 'midonet': | ||
264 | 990 | ctxt.update(self.midonet_ctxt()) | ||
265 | 975 | 991 | ||
266 | 976 | alchemy_flags = config('neutron-alchemy-flags') | 992 | alchemy_flags = config('neutron-alchemy-flags') |
267 | 977 | if alchemy_flags: | 993 | if alchemy_flags: |
268 | @@ -1104,7 +1120,7 @@ | |||
269 | 1104 | 1120 | ||
270 | 1105 | ctxt = { | 1121 | ctxt = { |
271 | 1106 | ... other context ... | 1122 | ... other context ... |
273 | 1107 | 'subordinate_config': { | 1123 | 'subordinate_configuration': { |
274 | 1108 | 'DEFAULT': { | 1124 | 'DEFAULT': { |
275 | 1109 | 'key1': 'value1', | 1125 | 'key1': 'value1', |
276 | 1110 | }, | 1126 | }, |
277 | @@ -1145,22 +1161,23 @@ | |||
278 | 1145 | try: | 1161 | try: |
279 | 1146 | sub_config = json.loads(sub_config) | 1162 | sub_config = json.loads(sub_config) |
280 | 1147 | except: | 1163 | except: |
283 | 1148 | log('Could not parse JSON from subordinate_config ' | 1164 | log('Could not parse JSON from ' |
284 | 1149 | 'setting from %s' % rid, level=ERROR) | 1165 | 'subordinate_configuration setting from %s' |
285 | 1166 | % rid, level=ERROR) | ||
286 | 1150 | continue | 1167 | continue |
287 | 1151 | 1168 | ||
288 | 1152 | for service in self.services: | 1169 | for service in self.services: |
289 | 1153 | if service not in sub_config: | 1170 | if service not in sub_config: |
293 | 1154 | log('Found subordinate_config on %s but it contained' | 1171 | log('Found subordinate_configuration on %s but it ' |
294 | 1155 | 'nothing for %s service' % (rid, service), | 1172 | 'contained nothing for %s service' |
295 | 1156 | level=INFO) | 1173 | % (rid, service), level=INFO) |
296 | 1157 | continue | 1174 | continue |
297 | 1158 | 1175 | ||
298 | 1159 | sub_config = sub_config[service] | 1176 | sub_config = sub_config[service] |
299 | 1160 | if self.config_file not in sub_config: | 1177 | if self.config_file not in sub_config: |
303 | 1161 | log('Found subordinate_config on %s but it contained' | 1178 | log('Found subordinate_configuration on %s but it ' |
304 | 1162 | 'nothing for %s' % (rid, self.config_file), | 1179 | 'contained nothing for %s' |
305 | 1163 | level=INFO) | 1180 | % (rid, self.config_file), level=INFO) |
306 | 1164 | continue | 1181 | continue |
307 | 1165 | 1182 | ||
308 | 1166 | sub_config = sub_config[self.config_file] | 1183 | sub_config = sub_config[self.config_file] |
309 | @@ -1363,7 +1380,7 @@ | |||
310 | 1363 | normalized.update({port: port for port in resolved | 1380 | normalized.update({port: port for port in resolved |
311 | 1364 | if port in ports}) | 1381 | if port in ports}) |
312 | 1365 | if resolved: | 1382 | if resolved: |
314 | 1366 | return {bridge: normalized[port] for port, bridge in | 1383 | return {normalized[port]: bridge for port, bridge in |
315 | 1367 | six.iteritems(portmap) if port in normalized.keys()} | 1384 | six.iteritems(portmap) if port in normalized.keys()} |
316 | 1368 | 1385 | ||
317 | 1369 | return None | 1386 | return None |
318 | @@ -1374,12 +1391,22 @@ | |||
319 | 1374 | def __call__(self): | 1391 | def __call__(self): |
320 | 1375 | ctxt = {} | 1392 | ctxt = {} |
321 | 1376 | mappings = super(PhyNICMTUContext, self).__call__() | 1393 | mappings = super(PhyNICMTUContext, self).__call__() |
324 | 1377 | if mappings and mappings.values(): | 1394 | if mappings and mappings.keys(): |
325 | 1378 | ports = mappings.values() | 1395 | ports = sorted(mappings.keys()) |
326 | 1379 | napi_settings = NeutronAPIContext()() | 1396 | napi_settings = NeutronAPIContext()() |
327 | 1380 | mtu = napi_settings.get('network_device_mtu') | 1397 | mtu = napi_settings.get('network_device_mtu') |
328 | 1398 | all_ports = set() | ||
329 | 1399 | # If any of ports is a vlan device, its underlying device must have | ||
330 | 1400 | # mtu applied first. | ||
331 | 1401 | for port in ports: | ||
332 | 1402 | for lport in glob.glob("/sys/class/net/%s/lower_*" % port): | ||
333 | 1403 | lport = os.path.basename(lport) | ||
334 | 1404 | all_ports.add(lport.split('_')[1]) | ||
335 | 1405 | |||
336 | 1406 | all_ports = list(all_ports) | ||
337 | 1407 | all_ports.extend(ports) | ||
338 | 1381 | if mtu: | 1408 | if mtu: |
340 | 1382 | ctxt["devs"] = '\\n'.join(ports) | 1409 | ctxt["devs"] = '\\n'.join(all_ports) |
341 | 1383 | ctxt['mtu'] = mtu | 1410 | ctxt['mtu'] = mtu |
342 | 1384 | 1411 | ||
343 | 1385 | return ctxt | 1412 | return ctxt |
344 | 1386 | 1413 | ||
345 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' | |||
346 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2015-10-22 13:20:40 +0000 | |||
347 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2016-01-07 14:27:41 +0000 | |||
348 | @@ -209,6 +209,20 @@ | |||
349 | 209 | 'server_packages': ['neutron-server', | 209 | 'server_packages': ['neutron-server', |
350 | 210 | 'neutron-plugin-plumgrid'], | 210 | 'neutron-plugin-plumgrid'], |
351 | 211 | 'server_services': ['neutron-server'] | 211 | 'server_services': ['neutron-server'] |
352 | 212 | }, | ||
353 | 213 | 'midonet': { | ||
354 | 214 | 'config': '/etc/neutron/plugins/midonet/midonet.ini', | ||
355 | 215 | 'driver': 'midonet.neutron.plugin.MidonetPluginV2', | ||
356 | 216 | 'contexts': [ | ||
357 | 217 | context.SharedDBContext(user=config('neutron-database-user'), | ||
358 | 218 | database=config('neutron-database'), | ||
359 | 219 | relation_prefix='neutron', | ||
360 | 220 | ssl_dir=NEUTRON_CONF_DIR)], | ||
361 | 221 | 'services': [], | ||
362 | 222 | 'packages': [[headers_package()] + determine_dkms_package()], | ||
363 | 223 | 'server_packages': ['neutron-server', | ||
364 | 224 | 'python-neutron-plugin-midonet'], | ||
365 | 225 | 'server_services': ['neutron-server'] | ||
366 | 212 | } | 226 | } |
367 | 213 | } | 227 | } |
368 | 214 | if release >= 'icehouse': | 228 | if release >= 'icehouse': |
369 | @@ -310,10 +324,10 @@ | |||
370 | 310 | def parse_data_port_mappings(mappings, default_bridge='br-data'): | 324 | def parse_data_port_mappings(mappings, default_bridge='br-data'): |
371 | 311 | """Parse data port mappings. | 325 | """Parse data port mappings. |
372 | 312 | 326 | ||
374 | 313 | Mappings must be a space-delimited list of port:bridge mappings. | 327 | Mappings must be a space-delimited list of bridge:port. |
375 | 314 | 328 | ||
378 | 315 | Returns dict of the form {port:bridge} where port may be an mac address or | 329 | Returns dict of the form {port:bridge} where ports may be mac addresses or |
379 | 316 | interface name. | 330 | interface names. |
380 | 317 | """ | 331 | """ |
381 | 318 | 332 | ||
382 | 319 | # NOTE(dosaboy): we use rvalue for key to allow multiple values to be | 333 | # NOTE(dosaboy): we use rvalue for key to allow multiple values to be |
383 | 320 | 334 | ||
384 | === modified file 'hooks/charmhelpers/contrib/openstack/templates/ceph.conf' | |||
385 | --- hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2015-08-10 16:35:15 +0000 | |||
386 | +++ hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2016-01-07 14:27:41 +0000 | |||
387 | @@ -13,3 +13,9 @@ | |||
388 | 13 | err to syslog = {{ use_syslog }} | 13 | err to syslog = {{ use_syslog }} |
389 | 14 | clog to syslog = {{ use_syslog }} | 14 | clog to syslog = {{ use_syslog }} |
390 | 15 | 15 | ||
391 | 16 | [client] | ||
392 | 17 | {% if rbd_client_cache_settings -%} | ||
393 | 18 | {% for key, value in rbd_client_cache_settings.iteritems() -%} | ||
394 | 19 | {{ key }} = {{ value }} | ||
395 | 20 | {% endfor -%} | ||
396 | 21 | {%- endif %} | ||
397 | 16 | \ No newline at end of file | 22 | \ No newline at end of file |
398 | 17 | 23 | ||
399 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' | |||
400 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-10-22 13:20:40 +0000 | |||
401 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2016-01-07 14:27:41 +0000 | |||
402 | @@ -54,7 +54,8 @@ | |||
403 | 54 | ) | 54 | ) |
404 | 55 | 55 | ||
405 | 56 | from charmhelpers.contrib.network.ip import ( | 56 | from charmhelpers.contrib.network.ip import ( |
407 | 57 | get_ipv6_addr | 57 | get_ipv6_addr, |
408 | 58 | is_ipv6, | ||
409 | 58 | ) | 59 | ) |
410 | 59 | 60 | ||
411 | 60 | from charmhelpers.contrib.python.packages import ( | 61 | from charmhelpers.contrib.python.packages import ( |
412 | @@ -120,36 +121,37 @@ | |||
413 | 120 | ('2.2.2', 'kilo'), | 121 | ('2.2.2', 'kilo'), |
414 | 121 | ('2.3.0', 'liberty'), | 122 | ('2.3.0', 'liberty'), |
415 | 122 | ('2.4.0', 'liberty'), | 123 | ('2.4.0', 'liberty'), |
416 | 124 | ('2.5.0', 'liberty'), | ||
417 | 123 | ]) | 125 | ]) |
418 | 124 | 126 | ||
419 | 125 | # >= Liberty version->codename mapping | 127 | # >= Liberty version->codename mapping |
420 | 126 | PACKAGE_CODENAMES = { | 128 | PACKAGE_CODENAMES = { |
421 | 127 | 'nova-common': OrderedDict([ | 129 | 'nova-common': OrderedDict([ |
423 | 128 | ('12.0.0', 'liberty'), | 130 | ('12.0', 'liberty'), |
424 | 129 | ]), | 131 | ]), |
425 | 130 | 'neutron-common': OrderedDict([ | 132 | 'neutron-common': OrderedDict([ |
427 | 131 | ('7.0.0', 'liberty'), | 133 | ('7.0', 'liberty'), |
428 | 132 | ]), | 134 | ]), |
429 | 133 | 'cinder-common': OrderedDict([ | 135 | 'cinder-common': OrderedDict([ |
431 | 134 | ('7.0.0', 'liberty'), | 136 | ('7.0', 'liberty'), |
432 | 135 | ]), | 137 | ]), |
433 | 136 | 'keystone': OrderedDict([ | 138 | 'keystone': OrderedDict([ |
435 | 137 | ('8.0.0', 'liberty'), | 139 | ('8.0', 'liberty'), |
436 | 138 | ]), | 140 | ]), |
437 | 139 | 'horizon-common': OrderedDict([ | 141 | 'horizon-common': OrderedDict([ |
439 | 140 | ('8.0.0', 'liberty'), | 142 | ('8.0', 'liberty'), |
440 | 141 | ]), | 143 | ]), |
441 | 142 | 'ceilometer-common': OrderedDict([ | 144 | 'ceilometer-common': OrderedDict([ |
443 | 143 | ('5.0.0', 'liberty'), | 145 | ('5.0', 'liberty'), |
444 | 144 | ]), | 146 | ]), |
445 | 145 | 'heat-common': OrderedDict([ | 147 | 'heat-common': OrderedDict([ |
447 | 146 | ('5.0.0', 'liberty'), | 148 | ('5.0', 'liberty'), |
448 | 147 | ]), | 149 | ]), |
449 | 148 | 'glance-common': OrderedDict([ | 150 | 'glance-common': OrderedDict([ |
451 | 149 | ('11.0.0', 'liberty'), | 151 | ('11.0', 'liberty'), |
452 | 150 | ]), | 152 | ]), |
453 | 151 | 'openstack-dashboard': OrderedDict([ | 153 | 'openstack-dashboard': OrderedDict([ |
455 | 152 | ('8.0.0', 'liberty'), | 154 | ('8.0', 'liberty'), |
456 | 153 | ]), | 155 | ]), |
457 | 154 | } | 156 | } |
458 | 155 | 157 | ||
459 | @@ -236,7 +238,14 @@ | |||
460 | 236 | error_out(e) | 238 | error_out(e) |
461 | 237 | 239 | ||
462 | 238 | vers = apt.upstream_version(pkg.current_ver.ver_str) | 240 | vers = apt.upstream_version(pkg.current_ver.ver_str) |
464 | 239 | match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) | 241 | if 'swift' in pkg.name: |
465 | 242 | # Fully x.y.z match for swift versions | ||
466 | 243 | match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) | ||
467 | 244 | else: | ||
468 | 245 | # x.y match only for 20XX.X | ||
469 | 246 | # and ignore patch level for other packages | ||
470 | 247 | match = re.match('^(\d+)\.(\d+)', vers) | ||
471 | 248 | |||
472 | 240 | if match: | 249 | if match: |
473 | 241 | vers = match.group(0) | 250 | vers = match.group(0) |
474 | 242 | 251 | ||
475 | @@ -248,13 +257,8 @@ | |||
476 | 248 | # < Liberty co-ordinated project versions | 257 | # < Liberty co-ordinated project versions |
477 | 249 | try: | 258 | try: |
478 | 250 | if 'swift' in pkg.name: | 259 | if 'swift' in pkg.name: |
484 | 251 | swift_vers = vers[:5] | 260 | return SWIFT_CODENAMES[vers] |
480 | 252 | if swift_vers not in SWIFT_CODENAMES: | ||
481 | 253 | # Deal with 1.10.0 upward | ||
482 | 254 | swift_vers = vers[:6] | ||
483 | 255 | return SWIFT_CODENAMES[swift_vers] | ||
485 | 256 | else: | 261 | else: |
486 | 257 | vers = vers[:6] | ||
487 | 258 | return OPENSTACK_CODENAMES[vers] | 262 | return OPENSTACK_CODENAMES[vers] |
488 | 259 | except KeyError: | 263 | except KeyError: |
489 | 260 | if not fatal: | 264 | if not fatal: |
490 | @@ -519,6 +523,12 @@ | |||
491 | 519 | relation_prefix=None): | 523 | relation_prefix=None): |
492 | 520 | hosts = get_ipv6_addr(dynamic_only=False) | 524 | hosts = get_ipv6_addr(dynamic_only=False) |
493 | 521 | 525 | ||
494 | 526 | if config('vip'): | ||
495 | 527 | vips = config('vip').split() | ||
496 | 528 | for vip in vips: | ||
497 | 529 | if vip and is_ipv6(vip): | ||
498 | 530 | hosts.append(vip) | ||
499 | 531 | |||
500 | 522 | kwargs = {'database': database, | 532 | kwargs = {'database': database, |
501 | 523 | 'username': database_user, | 533 | 'username': database_user, |
502 | 524 | 'hostname': json.dumps(hosts)} | 534 | 'hostname': json.dumps(hosts)} |
503 | @@ -851,7 +861,9 @@ | |||
504 | 851 | if charm_state != 'active' and charm_state != 'unknown': | 861 | if charm_state != 'active' and charm_state != 'unknown': |
505 | 852 | state = workload_state_compare(state, charm_state) | 862 | state = workload_state_compare(state, charm_state) |
506 | 853 | if message: | 863 | if message: |
508 | 854 | message = "{} {}".format(message, charm_message) | 864 | charm_message = charm_message.replace("Incomplete relations: ", |
509 | 865 | "") | ||
510 | 866 | message = "{}, {}".format(message, charm_message) | ||
511 | 855 | else: | 867 | else: |
512 | 856 | message = charm_message | 868 | message = charm_message |
513 | 857 | 869 | ||
514 | 858 | 870 | ||
515 | === modified file 'hooks/charmhelpers/core/hookenv.py' | |||
516 | --- hooks/charmhelpers/core/hookenv.py 2015-10-22 13:20:40 +0000 | |||
517 | +++ hooks/charmhelpers/core/hookenv.py 2016-01-07 14:27:41 +0000 | |||
518 | @@ -623,6 +623,38 @@ | |||
519 | 623 | return unit_get('private-address') | 623 | return unit_get('private-address') |
520 | 624 | 624 | ||
521 | 625 | 625 | ||
522 | 626 | @cached | ||
523 | 627 | def storage_get(attribute="", storage_id=""): | ||
524 | 628 | """Get storage attributes""" | ||
525 | 629 | _args = ['storage-get', '--format=json'] | ||
526 | 630 | if storage_id: | ||
527 | 631 | _args.extend(('-s', storage_id)) | ||
528 | 632 | if attribute: | ||
529 | 633 | _args.append(attribute) | ||
530 | 634 | try: | ||
531 | 635 | return json.loads(subprocess.check_output(_args).decode('UTF-8')) | ||
532 | 636 | except ValueError: | ||
533 | 637 | return None | ||
534 | 638 | |||
535 | 639 | |||
536 | 640 | @cached | ||
537 | 641 | def storage_list(storage_name=""): | ||
538 | 642 | """List the storage IDs for the unit""" | ||
539 | 643 | _args = ['storage-list', '--format=json'] | ||
540 | 644 | if storage_name: | ||
541 | 645 | _args.append(storage_name) | ||
542 | 646 | try: | ||
543 | 647 | return json.loads(subprocess.check_output(_args).decode('UTF-8')) | ||
544 | 648 | except ValueError: | ||
545 | 649 | return None | ||
546 | 650 | except OSError as e: | ||
547 | 651 | import errno | ||
548 | 652 | if e.errno == errno.ENOENT: | ||
549 | 653 | # storage-list does not exist | ||
550 | 654 | return [] | ||
551 | 655 | raise | ||
552 | 656 | |||
553 | 657 | |||
554 | 626 | class UnregisteredHookError(Exception): | 658 | class UnregisteredHookError(Exception): |
555 | 627 | """Raised when an undefined hook is called""" | 659 | """Raised when an undefined hook is called""" |
556 | 628 | pass | 660 | pass |
557 | 629 | 661 | ||
558 | === modified file 'hooks/charmhelpers/core/host.py' | |||
559 | --- hooks/charmhelpers/core/host.py 2015-10-22 13:20:40 +0000 | |||
560 | +++ hooks/charmhelpers/core/host.py 2016-01-07 14:27:41 +0000 | |||
561 | @@ -566,7 +566,14 @@ | |||
562 | 566 | os.chdir(cur) | 566 | os.chdir(cur) |
563 | 567 | 567 | ||
564 | 568 | 568 | ||
566 | 569 | def chownr(path, owner, group, follow_links=True): | 569 | def chownr(path, owner, group, follow_links=True, chowntopdir=False): |
567 | 570 | """ | ||
568 | 571 | Recursively change user and group ownership of files and directories | ||
569 | 572 | in given path. Doesn't chown path itself by default, only its children. | ||
570 | 573 | |||
571 | 574 | :param bool follow_links: Also Chown links if True | ||
572 | 575 | :param bool chowntopdir: Also chown path itself if True | ||
573 | 576 | """ | ||
574 | 570 | uid = pwd.getpwnam(owner).pw_uid | 577 | uid = pwd.getpwnam(owner).pw_uid |
575 | 571 | gid = grp.getgrnam(group).gr_gid | 578 | gid = grp.getgrnam(group).gr_gid |
576 | 572 | if follow_links: | 579 | if follow_links: |
577 | @@ -574,6 +581,10 @@ | |||
578 | 574 | else: | 581 | else: |
579 | 575 | chown = os.lchown | 582 | chown = os.lchown |
580 | 576 | 583 | ||
581 | 584 | if chowntopdir: | ||
582 | 585 | broken_symlink = os.path.lexists(path) and not os.path.exists(path) | ||
583 | 586 | if not broken_symlink: | ||
584 | 587 | chown(path, uid, gid) | ||
585 | 577 | for root, dirs, files in os.walk(path): | 588 | for root, dirs, files in os.walk(path): |
586 | 578 | for name in dirs + files: | 589 | for name in dirs + files: |
587 | 579 | full = os.path.join(root, name) | 590 | full = os.path.join(root, name) |
588 | 580 | 591 | ||
589 | === modified file 'hooks/charmhelpers/core/hugepage.py' | |||
590 | --- hooks/charmhelpers/core/hugepage.py 2015-10-22 13:20:40 +0000 | |||
591 | +++ hooks/charmhelpers/core/hugepage.py 2016-01-07 14:27:41 +0000 | |||
592 | @@ -46,6 +46,8 @@ | |||
593 | 46 | group_info = add_group(group) | 46 | group_info = add_group(group) |
594 | 47 | gid = group_info.gr_gid | 47 | gid = group_info.gr_gid |
595 | 48 | add_user_to_group(user, group) | 48 | add_user_to_group(user, group) |
596 | 49 | if max_map_count < 2 * nr_hugepages: | ||
597 | 50 | max_map_count = 2 * nr_hugepages | ||
598 | 49 | sysctl_settings = { | 51 | sysctl_settings = { |
599 | 50 | 'vm.nr_hugepages': nr_hugepages, | 52 | 'vm.nr_hugepages': nr_hugepages, |
600 | 51 | 'vm.max_map_count': max_map_count, | 53 | 'vm.max_map_count': max_map_count, |
601 | 52 | 54 | ||
602 | === added file 'hooks/charmhelpers/core/kernel.py' | |||
603 | --- hooks/charmhelpers/core/kernel.py 1970-01-01 00:00:00 +0000 | |||
604 | +++ hooks/charmhelpers/core/kernel.py 2016-01-07 14:27:41 +0000 | |||
605 | @@ -0,0 +1,68 @@ | |||
606 | 1 | #!/usr/bin/env python | ||
607 | 2 | # -*- coding: utf-8 -*- | ||
608 | 3 | |||
609 | 4 | # Copyright 2014-2015 Canonical Limited. | ||
610 | 5 | # | ||
611 | 6 | # This file is part of charm-helpers. | ||
612 | 7 | # | ||
613 | 8 | # charm-helpers is free software: you can redistribute it and/or modify | ||
614 | 9 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
615 | 10 | # published by the Free Software Foundation. | ||
616 | 11 | # | ||
617 | 12 | # charm-helpers is distributed in the hope that it will be useful, | ||
618 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
619 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
620 | 15 | # GNU Lesser General Public License for more details. | ||
621 | 16 | # | ||
622 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
623 | 18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
624 | 19 | |||
625 | 20 | __author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" | ||
626 | 21 | |||
627 | 22 | from charmhelpers.core.hookenv import ( | ||
628 | 23 | log, | ||
629 | 24 | INFO | ||
630 | 25 | ) | ||
631 | 26 | |||
632 | 27 | from subprocess import check_call, check_output | ||
633 | 28 | import re | ||
634 | 29 | |||
635 | 30 | |||
636 | 31 | def modprobe(module, persist=True): | ||
637 | 32 | """Load a kernel module and configure for auto-load on reboot.""" | ||
638 | 33 | cmd = ['modprobe', module] | ||
639 | 34 | |||
640 | 35 | log('Loading kernel module %s' % module, level=INFO) | ||
641 | 36 | |||
642 | 37 | check_call(cmd) | ||
643 | 38 | if persist: | ||
644 | 39 | with open('/etc/modules', 'r+') as modules: | ||
645 | 40 | if module not in modules.read(): | ||
646 | 41 | modules.write(module) | ||
647 | 42 | |||
648 | 43 | |||
649 | 44 | def rmmod(module, force=False): | ||
650 | 45 | """Remove a module from the linux kernel""" | ||
651 | 46 | cmd = ['rmmod'] | ||
652 | 47 | if force: | ||
653 | 48 | cmd.append('-f') | ||
654 | 49 | cmd.append(module) | ||
655 | 50 | log('Removing kernel module %s' % module, level=INFO) | ||
656 | 51 | return check_call(cmd) | ||
657 | 52 | |||
658 | 53 | |||
659 | 54 | def lsmod(): | ||
660 | 55 | """Shows what kernel modules are currently loaded""" | ||
661 | 56 | return check_output(['lsmod'], | ||
662 | 57 | universal_newlines=True) | ||
663 | 58 | |||
664 | 59 | |||
665 | 60 | def is_module_loaded(module): | ||
666 | 61 | """Checks if a kernel module is already loaded""" | ||
667 | 62 | matches = re.findall('^%s[ ]+' % module, lsmod(), re.M) | ||
668 | 63 | return len(matches) > 0 | ||
669 | 64 | |||
670 | 65 | |||
671 | 66 | def update_initramfs(version='all'): | ||
672 | 67 | """Updates an initramfs image""" | ||
673 | 68 | return check_call(["update-initramfs", "-k", version, "-u"]) | ||
674 | 0 | 69 | ||
675 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' | |||
676 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-10-22 13:20:40 +0000 | |||
677 | +++ tests/charmhelpers/contrib/amulet/utils.py 2016-01-07 14:27:41 +0000 | |||
678 | @@ -326,7 +326,7 @@ | |||
679 | 326 | 326 | ||
680 | 327 | def service_restarted_since(self, sentry_unit, mtime, service, | 327 | def service_restarted_since(self, sentry_unit, mtime, service, |
681 | 328 | pgrep_full=None, sleep_time=20, | 328 | pgrep_full=None, sleep_time=20, |
683 | 329 | retry_count=2, retry_sleep_time=30): | 329 | retry_count=30, retry_sleep_time=10): |
684 | 330 | """Check if service was been started after a given time. | 330 | """Check if service was been started after a given time. |
685 | 331 | 331 | ||
686 | 332 | Args: | 332 | Args: |
687 | @@ -334,8 +334,9 @@ | |||
688 | 334 | mtime (float): The epoch time to check against | 334 | mtime (float): The epoch time to check against |
689 | 335 | service (string): service name to look for in process table | 335 | service (string): service name to look for in process table |
690 | 336 | pgrep_full: [Deprecated] Use full command line search mode with pgrep | 336 | pgrep_full: [Deprecated] Use full command line search mode with pgrep |
693 | 337 | sleep_time (int): Seconds to sleep before looking for process | 337 | sleep_time (int): Initial sleep time (s) before looking for file |
694 | 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 |
695 | 339 | retry_count (int): If file is not found, how many times to retry | ||
696 | 339 | 340 | ||
697 | 340 | Returns: | 341 | Returns: |
698 | 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, |
699 | @@ -359,11 +360,12 @@ | |||
700 | 359 | pgrep_full) | 360 | pgrep_full) |
701 | 360 | self.log.debug('Attempt {} to get {} proc start time on {} ' | 361 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
702 | 361 | 'OK'.format(tries, service, unit_name)) | 362 | 'OK'.format(tries, service, unit_name)) |
704 | 362 | except IOError: | 363 | except IOError as e: |
705 | 363 | # NOTE(beisner) - race avoidance, proc may not exist yet. | 364 | # NOTE(beisner) - race avoidance, proc may not exist yet. |
706 | 364 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 | 365 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 |
707 | 365 | self.log.debug('Attempt {} to get {} proc start time on {} ' | 366 | self.log.debug('Attempt {} to get {} proc start time on {} ' |
709 | 366 | 'failed'.format(tries, service, unit_name)) | 367 | 'failed\n{}'.format(tries, service, |
710 | 368 | unit_name, e)) | ||
711 | 367 | time.sleep(retry_sleep_time) | 369 | time.sleep(retry_sleep_time) |
712 | 368 | tries += 1 | 370 | tries += 1 |
713 | 369 | 371 | ||
714 | @@ -383,35 +385,62 @@ | |||
715 | 383 | return False | 385 | return False |
716 | 384 | 386 | ||
717 | 385 | def config_updated_since(self, sentry_unit, filename, mtime, | 387 | def config_updated_since(self, sentry_unit, filename, mtime, |
719 | 386 | sleep_time=20): | 388 | sleep_time=20, retry_count=30, |
720 | 389 | retry_sleep_time=10): | ||
721 | 387 | """Check if file was modified after a given time. | 390 | """Check if file was modified after a given time. |
722 | 388 | 391 | ||
723 | 389 | Args: | 392 | Args: |
724 | 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 |
725 | 391 | filename (string): The file to check mtime of | 394 | filename (string): The file to check mtime of |
726 | 392 | mtime (float): The epoch time to check against | 395 | mtime (float): The epoch time to check against |
728 | 393 | sleep_time (int): Seconds to sleep before looking for process | 396 | sleep_time (int): Initial sleep time (s) before looking for file |
729 | 397 | retry_sleep_time (int): Time (s) to sleep between retries | ||
730 | 398 | retry_count (int): If file is not found, how many times to retry | ||
731 | 394 | 399 | ||
732 | 395 | Returns: | 400 | Returns: |
733 | 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 |
735 | 397 | file was modified before mtime, | 402 | file was modified before mtime, or if file not found. |
736 | 398 | """ | 403 | """ |
738 | 399 | self.log.debug('Checking %s updated since %s' % (filename, mtime)) | 404 | unit_name = sentry_unit.info['unit_name'] |
739 | 405 | self.log.debug('Checking that %s updated since %s on ' | ||
740 | 406 | '%s' % (filename, mtime, unit_name)) | ||
741 | 400 | time.sleep(sleep_time) | 407 | time.sleep(sleep_time) |
743 | 401 | file_mtime = self._get_file_mtime(sentry_unit, filename) | 408 | file_mtime = None |
744 | 409 | tries = 0 | ||
745 | 410 | while tries <= retry_count and not file_mtime: | ||
746 | 411 | try: | ||
747 | 412 | file_mtime = self._get_file_mtime(sentry_unit, filename) | ||
748 | 413 | self.log.debug('Attempt {} to get {} file mtime on {} ' | ||
749 | 414 | 'OK'.format(tries, filename, unit_name)) | ||
750 | 415 | except IOError as e: | ||
751 | 416 | # NOTE(beisner) - race avoidance, file may not exist yet. | ||
752 | 417 | # https://bugs.launchpad.net/charm-helpers/+bug/1474030 | ||
753 | 418 | self.log.debug('Attempt {} to get {} file mtime on {} ' | ||
754 | 419 | 'failed\n{}'.format(tries, filename, | ||
755 | 420 | unit_name, e)) | ||
756 | 421 | time.sleep(retry_sleep_time) | ||
757 | 422 | tries += 1 | ||
758 | 423 | |||
759 | 424 | if not file_mtime: | ||
760 | 425 | self.log.warn('Could not determine file mtime, assuming ' | ||
761 | 426 | 'file does not exist') | ||
762 | 427 | return False | ||
763 | 428 | |||
764 | 402 | if file_mtime >= mtime: | 429 | if file_mtime >= mtime: |
765 | 403 | self.log.debug('File mtime is newer than provided mtime ' | 430 | self.log.debug('File mtime is newer than provided mtime ' |
767 | 404 | '(%s >= %s)' % (file_mtime, mtime)) | 431 | '(%s >= %s) on %s (OK)' % (file_mtime, |
768 | 432 | mtime, unit_name)) | ||
769 | 405 | return True | 433 | return True |
770 | 406 | else: | 434 | else: |
773 | 407 | self.log.warn('File mtime %s is older than provided mtime %s' | 435 | self.log.warn('File mtime is older than provided mtime' |
774 | 408 | % (file_mtime, mtime)) | 436 | '(%s < on %s) on %s' % (file_mtime, |
775 | 437 | mtime, unit_name)) | ||
776 | 409 | return False | 438 | return False |
777 | 410 | 439 | ||
778 | 411 | def validate_service_config_changed(self, sentry_unit, mtime, service, | 440 | def validate_service_config_changed(self, sentry_unit, mtime, service, |
779 | 412 | filename, pgrep_full=None, | 441 | filename, pgrep_full=None, |
782 | 413 | sleep_time=20, retry_count=2, | 442 | sleep_time=20, retry_count=30, |
783 | 414 | retry_sleep_time=30): | 443 | retry_sleep_time=10): |
784 | 415 | """Check service and file were updated after mtime | 444 | """Check service and file were updated after mtime |
785 | 416 | 445 | ||
786 | 417 | Args: | 446 | Args: |
787 | @@ -456,7 +485,9 @@ | |||
788 | 456 | sentry_unit, | 485 | sentry_unit, |
789 | 457 | filename, | 486 | filename, |
790 | 458 | mtime, | 487 | mtime, |
792 | 459 | sleep_time=0) | 488 | sleep_time=sleep_time, |
793 | 489 | retry_count=retry_count, | ||
794 | 490 | retry_sleep_time=retry_sleep_time) | ||
795 | 460 | 491 | ||
796 | 461 | return service_restart and config_update | 492 | return service_restart and config_update |
797 | 462 | 493 | ||
798 | 463 | 494 | ||
799 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
800 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-22 13:20:40 +0000 | |||
801 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2016-01-07 14:27:41 +0000 | |||
802 | @@ -14,12 +14,18 @@ | |||
803 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
804 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
805 | 16 | 16 | ||
806 | 17 | import logging | ||
807 | 18 | import re | ||
808 | 19 | import sys | ||
809 | 17 | import six | 20 | import six |
810 | 18 | from collections import OrderedDict | 21 | from collections import OrderedDict |
811 | 19 | from charmhelpers.contrib.amulet.deployment import ( | 22 | from charmhelpers.contrib.amulet.deployment import ( |
812 | 20 | AmuletDeployment | 23 | AmuletDeployment |
813 | 21 | ) | 24 | ) |
814 | 22 | 25 | ||
815 | 26 | DEBUG = logging.DEBUG | ||
816 | 27 | ERROR = logging.ERROR | ||
817 | 28 | |||
818 | 23 | 29 | ||
819 | 24 | class OpenStackAmuletDeployment(AmuletDeployment): | 30 | class OpenStackAmuletDeployment(AmuletDeployment): |
820 | 25 | """OpenStack amulet deployment. | 31 | """OpenStack amulet deployment. |
821 | @@ -28,9 +34,12 @@ | |||
822 | 28 | that is specifically for use by OpenStack charms. | 34 | that is specifically for use by OpenStack charms. |
823 | 29 | """ | 35 | """ |
824 | 30 | 36 | ||
826 | 31 | def __init__(self, series=None, openstack=None, source=None, stable=True): | 37 | def __init__(self, series=None, openstack=None, source=None, |
827 | 38 | stable=True, log_level=DEBUG): | ||
828 | 32 | """Initialize the deployment environment.""" | 39 | """Initialize the deployment environment.""" |
829 | 33 | super(OpenStackAmuletDeployment, self).__init__(series) | 40 | super(OpenStackAmuletDeployment, self).__init__(series) |
830 | 41 | self.log = self.get_logger(level=log_level) | ||
831 | 42 | self.log.info('OpenStackAmuletDeployment: init') | ||
832 | 34 | self.openstack = openstack | 43 | self.openstack = openstack |
833 | 35 | self.source = source | 44 | self.source = source |
834 | 36 | self.stable = stable | 45 | self.stable = stable |
835 | @@ -38,6 +47,22 @@ | |||
836 | 38 | # out. | 47 | # out. |
837 | 39 | self.current_next = "trusty" | 48 | self.current_next = "trusty" |
838 | 40 | 49 | ||
839 | 50 | def get_logger(self, name="deployment-logger", level=logging.DEBUG): | ||
840 | 51 | """Get a logger object that will log to stdout.""" | ||
841 | 52 | log = logging | ||
842 | 53 | logger = log.getLogger(name) | ||
843 | 54 | fmt = log.Formatter("%(asctime)s %(funcName)s " | ||
844 | 55 | "%(levelname)s: %(message)s") | ||
845 | 56 | |||
846 | 57 | handler = log.StreamHandler(stream=sys.stdout) | ||
847 | 58 | handler.setLevel(level) | ||
848 | 59 | handler.setFormatter(fmt) | ||
849 | 60 | |||
850 | 61 | logger.addHandler(handler) | ||
851 | 62 | logger.setLevel(level) | ||
852 | 63 | |||
853 | 64 | return logger | ||
854 | 65 | |||
855 | 41 | def _determine_branch_locations(self, other_services): | 66 | def _determine_branch_locations(self, other_services): |
856 | 42 | """Determine the branch locations for the other services. | 67 | """Determine the branch locations for the other services. |
857 | 43 | 68 | ||
858 | @@ -45,6 +70,8 @@ | |||
859 | 45 | stable or next (dev) branch, and based on this, use the corresonding | 70 | stable or next (dev) branch, and based on this, use the corresonding |
860 | 46 | stable or next branches for the other_services.""" | 71 | stable or next branches for the other_services.""" |
861 | 47 | 72 | ||
862 | 73 | self.log.info('OpenStackAmuletDeployment: determine branch locations') | ||
863 | 74 | |||
864 | 48 | # Charms outside the lp:~openstack-charmers namespace | 75 | # Charms outside the lp:~openstack-charmers namespace |
865 | 49 | base_charms = ['mysql', 'mongodb', 'nrpe'] | 76 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
866 | 50 | 77 | ||
867 | @@ -82,6 +109,8 @@ | |||
868 | 82 | 109 | ||
869 | 83 | def _add_services(self, this_service, other_services): | 110 | def _add_services(self, this_service, other_services): |
870 | 84 | """Add services to the deployment and set openstack-origin/source.""" | 111 | """Add services to the deployment and set openstack-origin/source.""" |
871 | 112 | self.log.info('OpenStackAmuletDeployment: adding services') | ||
872 | 113 | |||
873 | 85 | other_services = self._determine_branch_locations(other_services) | 114 | other_services = self._determine_branch_locations(other_services) |
874 | 86 | 115 | ||
875 | 87 | super(OpenStackAmuletDeployment, self)._add_services(this_service, | 116 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
876 | @@ -111,9 +140,79 @@ | |||
877 | 111 | 140 | ||
878 | 112 | def _configure_services(self, configs): | 141 | def _configure_services(self, configs): |
879 | 113 | """Configure all of the services.""" | 142 | """Configure all of the services.""" |
880 | 143 | self.log.info('OpenStackAmuletDeployment: configure services') | ||
881 | 114 | for service, config in six.iteritems(configs): | 144 | for service, config in six.iteritems(configs): |
882 | 115 | self.d.configure(service, config) | 145 | self.d.configure(service, config) |
883 | 116 | 146 | ||
884 | 147 | def _auto_wait_for_status(self, message=None, exclude_services=None, | ||
885 | 148 | include_only=None, timeout=1800): | ||
886 | 149 | """Wait for all units to have a specific extended status, except | ||
887 | 150 | for any defined as excluded. Unless specified via message, any | ||
888 | 151 | status containing any case of 'ready' will be considered a match. | ||
889 | 152 | |||
890 | 153 | Examples of message usage: | ||
891 | 154 | |||
892 | 155 | Wait for all unit status to CONTAIN any case of 'ready' or 'ok': | ||
893 | 156 | message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) | ||
894 | 157 | |||
895 | 158 | Wait for all units to reach this status (exact match): | ||
896 | 159 | message = re.compile('^Unit is ready and clustered$') | ||
897 | 160 | |||
898 | 161 | Wait for all units to reach any one of these (exact match): | ||
899 | 162 | message = re.compile('Unit is ready|OK|Ready') | ||
900 | 163 | |||
901 | 164 | Wait for at least one unit to reach this status (exact match): | ||
902 | 165 | message = {'ready'} | ||
903 | 166 | |||
904 | 167 | See Amulet's sentry.wait_for_messages() for message usage detail. | ||
905 | 168 | https://github.com/juju/amulet/blob/master/amulet/sentry.py | ||
906 | 169 | |||
907 | 170 | :param message: Expected status match | ||
908 | 171 | :param exclude_services: List of juju service names to ignore, | ||
909 | 172 | not to be used in conjuction with include_only. | ||
910 | 173 | :param include_only: List of juju service names to exclusively check, | ||
911 | 174 | not to be used in conjuction with exclude_services. | ||
912 | 175 | :param timeout: Maximum time in seconds to wait for status match | ||
913 | 176 | :returns: None. Raises if timeout is hit. | ||
914 | 177 | """ | ||
915 | 178 | self.log.info('Waiting for extended status on units...') | ||
916 | 179 | |||
917 | 180 | all_services = self.d.services.keys() | ||
918 | 181 | |||
919 | 182 | if exclude_services and include_only: | ||
920 | 183 | raise ValueError('exclude_services can not be used ' | ||
921 | 184 | 'with include_only') | ||
922 | 185 | |||
923 | 186 | if message: | ||
924 | 187 | if isinstance(message, re._pattern_type): | ||
925 | 188 | match = message.pattern | ||
926 | 189 | else: | ||
927 | 190 | match = message | ||
928 | 191 | |||
929 | 192 | self.log.debug('Custom extended status wait match: ' | ||
930 | 193 | '{}'.format(match)) | ||
931 | 194 | else: | ||
932 | 195 | self.log.debug('Default extended status wait match: contains ' | ||
933 | 196 | 'READY (case-insensitive)') | ||
934 | 197 | message = re.compile('.*ready.*', re.IGNORECASE) | ||
935 | 198 | |||
936 | 199 | if exclude_services: | ||
937 | 200 | self.log.debug('Excluding services from extended status match: ' | ||
938 | 201 | '{}'.format(exclude_services)) | ||
939 | 202 | else: | ||
940 | 203 | exclude_services = [] | ||
941 | 204 | |||
942 | 205 | if include_only: | ||
943 | 206 | services = include_only | ||
944 | 207 | else: | ||
945 | 208 | services = list(set(all_services) - set(exclude_services)) | ||
946 | 209 | |||
947 | 210 | self.log.debug('Waiting up to {}s for extended status on services: ' | ||
948 | 211 | '{}'.format(timeout, services)) | ||
949 | 212 | service_messages = {service: message for service in services} | ||
950 | 213 | self.d.sentry.wait_for_messages(service_messages, timeout=timeout) | ||
951 | 214 | self.log.info('OK') | ||
952 | 215 | |||
953 | 117 | def _get_openstack_release(self): | 216 | def _get_openstack_release(self): |
954 | 118 | """Get openstack release. | 217 | """Get openstack release. |
955 | 119 | 218 | ||
956 | 120 | 219 | ||
957 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' | |||
958 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-22 13:20:40 +0000 | |||
959 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2016-01-07 14:27:41 +0000 | |||
960 | @@ -18,6 +18,7 @@ | |||
961 | 18 | import json | 18 | import json |
962 | 19 | import logging | 19 | import logging |
963 | 20 | import os | 20 | import os |
964 | 21 | import re | ||
965 | 21 | import six | 22 | import six |
966 | 22 | import time | 23 | import time |
967 | 23 | import urllib | 24 | import urllib |
968 | @@ -604,7 +605,22 @@ | |||
969 | 604 | '{}'.format(sample_type, samples)) | 605 | '{}'.format(sample_type, samples)) |
970 | 605 | return None | 606 | return None |
971 | 606 | 607 | ||
973 | 607 | # rabbitmq/amqp specific helpers: | 608 | # rabbitmq/amqp specific helpers: |
974 | 609 | |||
975 | 610 | def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): | ||
976 | 611 | """Wait for rmq units extended status to show cluster readiness, | ||
977 | 612 | after an optional initial sleep period. Initial sleep is likely | ||
978 | 613 | necessary to be effective following a config change, as status | ||
979 | 614 | message may not instantly update to non-ready.""" | ||
980 | 615 | |||
981 | 616 | if init_sleep: | ||
982 | 617 | time.sleep(init_sleep) | ||
983 | 618 | |||
984 | 619 | message = re.compile('^Unit is ready and clustered$') | ||
985 | 620 | deployment._auto_wait_for_status(message=message, | ||
986 | 621 | timeout=timeout, | ||
987 | 622 | include_only=['rabbitmq-server']) | ||
988 | 623 | |||
989 | 608 | def add_rmq_test_user(self, sentry_units, | 624 | def add_rmq_test_user(self, sentry_units, |
990 | 609 | username="testuser1", password="changeme"): | 625 | username="testuser1", password="changeme"): |
991 | 610 | """Add a test user via the first rmq juju unit, check connection as | 626 | """Add a test user via the first rmq juju unit, check connection as |
992 | @@ -752,7 +768,7 @@ | |||
993 | 752 | self.log.debug('SSL is enabled @{}:{} ' | 768 | self.log.debug('SSL is enabled @{}:{} ' |
994 | 753 | '({})'.format(host, port, unit_name)) | 769 | '({})'.format(host, port, unit_name)) |
995 | 754 | return True | 770 | return True |
997 | 755 | elif not port and not conf_ssl: | 771 | elif not conf_ssl: |
998 | 756 | self.log.debug('SSL not enabled @{}:{} ' | 772 | self.log.debug('SSL not enabled @{}:{} ' |
999 | 757 | '({})'.format(host, port, unit_name)) | 773 | '({})'.format(host, port, unit_name)) |
1000 | 758 | return False | 774 | return False |
1001 | @@ -805,7 +821,10 @@ | |||
1002 | 805 | if port: | 821 | if port: |
1003 | 806 | config['ssl_port'] = port | 822 | config['ssl_port'] = port |
1004 | 807 | 823 | ||
1006 | 808 | deployment.configure('rabbitmq-server', config) | 824 | deployment.d.configure('rabbitmq-server', config) |
1007 | 825 | |||
1008 | 826 | # Wait for unit status | ||
1009 | 827 | self.rmq_wait_for_cluster(deployment) | ||
1010 | 809 | 828 | ||
1011 | 810 | # Confirm | 829 | # Confirm |
1012 | 811 | tries = 0 | 830 | tries = 0 |
1013 | @@ -832,7 +851,10 @@ | |||
1014 | 832 | 851 | ||
1015 | 833 | # Disable RMQ SSL | 852 | # Disable RMQ SSL |
1016 | 834 | config = {'ssl': 'off'} | 853 | config = {'ssl': 'off'} |
1018 | 835 | deployment.configure('rabbitmq-server', config) | 854 | deployment.d.configure('rabbitmq-server', config) |
1019 | 855 | |||
1020 | 856 | # Wait for unit status | ||
1021 | 857 | self.rmq_wait_for_cluster(deployment) | ||
1022 | 836 | 858 | ||
1023 | 837 | # Confirm | 859 | # Confirm |
1024 | 838 | tries = 0 | 860 | tries = 0 |
charm_unit_test #15627 heat for james-page mp281869
UNIT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_unit_ test/15627/