Merge lp:~1chb1n/charms/trusty/cinder/amulet-fix-vol-from-img-lp1440948 into lp:~openstack-charmers-archive/charms/trusty/cinder/next

Proposed by Ryan Beisner
Status: Merged
Merged at revision: 83
Proposed branch: lp:~1chb1n/charms/trusty/cinder/amulet-fix-vol-from-img-lp1440948
Merge into: lp:~openstack-charmers-archive/charms/trusty/cinder/next
Diff against target: 681 lines (+375/-95)
11 files modified
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+25/-2)
hooks/charmhelpers/contrib/openstack/context.py (+149/-5)
hooks/charmhelpers/contrib/openstack/templates/git.upstart (+13/-0)
hooks/charmhelpers/contrib/openstack/templates/section-zeromq (+14/-0)
hooks/charmhelpers/contrib/openstack/templates/zeromq (+0/-14)
hooks/charmhelpers/contrib/openstack/utils.py (+132/-69)
hooks/charmhelpers/core/hookenv.py (+14/-1)
hooks/charmhelpers/core/unitdata.py (+1/-1)
hooks/cinder_utils.py (+1/-0)
tests/basic_deployment.py (+1/-1)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+25/-2)
To merge this branch: bzr merge lp:~1chb1n/charms/trusty/cinder/amulet-fix-vol-from-img-lp1440948
Reviewer Review Type Date Requested Status
James Page Approve
Review via email: mp+255705@code.launchpad.net

Description of the change

 - Addresses bug 1440948 by explictly installing librbd1.
 - Enables amulet debug logging.
 - Sync charmhelpers.

Reveals a separate issue (bug 1442268), which is not related to the changes of this MP. http://paste.ubuntu.com/10783843/

To post a comment you must log in.

charm_lint_check #3193 cinder-next for 1chb1n mp255705
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/3193/

charm_unit_test #2981 cinder-next for 1chb1n mp255705
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/2981/

charm_amulet_test #3003 cinder-next for 1chb1n mp255705
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/10784058/
Build: http://10.245.162.77:8080/job/charm_amulet_test/3003/

charm_lint_check #3194 cinder-next for 1chb1n mp255705
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/3194/

charm_unit_test #2982 cinder-next for 1chb1n mp255705
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/2982/

charm_amulet_test #3004 cinder-next for 1chb1n mp255705
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/10784240/
Build: http://10.245.162.77:8080/job/charm_amulet_test/3004/

Ryan Beisner (1chb1n) wrote :

^ FYI amulet fail is expected due to a separate issue. See MP initial description.

