Merge lp:~james-page/charms/trusty/keystone/lp1531102-trunk into lp:~openstack-charmers-archive/charms/trusty/keystone/trunk
- Trusty Tahr (14.04)
- lp1531102-trunk
- Merge into trunk
Proposed by
James Page
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 158 | ||||
Proposed branch: | lp:~james-page/charms/trusty/keystone/lp1531102-trunk | ||||
Merge into: | lp:~openstack-charmers-archive/charms/trusty/keystone/trunk | ||||
Diff against target: |
834 lines (+419/-44) 11 files modified
charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1) charmhelpers/contrib/openstack/amulet/utils.py (+26/-4) charmhelpers/contrib/openstack/context.py (+40/-13) charmhelpers/contrib/openstack/neutron.py (+17/-3) charmhelpers/contrib/openstack/templates/ceph.conf (+6/-0) charmhelpers/contrib/openstack/utils.py (+22/-17) charmhelpers/core/host.py (+12/-1) charmhelpers/core/hugepage.py (+2/-0) charmhelpers/core/kernel.py (+68/-0) 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/keystone/lp1531102-trunk | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+281868@code.launchpad.net |
Commit message
Resync helpers
Description of the change
Resync stable helpers to resolve liberty point release issues.
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 #15628 keystone for james-page mp281868
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #8567 keystone for james-page mp281868
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 'charmhelpers/contrib/openstack/amulet/deployment.py' |
2 | --- charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-22 13:21:20 +0000 |
3 | +++ charmhelpers/contrib/openstack/amulet/deployment.py 2016-01-07 14:26:15 +0000 |
4 | @@ -14,12 +14,18 @@ |
5 | # You should have received a copy of the GNU Lesser General Public License |
6 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
7 | |
8 | +import logging |
9 | +import re |
10 | +import sys |
11 | import six |
12 | from collections import OrderedDict |
13 | from charmhelpers.contrib.amulet.deployment import ( |
14 | AmuletDeployment |
15 | ) |
16 | |
17 | +DEBUG = logging.DEBUG |
18 | +ERROR = logging.ERROR |
19 | + |
20 | |
21 | class OpenStackAmuletDeployment(AmuletDeployment): |
22 | """OpenStack amulet deployment. |
23 | @@ -28,9 +34,12 @@ |
24 | that is specifically for use by OpenStack charms. |
25 | """ |
26 | |
27 | - def __init__(self, series=None, openstack=None, source=None, stable=True): |
28 | + def __init__(self, series=None, openstack=None, source=None, |
29 | + stable=True, log_level=DEBUG): |
30 | """Initialize the deployment environment.""" |
31 | super(OpenStackAmuletDeployment, self).__init__(series) |
32 | + self.log = self.get_logger(level=log_level) |
33 | + self.log.info('OpenStackAmuletDeployment: init') |
34 | self.openstack = openstack |
35 | self.source = source |
36 | self.stable = stable |
37 | @@ -38,6 +47,22 @@ |
38 | # out. |
39 | self.current_next = "trusty" |
40 | |
41 | + def get_logger(self, name="deployment-logger", level=logging.DEBUG): |
42 | + """Get a logger object that will log to stdout.""" |
43 | + log = logging |
44 | + logger = log.getLogger(name) |
45 | + fmt = log.Formatter("%(asctime)s %(funcName)s " |
46 | + "%(levelname)s: %(message)s") |
47 | + |
48 | + handler = log.StreamHandler(stream=sys.stdout) |
49 | + handler.setLevel(level) |
50 | + handler.setFormatter(fmt) |
51 | + |
52 | + logger.addHandler(handler) |
53 | + logger.setLevel(level) |
54 | + |
55 | + return logger |
56 | + |
57 | def _determine_branch_locations(self, other_services): |
58 | """Determine the branch locations for the other services. |
59 | |
60 | @@ -45,6 +70,8 @@ |
61 | stable or next (dev) branch, and based on this, use the corresonding |
62 | stable or next branches for the other_services.""" |
63 | |
64 | + self.log.info('OpenStackAmuletDeployment: determine branch locations') |
65 | + |
66 | # Charms outside the lp:~openstack-charmers namespace |
67 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
68 | |
69 | @@ -82,6 +109,8 @@ |
70 | |
71 | def _add_services(self, this_service, other_services): |
72 | """Add services to the deployment and set openstack-origin/source.""" |
73 | + self.log.info('OpenStackAmuletDeployment: adding services') |
74 | + |
75 | other_services = self._determine_branch_locations(other_services) |
76 | |
77 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
78 | @@ -111,9 +140,79 @@ |
79 | |
80 | def _configure_services(self, configs): |
81 | """Configure all of the services.""" |
82 | + self.log.info('OpenStackAmuletDeployment: configure services') |
83 | for service, config in six.iteritems(configs): |
84 | self.d.configure(service, config) |
85 | |
86 | + def _auto_wait_for_status(self, message=None, exclude_services=None, |
87 | + include_only=None, timeout=1800): |
88 | + """Wait for all units to have a specific extended status, except |
89 | + for any defined as excluded. Unless specified via message, any |
90 | + status containing any case of 'ready' will be considered a match. |
91 | + |
92 | + Examples of message usage: |
93 | + |
94 | + Wait for all unit status to CONTAIN any case of 'ready' or 'ok': |
95 | + message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) |
96 | + |
97 | + Wait for all units to reach this status (exact match): |
98 | + message = re.compile('^Unit is ready and clustered$') |
99 | + |
100 | + Wait for all units to reach any one of these (exact match): |
101 | + message = re.compile('Unit is ready|OK|Ready') |
102 | + |
103 | + Wait for at least one unit to reach this status (exact match): |
104 | + message = {'ready'} |
105 | + |
106 | + See Amulet's sentry.wait_for_messages() for message usage detail. |
107 | + https://github.com/juju/amulet/blob/master/amulet/sentry.py |
108 | + |
109 | + :param message: Expected status match |
110 | + :param exclude_services: List of juju service names to ignore, |
111 | + not to be used in conjuction with include_only. |
112 | + :param include_only: List of juju service names to exclusively check, |
113 | + not to be used in conjuction with exclude_services. |
114 | + :param timeout: Maximum time in seconds to wait for status match |
115 | + :returns: None. Raises if timeout is hit. |
116 | + """ |
117 | + self.log.info('Waiting for extended status on units...') |
118 | + |
119 | + all_services = self.d.services.keys() |
120 | + |
121 | + if exclude_services and include_only: |
122 | + raise ValueError('exclude_services can not be used ' |
123 | + 'with include_only') |
124 | + |
125 | + if message: |
126 | + if isinstance(message, re._pattern_type): |
127 | + match = message.pattern |
128 | + else: |
129 | + match = message |
130 | + |
131 | + self.log.debug('Custom extended status wait match: ' |
132 | + '{}'.format(match)) |
133 | + else: |
134 | + self.log.debug('Default extended status wait match: contains ' |
135 | + 'READY (case-insensitive)') |
136 | + message = re.compile('.*ready.*', re.IGNORECASE) |
137 | + |
138 | + if exclude_services: |
139 | + self.log.debug('Excluding services from extended status match: ' |
140 | + '{}'.format(exclude_services)) |
141 | + else: |
142 | + exclude_services = [] |
143 | + |
144 | + if include_only: |
145 | + services = include_only |
146 | + else: |
147 | + services = list(set(all_services) - set(exclude_services)) |
148 | + |
149 | + self.log.debug('Waiting up to {}s for extended status on services: ' |
150 | + '{}'.format(timeout, services)) |
151 | + service_messages = {service: message for service in services} |
152 | + self.d.sentry.wait_for_messages(service_messages, timeout=timeout) |
153 | + self.log.info('OK') |
154 | + |
155 | def _get_openstack_release(self): |
156 | """Get openstack release. |
157 | |
158 | |
159 | === modified file 'charmhelpers/contrib/openstack/amulet/utils.py' |
160 | --- charmhelpers/contrib/openstack/amulet/utils.py 2015-10-22 13:21:20 +0000 |
161 | +++ charmhelpers/contrib/openstack/amulet/utils.py 2016-01-07 14:26:15 +0000 |
162 | @@ -18,6 +18,7 @@ |
163 | import json |
164 | import logging |
165 | import os |
166 | +import re |
167 | import six |
168 | import time |
169 | import urllib |
170 | @@ -604,7 +605,22 @@ |
171 | '{}'.format(sample_type, samples)) |
172 | return None |
173 | |
174 | -# rabbitmq/amqp specific helpers: |
175 | + # rabbitmq/amqp specific helpers: |
176 | + |
177 | + def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): |
178 | + """Wait for rmq units extended status to show cluster readiness, |
179 | + after an optional initial sleep period. Initial sleep is likely |
180 | + necessary to be effective following a config change, as status |
181 | + message may not instantly update to non-ready.""" |
182 | + |
183 | + if init_sleep: |
184 | + time.sleep(init_sleep) |
185 | + |
186 | + message = re.compile('^Unit is ready and clustered$') |
187 | + deployment._auto_wait_for_status(message=message, |
188 | + timeout=timeout, |
189 | + include_only=['rabbitmq-server']) |
190 | + |
191 | def add_rmq_test_user(self, sentry_units, |
192 | username="testuser1", password="changeme"): |
193 | """Add a test user via the first rmq juju unit, check connection as |
194 | @@ -752,7 +768,7 @@ |
195 | self.log.debug('SSL is enabled @{}:{} ' |
196 | '({})'.format(host, port, unit_name)) |
197 | return True |
198 | - elif not port and not conf_ssl: |
199 | + elif not conf_ssl: |
200 | self.log.debug('SSL not enabled @{}:{} ' |
201 | '({})'.format(host, port, unit_name)) |
202 | return False |
203 | @@ -805,7 +821,10 @@ |
204 | if port: |
205 | config['ssl_port'] = port |
206 | |
207 | - deployment.configure('rabbitmq-server', config) |
208 | + deployment.d.configure('rabbitmq-server', config) |
209 | + |
210 | + # Wait for unit status |
211 | + self.rmq_wait_for_cluster(deployment) |
212 | |
213 | # Confirm |
214 | tries = 0 |
215 | @@ -832,7 +851,10 @@ |
216 | |
217 | # Disable RMQ SSL |
218 | config = {'ssl': 'off'} |
219 | - deployment.configure('rabbitmq-server', config) |
220 | + deployment.d.configure('rabbitmq-server', config) |
221 | + |
222 | + # Wait for unit status |
223 | + self.rmq_wait_for_cluster(deployment) |
224 | |
225 | # Confirm |
226 | tries = 0 |
227 | |
228 | === modified file 'charmhelpers/contrib/openstack/context.py' |
229 | --- charmhelpers/contrib/openstack/context.py 2015-10-22 13:21:20 +0000 |
230 | +++ charmhelpers/contrib/openstack/context.py 2016-01-07 14:26:15 +0000 |
231 | @@ -14,6 +14,7 @@ |
232 | # You should have received a copy of the GNU Lesser General Public License |
233 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
234 | |
235 | +import glob |
236 | import json |
237 | import os |
238 | import re |
239 | @@ -951,6 +952,19 @@ |
240 | 'config': config} |
241 | return ovs_ctxt |
242 | |
243 | + def midonet_ctxt(self): |
244 | + driver = neutron_plugin_attribute(self.plugin, 'driver', |
245 | + self.network_manager) |
246 | + midonet_config = neutron_plugin_attribute(self.plugin, 'config', |
247 | + self.network_manager) |
248 | + mido_ctxt = {'core_plugin': driver, |
249 | + 'neutron_plugin': 'midonet', |
250 | + 'neutron_security_groups': self.neutron_security_groups, |
251 | + 'local_ip': unit_private_ip(), |
252 | + 'config': midonet_config} |
253 | + |
254 | + return mido_ctxt |
255 | + |
256 | def __call__(self): |
257 | if self.network_manager not in ['quantum', 'neutron']: |
258 | return {} |
259 | @@ -972,6 +986,8 @@ |
260 | ctxt.update(self.nuage_ctxt()) |
261 | elif self.plugin == 'plumgrid': |
262 | ctxt.update(self.pg_ctxt()) |
263 | + elif self.plugin == 'midonet': |
264 | + ctxt.update(self.midonet_ctxt()) |
265 | |
266 | alchemy_flags = config('neutron-alchemy-flags') |
267 | if alchemy_flags: |
268 | @@ -1104,7 +1120,7 @@ |
269 | |
270 | ctxt = { |
271 | ... other context ... |
272 | - 'subordinate_config': { |
273 | + 'subordinate_configuration': { |
274 | 'DEFAULT': { |
275 | 'key1': 'value1', |
276 | }, |
277 | @@ -1145,22 +1161,23 @@ |
278 | try: |
279 | sub_config = json.loads(sub_config) |
280 | except: |
281 | - log('Could not parse JSON from subordinate_config ' |
282 | - 'setting from %s' % rid, level=ERROR) |
283 | + log('Could not parse JSON from ' |
284 | + 'subordinate_configuration setting from %s' |
285 | + % rid, level=ERROR) |
286 | continue |
287 | |
288 | for service in self.services: |
289 | if service not in sub_config: |
290 | - log('Found subordinate_config on %s but it contained' |
291 | - 'nothing for %s service' % (rid, service), |
292 | - level=INFO) |
293 | + log('Found subordinate_configuration on %s but it ' |
294 | + 'contained nothing for %s service' |
295 | + % (rid, service), level=INFO) |
296 | continue |
297 | |
298 | sub_config = sub_config[service] |
299 | if self.config_file not in sub_config: |
300 | - log('Found subordinate_config on %s but it contained' |
301 | - 'nothing for %s' % (rid, self.config_file), |
302 | - level=INFO) |
303 | + log('Found subordinate_configuration on %s but it ' |
304 | + 'contained nothing for %s' |
305 | + % (rid, self.config_file), level=INFO) |
306 | continue |
307 | |
308 | sub_config = sub_config[self.config_file] |
309 | @@ -1363,7 +1380,7 @@ |
310 | normalized.update({port: port for port in resolved |
311 | if port in ports}) |
312 | if resolved: |
313 | - return {bridge: normalized[port] for port, bridge in |
314 | + return {normalized[port]: bridge for port, bridge in |
315 | six.iteritems(portmap) if port in normalized.keys()} |
316 | |
317 | return None |
318 | @@ -1374,12 +1391,22 @@ |
319 | def __call__(self): |
320 | ctxt = {} |
321 | mappings = super(PhyNICMTUContext, self).__call__() |
322 | - if mappings and mappings.values(): |
323 | - ports = mappings.values() |
324 | + if mappings and mappings.keys(): |
325 | + ports = sorted(mappings.keys()) |
326 | napi_settings = NeutronAPIContext()() |
327 | mtu = napi_settings.get('network_device_mtu') |
328 | + all_ports = set() |
329 | + # If any of ports is a vlan device, its underlying device must have |
330 | + # mtu applied first. |
331 | + for port in ports: |
332 | + for lport in glob.glob("/sys/class/net/%s/lower_*" % port): |
333 | + lport = os.path.basename(lport) |
334 | + all_ports.add(lport.split('_')[1]) |
335 | + |
336 | + all_ports = list(all_ports) |
337 | + all_ports.extend(ports) |
338 | if mtu: |
339 | - ctxt["devs"] = '\\n'.join(ports) |
340 | + ctxt["devs"] = '\\n'.join(all_ports) |
341 | ctxt['mtu'] = mtu |
342 | |
343 | return ctxt |
344 | |
345 | === modified file 'charmhelpers/contrib/openstack/neutron.py' |
346 | --- charmhelpers/contrib/openstack/neutron.py 2015-10-22 13:21:20 +0000 |
347 | +++ charmhelpers/contrib/openstack/neutron.py 2016-01-07 14:26:15 +0000 |
348 | @@ -209,6 +209,20 @@ |
349 | 'server_packages': ['neutron-server', |
350 | 'neutron-plugin-plumgrid'], |
351 | 'server_services': ['neutron-server'] |
352 | + }, |
353 | + 'midonet': { |
354 | + 'config': '/etc/neutron/plugins/midonet/midonet.ini', |
355 | + 'driver': 'midonet.neutron.plugin.MidonetPluginV2', |
356 | + 'contexts': [ |
357 | + context.SharedDBContext(user=config('neutron-database-user'), |
358 | + database=config('neutron-database'), |
359 | + relation_prefix='neutron', |
360 | + ssl_dir=NEUTRON_CONF_DIR)], |
361 | + 'services': [], |
362 | + 'packages': [[headers_package()] + determine_dkms_package()], |
363 | + 'server_packages': ['neutron-server', |
364 | + 'python-neutron-plugin-midonet'], |
365 | + 'server_services': ['neutron-server'] |
366 | } |
367 | } |
368 | if release >= 'icehouse': |
369 | @@ -310,10 +324,10 @@ |
370 | def parse_data_port_mappings(mappings, default_bridge='br-data'): |
371 | """Parse data port mappings. |
372 | |
373 | - Mappings must be a space-delimited list of port:bridge mappings. |
374 | + Mappings must be a space-delimited list of bridge:port. |
375 | |
376 | - Returns dict of the form {port:bridge} where port may be an mac address or |
377 | - interface name. |
378 | + Returns dict of the form {port:bridge} where ports may be mac addresses or |
379 | + interface names. |
380 | """ |
381 | |
382 | # NOTE(dosaboy): we use rvalue for key to allow multiple values to be |
383 | |
384 | === modified file 'charmhelpers/contrib/openstack/templates/ceph.conf' |
385 | --- charmhelpers/contrib/openstack/templates/ceph.conf 2015-10-22 13:21:20 +0000 |
386 | +++ charmhelpers/contrib/openstack/templates/ceph.conf 2016-01-07 14:26:15 +0000 |
387 | @@ -13,3 +13,9 @@ |
388 | err to syslog = {{ use_syslog }} |
389 | clog to syslog = {{ use_syslog }} |
390 | |
391 | +[client] |
392 | +{% if rbd_client_cache_settings -%} |
393 | +{% for key, value in rbd_client_cache_settings.iteritems() -%} |
394 | +{{ key }} = {{ value }} |
395 | +{% endfor -%} |
396 | +{%- endif %} |
397 | \ No newline at end of file |
398 | |
399 | === modified file 'charmhelpers/contrib/openstack/utils.py' |
400 | --- charmhelpers/contrib/openstack/utils.py 2015-10-22 13:21:20 +0000 |
401 | +++ charmhelpers/contrib/openstack/utils.py 2016-01-07 14:26:15 +0000 |
402 | @@ -121,36 +121,37 @@ |
403 | ('2.2.2', 'kilo'), |
404 | ('2.3.0', 'liberty'), |
405 | ('2.4.0', 'liberty'), |
406 | + ('2.5.0', 'liberty'), |
407 | ]) |
408 | |
409 | # >= Liberty version->codename mapping |
410 | PACKAGE_CODENAMES = { |
411 | 'nova-common': OrderedDict([ |
412 | - ('12.0.0', 'liberty'), |
413 | + ('12.0', 'liberty'), |
414 | ]), |
415 | 'neutron-common': OrderedDict([ |
416 | - ('7.0.0', 'liberty'), |
417 | + ('7.0', 'liberty'), |
418 | ]), |
419 | 'cinder-common': OrderedDict([ |
420 | - ('7.0.0', 'liberty'), |
421 | + ('7.0', 'liberty'), |
422 | ]), |
423 | 'keystone': OrderedDict([ |
424 | - ('8.0.0', 'liberty'), |
425 | + ('8.0', 'liberty'), |
426 | ]), |
427 | 'horizon-common': OrderedDict([ |
428 | - ('8.0.0', 'liberty'), |
429 | + ('8.0', 'liberty'), |
430 | ]), |
431 | 'ceilometer-common': OrderedDict([ |
432 | - ('5.0.0', 'liberty'), |
433 | + ('5.0', 'liberty'), |
434 | ]), |
435 | 'heat-common': OrderedDict([ |
436 | - ('5.0.0', 'liberty'), |
437 | + ('5.0', 'liberty'), |
438 | ]), |
439 | 'glance-common': OrderedDict([ |
440 | - ('11.0.0', 'liberty'), |
441 | + ('11.0', 'liberty'), |
442 | ]), |
443 | 'openstack-dashboard': OrderedDict([ |
444 | - ('8.0.0', 'liberty'), |
445 | + ('8.0', 'liberty'), |
446 | ]), |
447 | } |
448 | |
449 | @@ -237,7 +238,14 @@ |
450 | error_out(e) |
451 | |
452 | vers = apt.upstream_version(pkg.current_ver.ver_str) |
453 | - match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) |
454 | + if 'swift' in pkg.name: |
455 | + # Fully x.y.z match for swift versions |
456 | + match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) |
457 | + else: |
458 | + # x.y match only for 20XX.X |
459 | + # and ignore patch level for other packages |
460 | + match = re.match('^(\d+)\.(\d+)', vers) |
461 | + |
462 | if match: |
463 | vers = match.group(0) |
464 | |
465 | @@ -249,13 +257,8 @@ |
466 | # < Liberty co-ordinated project versions |
467 | try: |
468 | if 'swift' in pkg.name: |
469 | - swift_vers = vers[:5] |
470 | - if swift_vers not in SWIFT_CODENAMES: |
471 | - # Deal with 1.10.0 upward |
472 | - swift_vers = vers[:6] |
473 | - return SWIFT_CODENAMES[swift_vers] |
474 | + return SWIFT_CODENAMES[vers] |
475 | else: |
476 | - vers = vers[:6] |
477 | return OPENSTACK_CODENAMES[vers] |
478 | except KeyError: |
479 | if not fatal: |
480 | @@ -858,7 +861,9 @@ |
481 | if charm_state != 'active' and charm_state != 'unknown': |
482 | state = workload_state_compare(state, charm_state) |
483 | if message: |
484 | - message = "{} {}".format(message, charm_message) |
485 | + charm_message = charm_message.replace("Incomplete relations: ", |
486 | + "") |
487 | + message = "{}, {}".format(message, charm_message) |
488 | else: |
489 | message = charm_message |
490 | |
491 | |
492 | === modified file 'charmhelpers/core/host.py' |
493 | --- charmhelpers/core/host.py 2015-10-22 13:21:20 +0000 |
494 | +++ charmhelpers/core/host.py 2016-01-07 14:26:15 +0000 |
495 | @@ -566,7 +566,14 @@ |
496 | os.chdir(cur) |
497 | |
498 | |
499 | -def chownr(path, owner, group, follow_links=True): |
500 | +def chownr(path, owner, group, follow_links=True, chowntopdir=False): |
501 | + """ |
502 | + Recursively change user and group ownership of files and directories |
503 | + in given path. Doesn't chown path itself by default, only its children. |
504 | + |
505 | + :param bool follow_links: Also Chown links if True |
506 | + :param bool chowntopdir: Also chown path itself if True |
507 | + """ |
508 | uid = pwd.getpwnam(owner).pw_uid |
509 | gid = grp.getgrnam(group).gr_gid |
510 | if follow_links: |
511 | @@ -574,6 +581,10 @@ |
512 | else: |
513 | chown = os.lchown |
514 | |
515 | + if chowntopdir: |
516 | + broken_symlink = os.path.lexists(path) and not os.path.exists(path) |
517 | + if not broken_symlink: |
518 | + chown(path, uid, gid) |
519 | for root, dirs, files in os.walk(path): |
520 | for name in dirs + files: |
521 | full = os.path.join(root, name) |
522 | |
523 | === modified file 'charmhelpers/core/hugepage.py' |
524 | --- charmhelpers/core/hugepage.py 2015-10-22 13:21:20 +0000 |
525 | +++ charmhelpers/core/hugepage.py 2016-01-07 14:26:15 +0000 |
526 | @@ -46,6 +46,8 @@ |
527 | group_info = add_group(group) |
528 | gid = group_info.gr_gid |
529 | add_user_to_group(user, group) |
530 | + if max_map_count < 2 * nr_hugepages: |
531 | + max_map_count = 2 * nr_hugepages |
532 | sysctl_settings = { |
533 | 'vm.nr_hugepages': nr_hugepages, |
534 | 'vm.max_map_count': max_map_count, |
535 | |
536 | === added file 'charmhelpers/core/kernel.py' |
537 | --- charmhelpers/core/kernel.py 1970-01-01 00:00:00 +0000 |
538 | +++ charmhelpers/core/kernel.py 2016-01-07 14:26:15 +0000 |
539 | @@ -0,0 +1,68 @@ |
540 | +#!/usr/bin/env python |
541 | +# -*- coding: utf-8 -*- |
542 | + |
543 | +# Copyright 2014-2015 Canonical Limited. |
544 | +# |
545 | +# This file is part of charm-helpers. |
546 | +# |
547 | +# charm-helpers is free software: you can redistribute it and/or modify |
548 | +# it under the terms of the GNU Lesser General Public License version 3 as |
549 | +# published by the Free Software Foundation. |
550 | +# |
551 | +# charm-helpers is distributed in the hope that it will be useful, |
552 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
553 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
554 | +# GNU Lesser General Public License for more details. |
555 | +# |
556 | +# You should have received a copy of the GNU Lesser General Public License |
557 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
558 | + |
559 | +__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" |
560 | + |
561 | +from charmhelpers.core.hookenv import ( |
562 | + log, |
563 | + INFO |
564 | +) |
565 | + |
566 | +from subprocess import check_call, check_output |
567 | +import re |
568 | + |
569 | + |
570 | +def modprobe(module, persist=True): |
571 | + """Load a kernel module and configure for auto-load on reboot.""" |
572 | + cmd = ['modprobe', module] |
573 | + |
574 | + log('Loading kernel module %s' % module, level=INFO) |
575 | + |
576 | + check_call(cmd) |
577 | + if persist: |
578 | + with open('/etc/modules', 'r+') as modules: |
579 | + if module not in modules.read(): |
580 | + modules.write(module) |
581 | + |
582 | + |
583 | +def rmmod(module, force=False): |
584 | + """Remove a module from the linux kernel""" |
585 | + cmd = ['rmmod'] |
586 | + if force: |
587 | + cmd.append('-f') |
588 | + cmd.append(module) |
589 | + log('Removing kernel module %s' % module, level=INFO) |
590 | + return check_call(cmd) |
591 | + |
592 | + |
593 | +def lsmod(): |
594 | + """Shows what kernel modules are currently loaded""" |
595 | + return check_output(['lsmod'], |
596 | + universal_newlines=True) |
597 | + |
598 | + |
599 | +def is_module_loaded(module): |
600 | + """Checks if a kernel module is already loaded""" |
601 | + matches = re.findall('^%s[ ]+' % module, lsmod(), re.M) |
602 | + return len(matches) > 0 |
603 | + |
604 | + |
605 | +def update_initramfs(version='all'): |
606 | + """Updates an initramfs image""" |
607 | + return check_call(["update-initramfs", "-k", version, "-u"]) |
608 | |
609 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
610 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-22 13:21:20 +0000 |
611 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2016-01-07 14:26:15 +0000 |
612 | @@ -14,12 +14,18 @@ |
613 | # You should have received a copy of the GNU Lesser General Public License |
614 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
615 | |
616 | +import logging |
617 | +import re |
618 | +import sys |
619 | import six |
620 | from collections import OrderedDict |
621 | from charmhelpers.contrib.amulet.deployment import ( |
622 | AmuletDeployment |
623 | ) |
624 | |
625 | +DEBUG = logging.DEBUG |
626 | +ERROR = logging.ERROR |
627 | + |
628 | |
629 | class OpenStackAmuletDeployment(AmuletDeployment): |
630 | """OpenStack amulet deployment. |
631 | @@ -28,9 +34,12 @@ |
632 | that is specifically for use by OpenStack charms. |
633 | """ |
634 | |
635 | - def __init__(self, series=None, openstack=None, source=None, stable=True): |
636 | + def __init__(self, series=None, openstack=None, source=None, |
637 | + stable=True, log_level=DEBUG): |
638 | """Initialize the deployment environment.""" |
639 | super(OpenStackAmuletDeployment, self).__init__(series) |
640 | + self.log = self.get_logger(level=log_level) |
641 | + self.log.info('OpenStackAmuletDeployment: init') |
642 | self.openstack = openstack |
643 | self.source = source |
644 | self.stable = stable |
645 | @@ -38,6 +47,22 @@ |
646 | # out. |
647 | self.current_next = "trusty" |
648 | |
649 | + def get_logger(self, name="deployment-logger", level=logging.DEBUG): |
650 | + """Get a logger object that will log to stdout.""" |
651 | + log = logging |
652 | + logger = log.getLogger(name) |
653 | + fmt = log.Formatter("%(asctime)s %(funcName)s " |
654 | + "%(levelname)s: %(message)s") |
655 | + |
656 | + handler = log.StreamHandler(stream=sys.stdout) |
657 | + handler.setLevel(level) |
658 | + handler.setFormatter(fmt) |
659 | + |
660 | + logger.addHandler(handler) |
661 | + logger.setLevel(level) |
662 | + |
663 | + return logger |
664 | + |
665 | def _determine_branch_locations(self, other_services): |
666 | """Determine the branch locations for the other services. |
667 | |
668 | @@ -45,6 +70,8 @@ |
669 | stable or next (dev) branch, and based on this, use the corresonding |
670 | stable or next branches for the other_services.""" |
671 | |
672 | + self.log.info('OpenStackAmuletDeployment: determine branch locations') |
673 | + |
674 | # Charms outside the lp:~openstack-charmers namespace |
675 | base_charms = ['mysql', 'mongodb', 'nrpe'] |
676 | |
677 | @@ -82,6 +109,8 @@ |
678 | |
679 | def _add_services(self, this_service, other_services): |
680 | """Add services to the deployment and set openstack-origin/source.""" |
681 | + self.log.info('OpenStackAmuletDeployment: adding services') |
682 | + |
683 | other_services = self._determine_branch_locations(other_services) |
684 | |
685 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
686 | @@ -111,9 +140,79 @@ |
687 | |
688 | def _configure_services(self, configs): |
689 | """Configure all of the services.""" |
690 | + self.log.info('OpenStackAmuletDeployment: configure services') |
691 | for service, config in six.iteritems(configs): |
692 | self.d.configure(service, config) |
693 | |
694 | + def _auto_wait_for_status(self, message=None, exclude_services=None, |
695 | + include_only=None, timeout=1800): |
696 | + """Wait for all units to have a specific extended status, except |
697 | + for any defined as excluded. Unless specified via message, any |
698 | + status containing any case of 'ready' will be considered a match. |
699 | + |
700 | + Examples of message usage: |
701 | + |
702 | + Wait for all unit status to CONTAIN any case of 'ready' or 'ok': |
703 | + message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) |
704 | + |
705 | + Wait for all units to reach this status (exact match): |
706 | + message = re.compile('^Unit is ready and clustered$') |
707 | + |
708 | + Wait for all units to reach any one of these (exact match): |
709 | + message = re.compile('Unit is ready|OK|Ready') |
710 | + |
711 | + Wait for at least one unit to reach this status (exact match): |
712 | + message = {'ready'} |
713 | + |
714 | + See Amulet's sentry.wait_for_messages() for message usage detail. |
715 | + https://github.com/juju/amulet/blob/master/amulet/sentry.py |
716 | + |
717 | + :param message: Expected status match |
718 | + :param exclude_services: List of juju service names to ignore, |
719 | + not to be used in conjuction with include_only. |
720 | + :param include_only: List of juju service names to exclusively check, |
721 | + not to be used in conjuction with exclude_services. |
722 | + :param timeout: Maximum time in seconds to wait for status match |
723 | + :returns: None. Raises if timeout is hit. |
724 | + """ |
725 | + self.log.info('Waiting for extended status on units...') |
726 | + |
727 | + all_services = self.d.services.keys() |
728 | + |
729 | + if exclude_services and include_only: |
730 | + raise ValueError('exclude_services can not be used ' |
731 | + 'with include_only') |
732 | + |
733 | + if message: |
734 | + if isinstance(message, re._pattern_type): |
735 | + match = message.pattern |
736 | + else: |
737 | + match = message |
738 | + |
739 | + self.log.debug('Custom extended status wait match: ' |
740 | + '{}'.format(match)) |
741 | + else: |
742 | + self.log.debug('Default extended status wait match: contains ' |
743 | + 'READY (case-insensitive)') |
744 | + message = re.compile('.*ready.*', re.IGNORECASE) |
745 | + |
746 | + if exclude_services: |
747 | + self.log.debug('Excluding services from extended status match: ' |
748 | + '{}'.format(exclude_services)) |
749 | + else: |
750 | + exclude_services = [] |
751 | + |
752 | + if include_only: |
753 | + services = include_only |
754 | + else: |
755 | + services = list(set(all_services) - set(exclude_services)) |
756 | + |
757 | + self.log.debug('Waiting up to {}s for extended status on services: ' |
758 | + '{}'.format(timeout, services)) |
759 | + service_messages = {service: message for service in services} |
760 | + self.d.sentry.wait_for_messages(service_messages, timeout=timeout) |
761 | + self.log.info('OK') |
762 | + |
763 | def _get_openstack_release(self): |
764 | """Get openstack release. |
765 | |
766 | |
767 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' |
768 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-22 13:21:20 +0000 |
769 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2016-01-07 14:26:15 +0000 |
770 | @@ -18,6 +18,7 @@ |
771 | import json |
772 | import logging |
773 | import os |
774 | +import re |
775 | import six |
776 | import time |
777 | import urllib |
778 | @@ -604,7 +605,22 @@ |
779 | '{}'.format(sample_type, samples)) |
780 | return None |
781 | |
782 | -# rabbitmq/amqp specific helpers: |
783 | + # rabbitmq/amqp specific helpers: |
784 | + |
785 | + def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): |
786 | + """Wait for rmq units extended status to show cluster readiness, |
787 | + after an optional initial sleep period. Initial sleep is likely |
788 | + necessary to be effective following a config change, as status |
789 | + message may not instantly update to non-ready.""" |
790 | + |
791 | + if init_sleep: |
792 | + time.sleep(init_sleep) |
793 | + |
794 | + message = re.compile('^Unit is ready and clustered$') |
795 | + deployment._auto_wait_for_status(message=message, |
796 | + timeout=timeout, |
797 | + include_only=['rabbitmq-server']) |
798 | + |
799 | def add_rmq_test_user(self, sentry_units, |
800 | username="testuser1", password="changeme"): |
801 | """Add a test user via the first rmq juju unit, check connection as |
802 | @@ -752,7 +768,7 @@ |
803 | self.log.debug('SSL is enabled @{}:{} ' |
804 | '({})'.format(host, port, unit_name)) |
805 | return True |
806 | - elif not port and not conf_ssl: |
807 | + elif not conf_ssl: |
808 | self.log.debug('SSL not enabled @{}:{} ' |
809 | '({})'.format(host, port, unit_name)) |
810 | return False |
811 | @@ -805,7 +821,10 @@ |
812 | if port: |
813 | config['ssl_port'] = port |
814 | |
815 | - deployment.configure('rabbitmq-server', config) |
816 | + deployment.d.configure('rabbitmq-server', config) |
817 | + |
818 | + # Wait for unit status |
819 | + self.rmq_wait_for_cluster(deployment) |
820 | |
821 | # Confirm |
822 | tries = 0 |
823 | @@ -832,7 +851,10 @@ |
824 | |
825 | # Disable RMQ SSL |
826 | config = {'ssl': 'off'} |
827 | - deployment.configure('rabbitmq-server', config) |
828 | + deployment.d.configure('rabbitmq-server', config) |
829 | + |
830 | + # Wait for unit status |
831 | + self.rmq_wait_for_cluster(deployment) |
832 | |
833 | # Confirm |
834 | tries = 0 |
charm_lint_check #16734 keystone for james-page mp281868
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/16734/