James Page (james-page) :
review: Approve

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-03-13 13:00:03 +0000
3+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-09 16:27:05 +0000
4@@ -15,6 +15,7 @@
5 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
6
7 import six
8+from collections import OrderedDict
9 from charmhelpers.contrib.amulet.deployment import (
10 AmuletDeployment
11 )
12@@ -100,12 +101,34 @@
13 """
14 (self.precise_essex, self.precise_folsom, self.precise_grizzly,
15 self.precise_havana, self.precise_icehouse,
16- self.trusty_icehouse) = range(6)
17+ self.trusty_icehouse, self.trusty_juno, self.trusty_kilo) = range(8)
18 releases = {
19 ('precise', None): self.precise_essex,
20 ('precise', 'cloud:precise-folsom'): self.precise_folsom,
21 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
22 ('precise', 'cloud:precise-havana'): self.precise_havana,
23 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
24- ('trusty', None): self.trusty_icehouse}
25+ ('trusty', None): self.trusty_icehouse,
26+ ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
27+ ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo}
28 return releases[(self.series, self.openstack)]
29+
30+ def _get_openstack_release_string(self):
31+ """Get openstack release string.
32+
33+ Return a string representing the openstack release.
34+ """
35+ releases = OrderedDict([
36+ ('precise', 'essex'),
37+ ('quantal', 'folsom'),
38+ ('raring', 'grizzly'),
39+ ('saucy', 'havana'),
40+ ('trusty', 'icehouse'),
41+ ('utopic', 'juno'),
42+ ('vivid', 'kilo'),
43+ ])
44+ if self.openstack:
45+ os_origin = self.openstack.split(':')[1]
46+ return os_origin.split('%s-' % self.series)[1].split('/')[0]
47+ else:
48+ return releases[self.series]
49
50=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
51--- hooks/charmhelpers/contrib/openstack/context.py 2015-03-18 12:08:38 +0000
52+++ hooks/charmhelpers/contrib/openstack/context.py 2015-04-09 16:27:05 +0000
53@@ -47,6 +47,7 @@
54 )
55
56 from charmhelpers.core.sysctl import create as sysctl_create
57+from charmhelpers.core.strutils import bool_from_string
58
59 from charmhelpers.core.host import (
60 list_nics,
61@@ -67,6 +68,7 @@
62 )
63 from charmhelpers.contrib.openstack.neutron import (
64 neutron_plugin_attribute,
65+ parse_data_port_mappings,
66 )
67 from charmhelpers.contrib.openstack.ip import (
68 resolve_address,
69@@ -82,7 +84,6 @@
70 is_bridge_member,
71 )
72 from charmhelpers.contrib.openstack.utils import get_host_ip
73-
74 CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
75 ADDRESS_TYPES = ['admin', 'internal', 'public']
76
77@@ -319,14 +320,15 @@
78
79
80 class IdentityServiceContext(OSContextGenerator):
81- interfaces = ['identity-service']
82
83- def __init__(self, service=None, service_user=None):
84+ def __init__(self, service=None, service_user=None, rel_name='identity-service'):
85 self.service = service
86 self.service_user = service_user
87+ self.rel_name = rel_name
88+ self.interfaces = [self.rel_name]
89
90 def __call__(self):
91- log('Generating template context for identity-service', level=DEBUG)
92+ log('Generating template context for ' + self.rel_name, level=DEBUG)
93 ctxt = {}
94
95 if self.service and self.service_user:
96@@ -340,7 +342,7 @@
97
98 ctxt['signing_dir'] = cachedir
99
100- for rid in relation_ids('identity-service'):
101+ for rid in relation_ids(self.rel_name):
102 for unit in related_units(rid):
103 rdata = relation_get(rid=rid, unit=unit)
104 serv_host = rdata.get('service_host')
105@@ -1162,3 +1164,145 @@
106 sysctl_create(sysctl_dict,
107 '/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
108 return {'sysctl': sysctl_dict}
109+
110+
111+class NeutronAPIContext(OSContextGenerator):
112+ '''
113+ Inspects current neutron-plugin-api relation for neutron settings. Return
114+ defaults if it is not present.
115+ '''
116+ interfaces = ['neutron-plugin-api']
117+
118+ def __call__(self):
119+ self.neutron_defaults = {
120+ 'l2_population': {
121+ 'rel_key': 'l2-population',
122+ 'default': False,
123+ },
124+ 'overlay_network_type': {
125+ 'rel_key': 'overlay-network-type',
126+ 'default': 'gre',
127+ },
128+ 'neutron_security_groups': {
129+ 'rel_key': 'neutron-security-groups',
130+ 'default': False,
131+ },
132+ 'network_device_mtu': {
133+ 'rel_key': 'network-device-mtu',
134+ 'default': None,
135+ },
136+ 'enable_dvr': {
137+ 'rel_key': 'enable-dvr',
138+ 'default': False,
139+ },
140+ 'enable_l3ha': {
141+ 'rel_key': 'enable-l3ha',
142+ 'default': False,
143+ },
144+ }
145+ ctxt = self.get_neutron_options({})
146+ for rid in relation_ids('neutron-plugin-api'):
147+ for unit in related_units(rid):
148+ rdata = relation_get(rid=rid, unit=unit)
149+ if 'l2-population' in rdata:
150+ ctxt.update(self.get_neutron_options(rdata))
151+
152+ return ctxt
153+
154+ def get_neutron_options(self, rdata):
155+ settings = {}
156+ for nkey in self.neutron_defaults.keys():
157+ defv = self.neutron_defaults[nkey]['default']
158+ rkey = self.neutron_defaults[nkey]['rel_key']
159+ if rkey in rdata.keys():
160+ if type(defv) is bool:
161+ settings[nkey] = bool_from_string(rdata[rkey])
162+ else:
163+ settings[nkey] = rdata[rkey]
164+ else:
165+ settings[nkey] = defv
166+ return settings
167+
168+
169+class ExternalPortContext(NeutronPortContext):
170+
171+ def __call__(self):
172+ ctxt = {}
173+ ports = config('ext-port')
174+ if ports:
175+ ports = [p.strip() for p in ports.split()]
176+ ports = self.resolve_ports(ports)
177+ if ports:
178+ ctxt = {"ext_port": ports[0]}
179+ napi_settings = NeutronAPIContext()()
180+ mtu = napi_settings.get('network_device_mtu')
181+ if mtu:
182+ ctxt['ext_port_mtu'] = mtu
183+
184+ return ctxt
185+
186+
187+class DataPortContext(NeutronPortContext):
188+
189+ def __call__(self):
190+ ports = config('data-port')
191+ if ports:
192+ portmap = parse_data_port_mappings(ports)
193+ ports = portmap.values()
194+ resolved = self.resolve_ports(ports)
195+ normalized = {get_nic_hwaddr(port): port for port in resolved
196+ if port not in ports}
197+ normalized.update({port: port for port in resolved
198+ if port in ports})
199+ if resolved:
200+ return {bridge: normalized[port] for bridge, port in
201+ six.iteritems(portmap) if port in normalized.keys()}
202+
203+ return None
204+
205+
206+class PhyNICMTUContext(DataPortContext):
207+
208+ def __call__(self):
209+ ctxt = {}
210+ mappings = super(PhyNICMTUContext, self).__call__()
211+ if mappings and mappings.values():
212+ ports = mappings.values()
213+ napi_settings = NeutronAPIContext()()
214+ mtu = napi_settings.get('network_device_mtu')
215+ if mtu:
216+ ctxt["devs"] = '\\n'.join(ports)
217+ ctxt['mtu'] = mtu
218+
219+ return ctxt
220+
221+
222+class NetworkServiceContext(OSContextGenerator):
223+
224+ def __init__(self, rel_name='quantum-network-service'):
225+ self.rel_name = rel_name
226+ self.interfaces = [rel_name]
227+
228+ def __call__(self):
229+ for rid in relation_ids(self.rel_name):
230+ for unit in related_units(rid):
231+ rdata = relation_get(rid=rid, unit=unit)
232+ ctxt = {
233+ 'keystone_host': rdata.get('keystone_host'),
234+ 'service_port': rdata.get('service_port'),
235+ 'auth_port': rdata.get('auth_port'),
236+ 'service_tenant': rdata.get('service_tenant'),
237+ 'service_username': rdata.get('service_username'),
238+ 'service_password': rdata.get('service_password'),
239+ 'quantum_host': rdata.get('quantum_host'),
240+ 'quantum_port': rdata.get('quantum_port'),
241+ 'quantum_url': rdata.get('quantum_url'),
242+ 'region': rdata.get('region'),
243+ 'service_protocol':
244+ rdata.get('service_protocol') or 'http',
245+ 'auth_protocol':
246+ rdata.get('auth_protocol') or 'http',
247+ }
248+ if context_complete(ctxt):
249+ return ctxt
250+ return {}
251
252=== added file 'hooks/charmhelpers/contrib/openstack/templates/git.upstart'
253--- hooks/charmhelpers/contrib/openstack/templates/git.upstart 1970-01-01 00:00:00 +0000
254+++ hooks/charmhelpers/contrib/openstack/templates/git.upstart 2015-04-09 16:27:05 +0000
255@@ -0,0 +1,13 @@
256+description "{{ service_description }}"
257+author "Juju {{ service_name }} Charm <juju@localhost>"
258+
259+start on runlevel [2345]
260+stop on runlevel [!2345]
261+
262+respawn
263+
264+exec start-stop-daemon --start --chuid {{ user_name }} \
265+ --chdir {{ start_dir }} --name {{ process_name }} \
266+ --exec {{ executable_name }} -- \
267+ --config-file={{ config_file }} \
268+ --log-file={{ log_file }}
269
270=== added file 'hooks/charmhelpers/contrib/openstack/templates/section-zeromq'
271--- hooks/charmhelpers/contrib/openstack/templates/section-zeromq 1970-01-01 00:00:00 +0000
272+++ hooks/charmhelpers/contrib/openstack/templates/section-zeromq 2015-04-09 16:27:05 +0000
273@@ -0,0 +1,14 @@
274+{% if zmq_host -%}
275+# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
276+rpc_backend = zmq
277+rpc_zmq_host = {{ zmq_host }}
278+{% if zmq_redis_address -%}
279+rpc_zmq_matchmaker = redis
280+matchmaker_heartbeat_freq = 15
281+matchmaker_heartbeat_ttl = 30
282+[matchmaker_redis]
283+host = {{ zmq_redis_address }}
284+{% else -%}
285+rpc_zmq_matchmaker = ring
286+{% endif -%}
287+{% endif -%}
288
289=== removed file 'hooks/charmhelpers/contrib/openstack/templates/zeromq'
290--- hooks/charmhelpers/contrib/openstack/templates/zeromq 2015-02-24 12:37:30 +0000
291+++ hooks/charmhelpers/contrib/openstack/templates/zeromq 1970-01-01 00:00:00 +0000
292@@ -1,14 +0,0 @@
293-{% if zmq_host -%}
294-# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
295-rpc_backend = zmq
296-rpc_zmq_host = {{ zmq_host }}
297-{% if zmq_redis_address -%}
298-rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_redis.MatchMakerRedis
299-matchmaker_heartbeat_freq = 15
300-matchmaker_heartbeat_ttl = 30
301-[matchmaker_redis]
302-host = {{ zmq_redis_address }}
303-{% else -%}
304-rpc_zmq_matchmaker = oslo.messaging._drivers.matchmaker_ring.MatchMakerRing
305-{% endif -%}
306-{% endif -%}
307
308=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
309--- hooks/charmhelpers/contrib/openstack/utils.py 2015-03-13 13:00:03 +0000
310+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-04-09 16:27:05 +0000
311@@ -30,6 +30,10 @@
312
313 from charmhelpers.contrib.network import ip
314
315+from charmhelpers.core import (
316+ unitdata,
317+)
318+
319 from charmhelpers.core.hookenv import (
320 config,
321 log as juju_log,
322@@ -330,6 +334,21 @@
323 error_out("Invalid openstack-release specified: %s" % rel)
324
325
326+def config_value_changed(option):
327+ """
328+ Determine if config value changed since last call to this function.
329+ """
330+ hook_data = unitdata.HookData()
331+ with hook_data():
332+ db = unitdata.kv()
333+ current = config(option)
334+ saved = db.get(option)
335+ db.set(option, current)
336+ if saved is None:
337+ return False
338+ return current != saved
339+
340+
341 def save_script_rc(script_path="scripts/scriptrc", **env_vars):
342 """
343 Write an rc file in the charm-delivered directory containing
344@@ -469,82 +488,103 @@
345
346
347 def git_install_requested():
348- """Returns true if openstack-origin-git is specified."""
349- return config('openstack-origin-git') != "None"
350+ """
351+ Returns true if openstack-origin-git is specified.
352+ """
353+ return config('openstack-origin-git') is not None
354
355
356 requirements_dir = None
357
358
359-def git_clone_and_install(file_name, core_project):
360- """Clone/install all OpenStack repos specified in yaml config file."""
361+def git_clone_and_install(projects_yaml, core_project):
362+ """
363+ Clone/install all specified OpenStack repositories.
364+
365+ The expected format of projects_yaml is:
366+ repositories:
367+ - {name: keystone,
368+ repository: 'git://git.openstack.org/openstack/keystone.git',
369+ branch: 'stable/icehouse'}
370+ - {name: requirements,
371+ repository: 'git://git.openstack.org/openstack/requirements.git',
372+ branch: 'stable/icehouse'}
373+ directory: /mnt/openstack-git
374+ http_proxy: http://squid.internal:3128
375+ https_proxy: https://squid.internal:3128
376+
377+ The directory, http_proxy, and https_proxy keys are optional.
378+ """
379 global requirements_dir
380+ parent_dir = '/mnt/openstack-git'
381
382- if file_name == "None":
383+ if not projects_yaml:
384 return
385
386- yaml_file = os.path.join(charm_dir(), file_name)
387-
388- # clone/install the requirements project first
389- installed = _git_clone_and_install_subset(yaml_file,
390- whitelist=['requirements'])
391- if 'requirements' not in installed:
392- error_out('requirements git repository must be specified')
393-
394- # clone/install all other projects except requirements and the core project
395- blacklist = ['requirements', core_project]
396- _git_clone_and_install_subset(yaml_file, blacklist=blacklist,
397- update_requirements=True)
398-
399- # clone/install the core project
400- whitelist = [core_project]
401- installed = _git_clone_and_install_subset(yaml_file, whitelist=whitelist,
402- update_requirements=True)
403- if core_project not in installed:
404- error_out('{} git repository must be specified'.format(core_project))
405-
406-
407-def _git_clone_and_install_subset(yaml_file, whitelist=[], blacklist=[],
408- update_requirements=False):
409- """Clone/install subset of OpenStack repos specified in yaml config file."""
410- global requirements_dir
411- installed = []
412-
413- with open(yaml_file, 'r') as fd:
414- projects = yaml.load(fd)
415- for proj, val in projects.items():
416- # The project subset is chosen based on the following 3 rules:
417- # 1) If project is in blacklist, we don't clone/install it, period.
418- # 2) If whitelist is empty, we clone/install everything else.
419- # 3) If whitelist is not empty, we clone/install everything in the
420- # whitelist.
421- if proj in blacklist:
422- continue
423- if whitelist and proj not in whitelist:
424- continue
425- repo = val['repository']
426- branch = val['branch']
427- repo_dir = _git_clone_and_install_single(repo, branch,
428- update_requirements)
429- if proj == 'requirements':
430- requirements_dir = repo_dir
431- installed.append(proj)
432- return installed
433-
434-
435-def _git_clone_and_install_single(repo, branch, update_requirements=False):
436- """Clone and install a single git repository."""
437- dest_parent_dir = "/mnt/openstack-git/"
438- dest_dir = os.path.join(dest_parent_dir, os.path.basename(repo))
439-
440- if not os.path.exists(dest_parent_dir):
441- juju_log('Host dir not mounted at {}. '
442- 'Creating directory there instead.'.format(dest_parent_dir))
443- os.mkdir(dest_parent_dir)
444+ projects = yaml.load(projects_yaml)
445+ _git_validate_projects_yaml(projects, core_project)
446+
447+ if 'http_proxy' in projects.keys():
448+ os.environ['http_proxy'] = projects['http_proxy']
449+
450+ if 'https_proxy' in projects.keys():
451+ os.environ['https_proxy'] = projects['https_proxy']
452+
453+ if 'directory' in projects.keys():
454+ parent_dir = projects['directory']
455+
456+ for p in projects['repositories']:
457+ repo = p['repository']
458+ branch = p['branch']
459+ if p['name'] == 'requirements':
460+ repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
461+ update_requirements=False)
462+ requirements_dir = repo_dir
463+ else:
464+ repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
465+ update_requirements=True)
466+
467+
468+def _git_validate_projects_yaml(projects, core_project):
469+ """
470+ Validate the projects yaml.
471+ """
472+ _git_ensure_key_exists('repositories', projects)
473+
474+ for project in projects['repositories']:
475+ _git_ensure_key_exists('name', project.keys())
476+ _git_ensure_key_exists('repository', project.keys())
477+ _git_ensure_key_exists('branch', project.keys())
478+
479+ if projects['repositories'][0]['name'] != 'requirements':
480+ error_out('{} git repo must be specified first'.format('requirements'))
481+
482+ if projects['repositories'][-1]['name'] != core_project:
483+ error_out('{} git repo must be specified last'.format(core_project))
484+
485+
486+def _git_ensure_key_exists(key, keys):
487+ """
488+ Ensure that key exists in keys.
489+ """
490+ if key not in keys:
491+ error_out('openstack-origin-git key \'{}\' is missing'.format(key))
492+
493+
494+def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
495+ """
496+ Clone and install a single git repository.
497+ """
498+ dest_dir = os.path.join(parent_dir, os.path.basename(repo))
499+
500+ if not os.path.exists(parent_dir):
501+ juju_log('Directory already exists at {}. '
502+ 'No need to create directory.'.format(parent_dir))
503+ os.mkdir(parent_dir)
504
505 if not os.path.exists(dest_dir):
506 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
507- repo_dir = install_remote(repo, dest=dest_parent_dir, branch=branch)
508+ repo_dir = install_remote(repo, dest=parent_dir, branch=branch)
509 else:
510 repo_dir = dest_dir
511
512@@ -561,16 +601,39 @@
513
514
515 def _git_update_requirements(package_dir, reqs_dir):
516- """Update from global requirements.
517+ """
518+ Update from global requirements.
519
520- Update an OpenStack git directory's requirements.txt and
521- test-requirements.txt from global-requirements.txt."""
522+ Update an OpenStack git directory's requirements.txt and
523+ test-requirements.txt from global-requirements.txt.
524+ """
525 orig_dir = os.getcwd()
526 os.chdir(reqs_dir)
527- cmd = "python update.py {}".format(package_dir)
528+ cmd = ['python', 'update.py', package_dir]
529 try:
530- subprocess.check_call(cmd.split(' '))
531+ subprocess.check_call(cmd)
532 except subprocess.CalledProcessError:
533 package = os.path.basename(package_dir)
534 error_out("Error updating {} from global-requirements.txt".format(package))
535 os.chdir(orig_dir)
536+
537+
538+def git_src_dir(projects_yaml, project):
539+ """
540+ Return the directory where the specified project's source is located.
541+ """
542+ parent_dir = '/mnt/openstack-git'
543+
544+ if not projects_yaml:
545+ return
546+
547+ projects = yaml.load(projects_yaml)
548+
549+ if 'directory' in projects.keys():
550+ parent_dir = projects['directory']
551+
552+ for p in projects['repositories']:
553+ if p['name'] == project:
554+ return os.path.join(parent_dir, os.path.basename(p['repository']))
555+
556+ return None
557
558=== modified file 'hooks/charmhelpers/core/hookenv.py'
559--- hooks/charmhelpers/core/hookenv.py 2015-03-18 10:19:15 +0000
560+++ hooks/charmhelpers/core/hookenv.py 2015-04-09 16:27:05 +0000
561@@ -20,11 +20,13 @@
562 # Authors:
563 # Charm Helpers Developers <juju@lists.ubuntu.com>
564
565+from __future__ import print_function
566 import os
567 import json
568 import yaml
569 import subprocess
570 import sys
571+import errno
572 from subprocess import CalledProcessError
573
574 import six
575@@ -87,7 +89,18 @@
576 if not isinstance(message, six.string_types):
577 message = repr(message)
578 command += [message]
579- subprocess.call(command)
580+ # Missing juju-log should not cause failures in unit tests
581+ # Send log output to stderr
582+ try:
583+ subprocess.call(command)
584+ except OSError as e:
585+ if e.errno == errno.ENOENT:
586+ if level:
587+ message = "{}: {}".format(level, message)
588+ message = "juju-log: {}".format(message)
589+ print(message, file=sys.stderr)
590+ else:
591+ raise
592
593
594 class Serializable(UserDict):
595
596=== modified file 'hooks/charmhelpers/core/unitdata.py'
597--- hooks/charmhelpers/core/unitdata.py 2015-02-19 03:38:40 +0000
598+++ hooks/charmhelpers/core/unitdata.py 2015-04-09 16:27:05 +0000
599@@ -443,7 +443,7 @@
600 data = hookenv.execution_environment()
601 self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
602 self.rels = rels_delta = self.kv.delta(data['rels'], 'rels')
603- self.kv.set('env', data['env'])
604+ self.kv.set('env', dict(data['env']))
605 self.kv.set('unit', data['unit'])
606 self.kv.set('relid', data.get('relid'))
607 return conf_delta, rels_delta
608
609=== modified file 'hooks/cinder_utils.py'
610--- hooks/cinder_utils.py 2015-02-19 19:42:21 +0000
611+++ hooks/cinder_utils.py 2015-04-09 16:27:05 +0000
612@@ -69,6 +69,7 @@
613 'cinder-common',
614 'gdisk',
615 'haproxy',
616+ 'librbd1', # bug 1440948 vol-from-img
617 'python-jinja2',
618 'python-keystoneclient',
619 'python-mysqldb',
620
621=== modified file 'tests/basic_deployment.py'
622--- tests/basic_deployment.py 2015-03-26 16:24:22 +0000
623+++ tests/basic_deployment.py 2015-04-09 16:27:05 +0000
624@@ -16,7 +16,7 @@
625 )
626
627 # Use DEBUG to turn on debug logging
628-u = OpenStackAmuletUtils(ERROR)
629+u = OpenStackAmuletUtils(DEBUG)
630
631
632 class CinderBasicDeployment(OpenStackAmuletDeployment):
633
634=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
635--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-03-13 13:00:03 +0000
636+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-09 16:27:05 +0000
637@@ -15,6 +15,7 @@
638 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
639
640 import six
641+from collections import OrderedDict
642 from charmhelpers.contrib.amulet.deployment import (
643 AmuletDeployment
644 )
645@@ -100,12 +101,34 @@
646 """
647 (self.precise_essex, self.precise_folsom, self.precise_grizzly,
648 self.precise_havana, self.precise_icehouse,
649- self.trusty_icehouse) = range(6)
650+ self.trusty_icehouse, self.trusty_juno, self.trusty_kilo) = range(8)
651 releases = {
652 ('precise', None): self.precise_essex,
653 ('precise', 'cloud:precise-folsom'): self.precise_folsom,
654 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
655 ('precise', 'cloud:precise-havana'): self.precise_havana,
656 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
657- ('trusty', None): self.trusty_icehouse}
658+ ('trusty', None): self.trusty_icehouse,
659+ ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
660+ ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo}
661 return releases[(self.series, self.openstack)]
662+
663+ def _get_openstack_release_string(self):
664+ """Get openstack release string.
665+
666+ Return a string representing the openstack release.
667+ """
668+ releases = OrderedDict([
669+ ('precise', 'essex'),
670+ ('quantal', 'folsom'),
671+ ('raring', 'grizzly'),
672+ ('saucy', 'havana'),
673+ ('trusty', 'icehouse'),
674+ ('utopic', 'juno'),
675+ ('vivid', 'kilo'),
676+ ])
677+ if self.openstack:
678+ os_origin = self.openstack.split(':')[1]
679+ return os_origin.split('%s-' % self.series)[1].split('/')[0]
680+ else:
681+ return releases[self.series]

Subscribers

People subscribed via source and target branches