Merge lp:~stub/charm-helpers/py3-2 into lp:charm-helpers

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 258
Proposed branch: lp:~stub/charm-helpers/py3-2
Merge into: lp:charm-helpers
Diff against target: 3281 lines (+625/-393)
62 files modified
.bzrignore (+2/-0)
Makefile (+30/-7)
charmhelpers/__init__.py (+22/-0)
charmhelpers/cli/__init__.py (+3/-2)
charmhelpers/contrib/amulet/deployment.py (+3/-3)
charmhelpers/contrib/amulet/utils.py (+6/-4)
charmhelpers/contrib/charmhelpers/__init__.py (+26/-19)
charmhelpers/contrib/hahelpers/cluster.py (+4/-3)
charmhelpers/contrib/network/ip.py (+2/-2)
charmhelpers/contrib/openstack/amulet/deployment.py (+2/-1)
charmhelpers/contrib/openstack/amulet/utils.py (+3/-1)
charmhelpers/contrib/openstack/context.py (+14/-13)
charmhelpers/contrib/openstack/neutron.py (+2/-2)
charmhelpers/contrib/openstack/templates/haproxy.cfg (+2/-2)
charmhelpers/contrib/openstack/templating.py (+5/-5)
charmhelpers/contrib/openstack/utils.py (+8/-7)
charmhelpers/contrib/peerstorage/__init__.py (+4/-3)
charmhelpers/contrib/python/packages.py (+1/-1)
charmhelpers/contrib/ssl/__init__.py (+1/-1)
charmhelpers/contrib/storage/linux/ceph.py (+9/-6)
charmhelpers/contrib/storage/linux/loopback.py (+4/-4)
charmhelpers/contrib/storage/linux/lvm.py (+1/-0)
charmhelpers/contrib/storage/linux/utils.py (+3/-2)
charmhelpers/contrib/templating/contexts.py (+5/-3)
charmhelpers/core/fstab.py (+10/-8)
charmhelpers/core/hookenv.py (+20/-12)
charmhelpers/core/host.py (+22/-17)
charmhelpers/core/services/helpers.py (+9/-5)
charmhelpers/core/templating.py (+2/-1)
charmhelpers/fetch/__init__.py (+13/-11)
charmhelpers/fetch/archiveurl.py (+53/-16)
charmhelpers/fetch/bzrurl.py (+5/-1)
charmhelpers/fetch/giturl.py (+6/-2)
test_requirements.txt (+17/-18)
tests/cli/test_cmdline.py (+7/-7)
tests/cli/test_function_signature_analysis.py (+2/-2)
tests/contrib/charmhelpers/test_charmhelpers.py (+6/-6)
tests/contrib/hahelpers/test_cluster_utils.py (+2/-2)
tests/contrib/network/test_ip.py (+11/-10)
tests/contrib/openstack/test_neutron_utils.py (+2/-2)
tests/contrib/openstack/test_openstack_utils.py (+33/-24)
tests/contrib/openstack/test_os_contexts.py (+28/-27)
tests/contrib/openstack/test_os_templating.py (+9/-3)
tests/contrib/storage/test_linux_ceph.py (+8/-8)
tests/contrib/storage/test_linux_storage_lvm.py (+2/-2)
tests/contrib/storage/test_linux_storage_utils.py (+8/-8)
tests/contrib/templating/test_contexts.py (+9/-6)
tests/contrib/unison/test_unison.py (+2/-2)
tests/core/test_fstab.py (+1/-1)
tests/core/test_hookenv.py (+34/-32)
tests/core/test_host.py (+19/-20)
tests/core/test_services.py (+4/-4)
tests/core/test_sysctl.py (+10/-3)
tests/fetch/test_archiveurl.py (+12/-5)
tests/fetch/test_bzrurl.py (+25/-8)
tests/fetch/test_fetch.py (+13/-3)
tests/fetch/test_giturl.py (+24/-8)
tests/helpers.py (+15/-6)
tests/payload/test_archive.py (+1/-1)
tests/payload/test_execd.py (+1/-1)
tests/tools/test_charm_helper_sync.py (+13/-6)
tools/charm_helpers_sync/charm_helpers_sync.py (+5/-4)
To merge this branch: bzr merge lp:~stub/charm-helpers/py3-2
Reviewer Review Type Date Requested Status
Tim Van Steenburgh Approve
Jorge Niedbalski (community) Approve
Jorge Niedbalski Pending
Review via email: mp+242653@code.launchpad.net

Description of the change

Per https://code.launchpad.net/~stub/charm-helpers/py3/+merge/242639,
precise charms attempting to use the Python 3 updated charm-helperers generally will not work, as the version of six they will have installed is very, very old.

This branch backports charm-helpers to use six 1.1, rather than six 1.18.

I believe this version will be good enough until precise charms stop being maintained by maintained, or until the charm-helpers distribution story is improved (if we distribute charm-helpers as a package or similar, we can distribute modern six with it).

An alternative approach I looked at on the previous MP was embedding a copy of six. This worked fine, but would have required people to sync six.py into their charm as well as the rest of charm-helpers.

To post a comment you must log in.
lp:~stub/charm-helpers/py3-2 updated
196. By Stuart Bishop

Bootstrap six on import

197. By Stuart Bishop

Bootstrap yaml package

198. By Stuart Bishop

Fix reliance on implicit casts, such as treating subprocess.check_output result as text instead of bytes

199. By Stuart Bishop

Revert Py3 syntax hidden by mocking

200. By Stuart Bishop

antique six

201. By Stuart Bishop

Need modern distribute under precise

Revision history for this message
Tim Van Steenburgh (tvansteenburgh) wrote :

Running make test in a clean precise lxc:

Checking for Python syntax...
Py2 OK
charmhelpers/contrib/templating/contexts.py:99:54: E901 SyntaxError: invalid syntax
tests/contrib/templating/test_contexts.py:137:31: E901 SyntaxError: invalid syntax
tests/core/test_hookenv.py:132:40: E901 SyntaxError: invalid syntax
make: *** [lint] Error 1

Also, running apt-get as an import side-effect makes me grimace.

review: Needs Fixing
lp:~stub/charm-helpers/py3-2 updated
202. By Stuart Bishop

Python3.2 backport for precise

203. By Stuart Bishop

Don't need simplejson

Revision history for this message
Jorge Niedbalski (niedbalski) wrote :

My preliminary review shows everything OK.

- Trusty tests OK [python2.7, python3.4]
   - No import errors

- Precise tests OK [python2.7, python3.2]
   - No import errors

---------------------------------------------------------------------------
TOTAL 3683 236 94%
----------------------------------------------------------------------
Ran 773 tests in 7.484s

OK (SKIP=7)
OK

ubuntu@precise:~/charm-helpers$ sudo python2.7
Python 2.7.3 (default, Feb 27 2014, 19:58:35)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import charmhelpers
>>>

Python 3.2.3 (default, Feb 27 2014, 21:31:18)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import charmhelpers
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  python3-six
0 upgraded, 1 newly installed, 0 to remove and 54 not upgraded.
Need to get 5,894 B of archives.
After this operation, 50.2 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ precise/universe python3-six all 1.1.0-2 [5,894 B]
Fetched 5,894 B in 0s (16.7 kB/s)
Selecting previously unselected package python3-six.
(Reading database ... 58662 files and directories currently installed.)
Unpacking python3-six (from .../python3-six_1.1.0-2_all.deb) ...
Setting up python3-six (1.1.0-2) ...
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  python3-yaml
0 upgraded, 1 newly installed, 0 to remove and 54 not upgraded.
Need to get 111 kB of archives.
After this operation, 442 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ precise/main python3-yaml amd64 3.10-2 [111 kB]
Fetched 111 kB in 0s (120 kB/s)
Selecting previously unselected package python3-yaml.
(Reading database ... 58667 files and directories currently installed.)
Unpacking python3-yaml (from .../python3-yaml_3.10-2_amd64.deb) ...
Setting up python3-yaml (3.10-2) ...
>>>

lp:~stub/charm-helpers/py3-2 updated
204. By Stuart Bishop

merge trunk, revert reversion

Revision history for this message
Jorge Niedbalski (niedbalski) wrote :

After the latest merge, conflicts resolved. LGTM +1

review: Approve
Revision history for this message
Tim Van Steenburgh (tvansteenburgh) wrote :

> Running make test in a clean precise lxc:
>
> Checking for Python syntax...
> Py2 OK
> charmhelpers/contrib/templating/contexts.py:99:54: E901 SyntaxError: invalid
> syntax
> tests/contrib/templating/test_contexts.py:137:31: E901 SyntaxError: invalid
> syntax
> tests/core/test_hookenv.py:132:40: E901 SyntaxError: invalid syntax
> make: *** [lint] Error 1
>
>
> Also, running apt-get as an import side-effect makes me grimace.

Retested on precise and tests pass. Sorry for the noise, not sure what happened the first time.

Revision history for this message
Tim Van Steenburgh (tvansteenburgh) wrote :

+1 LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2014-11-25 14:35:22 +0000
3+++ .bzrignore 2014-11-25 15:09:14 +0000
4@@ -7,3 +7,5 @@
5 .env/
6 coverage.xml
7 docs/_build
8+.venv
9+.venv3
10
11=== modified file 'Makefile'
12--- Makefile 2014-11-25 14:35:22 +0000
13+++ Makefile 2014-11-25 15:09:14 +0000
14@@ -23,11 +23,12 @@
15 python setup.py sdist
16
17 clean:
18- python setup.py clean
19+ -python setup.py clean
20 rm -rf build/ MANIFEST
21 find . -name '*.pyc' -delete
22 rm -rf dist/*
23 rm -rf .venv
24+ rm -rf .venv3
25 (which dh_clean && dh_clean) || true
26
27 userinstall:
28@@ -38,19 +39,41 @@
29 sudo apt-get install -y gcc python-dev python-virtualenv python-apt
30 virtualenv .venv --system-site-packages
31 .venv/bin/pip install -U pip
32+ .venv/bin/pip install -U distribute
33 .venv/bin/pip install -I -r test_requirements.txt
34-
35-test: .venv
36- @echo Starting tests...
37+ .venv/bin/pip install bzr
38+ .venv/bin/pip install GitPython
39+
40+.venv3:
41+ sudo apt-get install -y gcc python3-dev python-virtualenv python3-apt
42+ virtualenv .venv3 --python=python3 --system-site-packages
43+ .venv3/bin/pip install -U pip
44+ .venv3/bin/pip install -U distribute
45+ .venv3/bin/pip install -I -r test_requirements.txt
46+
47+# Note we don't even attempt to run tests if lint isn't passing.
48+test: lint test2 test3
49+ @echo OK
50+
51+test2:
52+ @echo Starting Py2 tests...
53 .venv/bin/nosetests -s --nologcapture tests/
54
55-ftest: .venv
56+test3:
57+ @echo Starting Py3 tests...
58+ .venv3/bin/nosetests -s --nologcapture tests/
59+
60+ftest: lint
61 @echo Starting fast tests...
62 .venv/bin/nosetests --attr '!slow' --nologcapture tests/
63+ .venv3/bin/nosetests --attr '!slow' --nologcapture tests/
64
65-lint:
66+lint: .venv .venv3
67 @echo Checking for Python syntax...
68- @flake8 --ignore=E123,E501 $(PROJECT) $(TESTS) && echo OK
69+ @.venv/bin/flake8 --ignore=E501 $(PROJECT) $(TESTS) tools/ \
70+ && echo Py2 OK
71+ @.venv3/bin/flake8 --ignore=E501 $(PROJECT) $(TESTS) tools/ \
72+ && echo Py3 OK
73
74 docs:
75 - [ -z "`dpkg -l | grep python-sphinx`" ] && sudo apt-get install python-sphinx -y
76
77=== modified file 'charmhelpers/__init__.py'
78--- charmhelpers/__init__.py 2013-05-11 19:55:58 +0000
79+++ charmhelpers/__init__.py 2014-11-25 15:09:14 +0000
80@@ -0,0 +1,22 @@
81+# Bootstrap charm-helpers, installing its dependencies if necessary using
82+# only standard libraries.
83+import subprocess
84+import sys
85+
86+try:
87+ import six # flake8: noqa
88+except ImportError:
89+ if sys.version_info.major == 2:
90+ subprocess.check_call(['apt-get', 'install', '-y', 'python-six'])
91+ else:
92+ subprocess.check_call(['apt-get', 'install', '-y', 'python3-six'])
93+ import six # flake8: noqa
94+
95+try:
96+ import yaml # flake8: noqa
97+except ImportError:
98+ if sys.version_info.major == 2:
99+ subprocess.check_call(['apt-get', 'install', '-y', 'python-yaml'])
100+ else:
101+ subprocess.check_call(['apt-get', 'install', '-y', 'python3-yaml'])
102+ import yaml # flake8: noqa
103
104=== modified file 'charmhelpers/cli/__init__.py'
105--- charmhelpers/cli/__init__.py 2014-11-25 14:35:22 +0000
106+++ charmhelpers/cli/__init__.py 2014-11-25 15:09:14 +0000
107@@ -1,8 +1,9 @@
108 import inspect
109-import itertools
110 import argparse
111 import sys
112
113+from six.moves import zip
114+
115
116 class OutputFormatter(object):
117 def __init__(self, outfile=sys.stdout):
118@@ -136,7 +137,7 @@
119 if argspec.defaults:
120 positional_args = argspec.args[:-len(argspec.defaults)]
121 keyword_names = argspec.args[-len(argspec.defaults):]
122- for arg, default in itertools.izip(keyword_names, argspec.defaults):
123+ for arg, default in zip(keyword_names, argspec.defaults):
124 yield ('--{}'.format(arg),), {'default': default}
125 else:
126 positional_args = argspec.args
127
128=== modified file 'charmhelpers/contrib/amulet/deployment.py'
129--- charmhelpers/contrib/amulet/deployment.py 2014-11-25 14:35:22 +0000
130+++ charmhelpers/contrib/amulet/deployment.py 2014-11-25 15:09:14 +0000
131@@ -1,6 +1,6 @@
132 import amulet
133-
134 import os
135+import six
136
137
138 class AmuletDeployment(object):
139@@ -52,12 +52,12 @@
140
141 def _add_relations(self, relations):
142 """Add all of the relations for the services."""
143- for k, v in relations.iteritems():
144+ for k, v in six.iteritems(relations):
145 self.d.relate(k, v)
146
147 def _configure_services(self, configs):
148 """Configure all of the services."""
149- for service, config in configs.iteritems():
150+ for service, config in six.iteritems(configs):
151 self.d.configure(service, config)
152
153 def _deploy(self):
154
155=== modified file 'charmhelpers/contrib/amulet/utils.py'
156--- charmhelpers/contrib/amulet/utils.py 2014-11-25 14:35:22 +0000
157+++ charmhelpers/contrib/amulet/utils.py 2014-11-25 15:09:14 +0000
158@@ -5,6 +5,8 @@
159 import sys
160 import time
161
162+import six
163+
164
165 class AmuletUtils(object):
166 """Amulet utilities.
167@@ -58,7 +60,7 @@
168 Verify the specified services are running on the corresponding
169 service units.
170 """
171- for k, v in commands.iteritems():
172+ for k, v in six.iteritems(commands):
173 for cmd in v:
174 output, code = k.run(cmd)
175 if code != 0:
176@@ -100,11 +102,11 @@
177 longs, or can be a function that evaluate a variable and returns a
178 bool.
179 """
180- for k, v in expected.iteritems():
181+ for k, v in six.iteritems(expected):
182 if k in actual:
183- if (isinstance(v, basestring) or
184+ if (isinstance(v, six.string_types) or
185 isinstance(v, bool) or
186- isinstance(v, (int, long))):
187+ isinstance(v, six.integer_types)):
188 if v != actual[k]:
189 return "{}:{}".format(k, actual[k])
190 elif not v(actual[k]):
191
192=== modified file 'charmhelpers/contrib/charmhelpers/__init__.py'
193--- charmhelpers/contrib/charmhelpers/__init__.py 2014-11-25 14:35:22 +0000
194+++ charmhelpers/contrib/charmhelpers/__init__.py 2014-11-25 15:09:14 +0000
195@@ -8,19 +8,19 @@
196
197 __metaclass__ = type
198 __all__ = [
199- #'get_config', # core.hookenv.config()
200- #'log', # core.hookenv.log()
201- #'log_entry', # core.hookenv.log()
202- #'log_exit', # core.hookenv.log()
203- #'relation_get', # core.hookenv.relation_get()
204- #'relation_set', # core.hookenv.relation_set()
205- #'relation_ids', # core.hookenv.relation_ids()
206- #'relation_list', # core.hookenv.relation_units()
207- #'config_get', # core.hookenv.config()
208- #'unit_get', # core.hookenv.unit_get()
209- #'open_port', # core.hookenv.open_port()
210- #'close_port', # core.hookenv.close_port()
211- #'service_control', # core.host.service()
212+ # 'get_config', # core.hookenv.config()
213+ # 'log', # core.hookenv.log()
214+ # 'log_entry', # core.hookenv.log()
215+ # 'log_exit', # core.hookenv.log()
216+ # 'relation_get', # core.hookenv.relation_get()
217+ # 'relation_set', # core.hookenv.relation_set()
218+ # 'relation_ids', # core.hookenv.relation_ids()
219+ # 'relation_list', # core.hookenv.relation_units()
220+ # 'config_get', # core.hookenv.config()
221+ # 'unit_get', # core.hookenv.unit_get()
222+ # 'open_port', # core.hookenv.open_port()
223+ # 'close_port', # core.hookenv.close_port()
224+ # 'service_control', # core.host.service()
225 'unit_info', # client-side, NOT IMPLEMENTED
226 'wait_for_machine', # client-side, NOT IMPLEMENTED
227 'wait_for_page_contents', # client-side, NOT IMPLEMENTED
228@@ -31,17 +31,24 @@
229 import operator
230 import tempfile
231 import time
232-import urllib2
233 import yaml
234 import subprocess
235
236+import six
237+if six.PY3:
238+ from urllib.request import urlopen
239+ from urllib.error import (HTTPError, URLError)
240+else:
241+ from urllib2 import (urlopen, HTTPError, URLError)
242+
243+
244 SLEEP_AMOUNT = 0.1
245 # We create a juju_status Command here because it makes testing much,
246 # much easier.
247 juju_status = lambda: subprocess.check_call(['juju', 'status'])
248
249 # re-implemented as charmhelpers.fetch.configure_sources()
250-#def configure_source(update=False):
251+# def configure_source(update=False):
252 # source = config_get('source')
253 # if ((source.startswith('ppa:') or
254 # source.startswith('cloud:') or
255@@ -55,7 +62,7 @@
256
257 # DEPRECATED: client-side only
258 def make_charm_config_file(charm_config):
259- charm_config_file = tempfile.NamedTemporaryFile()
260+ charm_config_file = tempfile.NamedTemporaryFile(mode='w+')
261 charm_config_file.write(yaml.dump(charm_config))
262 charm_config_file.flush()
263 # The NamedTemporaryFile instance is returned instead of just the name
264@@ -119,7 +126,7 @@
265 # we're in LXC.
266 machine_data = get_machine_data()
267 non_zookeeper_machines = [
268- machine_data[key] for key in machine_data.keys()[1:]]
269+ machine_data[key] for key in list(machine_data.keys())[1:]]
270 if len(non_zookeeper_machines) >= num_machines:
271 all_machines_running = True
272 for machine in non_zookeeper_machines:
273@@ -170,8 +177,8 @@
274 start_time = time.time()
275 while True:
276 try:
277- stream = urllib2.urlopen(url)
278- except (urllib2.HTTPError, urllib2.URLError):
279+ stream = urlopen(url)
280+ except (HTTPError, URLError):
281 pass
282 else:
283 page = stream.read()
284
285=== modified file 'charmhelpers/contrib/hahelpers/cluster.py'
286--- charmhelpers/contrib/hahelpers/cluster.py 2014-11-25 14:35:22 +0000
287+++ charmhelpers/contrib/hahelpers/cluster.py 2014-11-25 15:09:14 +0000
288@@ -13,9 +13,10 @@
289
290 import subprocess
291 import os
292-
293 from socket import gethostname as get_unit_hostname
294
295+import six
296+
297 from charmhelpers.core.hookenv import (
298 log,
299 relation_ids,
300@@ -77,7 +78,7 @@
301 "show", resource
302 ]
303 try:
304- status = subprocess.check_output(cmd)
305+ status = subprocess.check_output(cmd).decode('UTF-8')
306 except subprocess.CalledProcessError:
307 return False
308 else:
309@@ -197,7 +198,7 @@
310 for setting in settings:
311 conf[setting] = config_get(setting)
312 missing = []
313- [missing.append(s) for s, v in conf.iteritems() if v is None]
314+ [missing.append(s) for s, v in six.iteritems(conf) if v is None]
315 if missing:
316 log('Insufficient config data to configure hacluster.', level=ERROR)
317 raise HAIncompleteConfig
318
319=== modified file 'charmhelpers/contrib/network/ip.py'
320--- charmhelpers/contrib/network/ip.py 2014-11-25 14:35:22 +0000
321+++ charmhelpers/contrib/network/ip.py 2014-11-25 15:09:14 +0000
322@@ -228,7 +228,7 @@
323 raise Exception("Interface '%s' doesn't have any %s addresses." %
324 (iface, inet_type))
325
326- return addresses
327+ return sorted(addresses)
328
329
330 get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')
331@@ -302,7 +302,7 @@
332 if global_addrs:
333 # Make sure any found global addresses are not temporary
334 cmd = ['ip', 'addr', 'show', iface]
335- out = subprocess.check_output(cmd)
336+ out = subprocess.check_output(cmd).decode('UTF-8')
337 if dynamic_only:
338 key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
339 else:
340
341=== modified file 'charmhelpers/contrib/openstack/amulet/deployment.py'
342--- charmhelpers/contrib/openstack/amulet/deployment.py 2014-11-25 14:35:22 +0000
343+++ charmhelpers/contrib/openstack/amulet/deployment.py 2014-11-25 15:09:14 +0000
344@@ -1,3 +1,4 @@
345+import six
346 from charmhelpers.contrib.amulet.deployment import (
347 AmuletDeployment
348 )
349@@ -69,7 +70,7 @@
350
351 def _configure_services(self, configs):
352 """Configure all of the services."""
353- for service, config in configs.iteritems():
354+ for service, config in six.iteritems(configs):
355 self.d.configure(service, config)
356
357 def _get_openstack_release(self):
358
359=== modified file 'charmhelpers/contrib/openstack/amulet/utils.py'
360--- charmhelpers/contrib/openstack/amulet/utils.py 2014-11-25 14:35:22 +0000
361+++ charmhelpers/contrib/openstack/amulet/utils.py 2014-11-25 15:09:14 +0000
362@@ -7,6 +7,8 @@
363 import keystoneclient.v2_0 as keystone_client
364 import novaclient.v1_1.client as nova_client
365
366+import six
367+
368 from charmhelpers.contrib.amulet.utils import (
369 AmuletUtils
370 )
371@@ -60,7 +62,7 @@
372 expected service catalog endpoints.
373 """
374 self.log.debug('actual: {}'.format(repr(actual)))
375- for k, v in expected.iteritems():
376+ for k, v in six.iteritems(expected):
377 if k in actual:
378 ret = self._validate_dict_data(expected[k][0], actual[k][0])
379 if ret:
380
381=== modified file 'charmhelpers/contrib/openstack/context.py'
382--- charmhelpers/contrib/openstack/context.py 2014-11-25 14:35:22 +0000
383+++ charmhelpers/contrib/openstack/context.py 2014-11-25 15:09:14 +0000
384@@ -1,10 +1,11 @@
385 import json
386 import os
387 import time
388-
389 from base64 import b64decode
390 from subprocess import check_call
391
392+import six
393+
394 from charmhelpers.fetch import (
395 apt_install,
396 filter_installed_packages,
397@@ -69,7 +70,7 @@
398
399 def context_complete(ctxt):
400 _missing = []
401- for k, v in ctxt.iteritems():
402+ for k, v in six.iteritems(ctxt):
403 if v is None or v == '':
404 _missing.append(k)
405
406@@ -97,7 +98,7 @@
407 split = config_flags.strip(' =').split('=')
408 limit = len(split)
409 flags = {}
410- for i in xrange(0, limit - 1):
411+ for i in range(0, limit - 1):
412 current = split[i]
413 next = split[i + 1]
414 vindex = next.rfind(',')
415@@ -375,7 +376,7 @@
416 host = format_ipv6_addr(host) or host
417 rabbitmq_hosts.append(host)
418
419- ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
420+ ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
421
422 if not context_complete(ctxt):
423 return {}
424@@ -408,7 +409,7 @@
425 ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
426 mon_hosts.append(ceph_addr)
427
428- ctxt = {'mon_hosts': ' '.join(mon_hosts),
429+ ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
430 'auth': auth,
431 'key': key,
432 'use_syslog': use_syslog}
433@@ -587,7 +588,7 @@
434 if k.startswith('ssl_key_'):
435 cns.append(k.lstrip('ssl_key_'))
436
437- return list(set(cns))
438+ return sorted(list(set(cns)))
439
440 def get_network_addresses(self):
441 """For each network configured, return corresponding address and vip
442@@ -631,10 +632,10 @@
443 else:
444 addresses.append((addr, addr))
445
446- return addresses
447+ return sorted(addresses)
448
449 def __call__(self):
450- if isinstance(self.external_ports, basestring):
451+ if isinstance(self.external_ports, six.string_types):
452 self.external_ports = [self.external_ports]
453
454 if not self.external_ports or not https():
455@@ -651,7 +652,7 @@
456 self.configure_cert(cn)
457
458 addresses = self.get_network_addresses()
459- for address, endpoint in set(addresses):
460+ for address, endpoint in sorted(set(addresses)):
461 for api_port in self.external_ports:
462 ext_port = determine_apache_port(api_port)
463 int_port = determine_api_port(api_port)
464@@ -659,7 +660,7 @@
465 ctxt['endpoints'].append(portmap)
466 ctxt['ext_ports'].append(int(ext_port))
467
468- ctxt['ext_ports'] = list(set(ctxt['ext_ports']))
469+ ctxt['ext_ports'] = sorted(list(set(ctxt['ext_ports'])))
470 return ctxt
471
472
473@@ -921,10 +922,10 @@
474 continue
475
476 sub_config = sub_config[self.config_file]
477- for k, v in sub_config.iteritems():
478+ for k, v in six.iteritems(sub_config):
479 if k == 'sections':
480- for section, config_dict in v.iteritems():
481- log("Adding section '%s'" % (section),
482+ for section, config_dict in six.iteritems(v):
483+ log("adding section '%s'" % (section),
484 level=DEBUG)
485 ctxt[k][section] = config_dict
486 else:
487
488=== modified file 'charmhelpers/contrib/openstack/neutron.py'
489--- charmhelpers/contrib/openstack/neutron.py 2014-11-17 15:15:52 +0000
490+++ charmhelpers/contrib/openstack/neutron.py 2014-11-25 15:09:14 +0000
491@@ -14,7 +14,7 @@
492 def headers_package():
493 """Ensures correct linux-headers for running kernel are installed,
494 for building DKMS package"""
495- kver = check_output(['uname', '-r']).strip()
496+ kver = check_output(['uname', '-r']).decode('UTF-8').strip()
497 return 'linux-headers-%s' % kver
498
499 QUANTUM_CONF_DIR = '/etc/quantum'
500@@ -22,7 +22,7 @@
501
502 def kernel_version():
503 """ Retrieve the current major kernel version as a tuple e.g. (3, 13) """
504- kver = check_output(['uname', '-r']).strip()
505+ kver = check_output(['uname', '-r']).decode('UTF-8').strip()
506 kver = kver.split('.')
507 return (int(kver[0]), int(kver[1]))
508
509
510=== modified file 'charmhelpers/contrib/openstack/templates/haproxy.cfg'
511--- charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-11-25 14:35:22 +0000
512+++ charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-11-25 15:09:14 +0000
513@@ -35,7 +35,7 @@
514 stats auth admin:password
515
516 {% if frontends -%}
517-{% for service, ports in service_ports.iteritems() -%}
518+{% for service, ports in service_ports.items() -%}
519 frontend tcp-in_{{ service }}
520 bind *:{{ ports[0] }}
521 bind :::{{ ports[0] }}
522@@ -46,7 +46,7 @@
523 {% for frontend in frontends -%}
524 backend {{ service }}_{{ frontend }}
525 balance leastconn
526- {% for unit, address in frontends[frontend]['backends'].iteritems() -%}
527+ {% for unit, address in frontends[frontend]['backends'].items() -%}
528 server {{ unit }} {{ address }}:{{ ports[1] }} check
529 {% endfor %}
530 {% endfor -%}
531
532=== modified file 'charmhelpers/contrib/openstack/templating.py'
533--- charmhelpers/contrib/openstack/templating.py 2014-11-25 14:35:22 +0000
534+++ charmhelpers/contrib/openstack/templating.py 2014-11-25 15:09:14 +0000
535@@ -1,13 +1,13 @@
536 import os
537
538+import six
539+
540 from charmhelpers.fetch import apt_install
541-
542 from charmhelpers.core.hookenv import (
543 log,
544 ERROR,
545 INFO
546 )
547-
548 from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES
549
550 try:
551@@ -43,7 +43,7 @@
552 order by OpenStack release.
553 """
554 tmpl_dirs = [(rel, os.path.join(templates_dir, rel))
555- for rel in OPENSTACK_CODENAMES.itervalues()]
556+ for rel in six.itervalues(OPENSTACK_CODENAMES)]
557
558 if not os.path.isdir(templates_dir):
559 log('Templates directory not found @ %s.' % templates_dir,
560@@ -258,7 +258,7 @@
561 """
562 Write out all registered config files.
563 """
564- [self.write(k) for k in self.templates.iterkeys()]
565+ [self.write(k) for k in six.iterkeys(self.templates)]
566
567 def set_release(self, openstack_release):
568 """
569@@ -275,5 +275,5 @@
570 '''
571 interfaces = []
572 [interfaces.extend(i.complete_contexts())
573- for i in self.templates.itervalues()]
574+ for i in six.itervalues(self.templates)]
575 return interfaces
576
577=== modified file 'charmhelpers/contrib/openstack/utils.py'
578--- charmhelpers/contrib/openstack/utils.py 2014-11-25 14:35:22 +0000
579+++ charmhelpers/contrib/openstack/utils.py 2014-11-25 15:09:14 +0000
580@@ -10,6 +10,8 @@
581 import socket
582 import sys
583
584+import six
585+
586 from charmhelpers.core.hookenv import (
587 config,
588 log as juju_log,
589@@ -113,7 +115,7 @@
590
591 # Best guess match based on deb string provided
592 if src.startswith('deb') or src.startswith('ppa'):
593- for k, v in OPENSTACK_CODENAMES.iteritems():
594+ for k, v in six.iteritems(OPENSTACK_CODENAMES):
595 if v in src:
596 return v
597
598@@ -134,7 +136,7 @@
599
600 def get_os_version_codename(codename):
601 '''Determine OpenStack version number from codename.'''
602- for k, v in OPENSTACK_CODENAMES.iteritems():
603+ for k, v in six.iteritems(OPENSTACK_CODENAMES):
604 if v == codename:
605 return k
606 e = 'Could not derive OpenStack version for '\
607@@ -194,7 +196,7 @@
608 else:
609 vers_map = OPENSTACK_CODENAMES
610
611- for version, cname in vers_map.iteritems():
612+ for version, cname in six.iteritems(vers_map):
613 if cname == codename:
614 return version
615 # e = "Could not determine OpenStack version for package: %s" % pkg
616@@ -318,7 +320,7 @@
617 rc_script.write(
618 "#!/bin/bash\n")
619 [rc_script.write('export %s=%s\n' % (u, p))
620- for u, p in env_vars.iteritems() if u != "script_path"]
621+ for u, p in six.iteritems(env_vars) if u != "script_path"]
622
623
624 def openstack_upgrade_available(package):
625@@ -418,7 +420,7 @@
626
627 if isinstance(address, dns.name.Name):
628 rtype = 'PTR'
629- elif isinstance(address, basestring):
630+ elif isinstance(address, six.string_types):
631 rtype = 'A'
632 else:
633 return None
634@@ -486,8 +488,7 @@
635 'hostname': json.dumps(hosts)}
636
637 if relation_prefix:
638- keys = kwargs.keys()
639- for key in keys:
640+ for key in list(kwargs.keys()):
641 kwargs["%s_%s" % (relation_prefix, key)] = kwargs[key]
642 del kwargs[key]
643
644
645=== modified file 'charmhelpers/contrib/peerstorage/__init__.py'
646--- charmhelpers/contrib/peerstorage/__init__.py 2014-11-25 14:35:22 +0000
647+++ charmhelpers/contrib/peerstorage/__init__.py 2014-11-25 15:09:14 +0000
648@@ -1,3 +1,4 @@
649+import six
650 from charmhelpers.core.hookenv import relation_id as current_relation_id
651 from charmhelpers.core.hookenv import (
652 is_relation_made,
653@@ -93,7 +94,7 @@
654 if ex in echo_data:
655 echo_data.pop(ex)
656 else:
657- for attribute, value in rdata.iteritems():
658+ for attribute, value in six.iteritems(rdata):
659 for include in includes:
660 if include in attribute:
661 echo_data[attribute] = value
662@@ -119,8 +120,8 @@
663 relation_settings=relation_settings,
664 **kwargs)
665 if is_relation_made(peer_relation_name):
666- for key, value in dict(kwargs.items() +
667- relation_settings.items()).iteritems():
668+ for key, value in six.iteritems(dict(list(kwargs.items()) +
669+ list(relation_settings.items()))):
670 key_prefix = relation_id or current_relation_id()
671 peer_store(key_prefix + delimiter + key,
672 value,
673
674=== modified file 'charmhelpers/contrib/python/packages.py'
675--- charmhelpers/contrib/python/packages.py 2014-11-25 14:35:22 +0000
676+++ charmhelpers/contrib/python/packages.py 2014-11-25 15:09:14 +0000
677@@ -15,7 +15,7 @@
678
679 def parse_options(given, available):
680 """Given a set of options, check if available"""
681- for key, value in given.items():
682+ for key, value in sorted(given.items()):
683 if key in available:
684 yield "--{0}={1}".format(key, value)
685
686
687=== modified file 'charmhelpers/contrib/ssl/__init__.py'
688--- charmhelpers/contrib/ssl/__init__.py 2014-11-25 14:35:22 +0000
689+++ charmhelpers/contrib/ssl/__init__.py 2014-11-25 15:09:14 +0000
690@@ -74,5 +74,5 @@
691 subprocess.check_call(cmd)
692 return True
693 except Exception as e:
694- print "Execution of openssl command failed:\n{}".format(e)
695+ print("Execution of openssl command failed:\n{}".format(e))
696 return False
697
698=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
699--- charmhelpers/contrib/storage/linux/ceph.py 2014-11-25 14:35:22 +0000
700+++ charmhelpers/contrib/storage/linux/ceph.py 2014-11-25 15:09:14 +0000
701@@ -65,7 +65,8 @@
702 def rbd_exists(service, pool, rbd_img):
703 """Check to see if a RADOS block device exists."""
704 try:
705- out = check_output(['rbd', 'list', '--id', service, '--pool', pool])
706+ out = check_output(['rbd', 'list', '--id',
707+ service, '--pool', pool]).decode('UTF-8')
708 except CalledProcessError:
709 return False
710
711@@ -82,7 +83,8 @@
712 def pool_exists(service, name):
713 """Check to see if a RADOS pool already exists."""
714 try:
715- out = check_output(['rados', '--id', service, 'lspools'])
716+ out = check_output(['rados', '--id', service,
717+ 'lspools']).decode('UTF-8')
718 except CalledProcessError:
719 return False
720
721@@ -96,7 +98,8 @@
722 version = ceph_version()
723 if version and version >= '0.56':
724 return json.loads(check_output(['ceph', '--id', service,
725- 'osd', 'ls', '--format=json']))
726+ 'osd', 'ls',
727+ '--format=json']).decode('UTF-8'))
728
729 return None
730
731@@ -112,7 +115,7 @@
732 # on upstream recommended best practices.
733 osds = get_osds(service)
734 if osds:
735- pgnum = (len(osds) * 100 / replicas)
736+ pgnum = (len(osds) * 100 // replicas)
737 else:
738 # NOTE(james-page): Default to 200 for older ceph versions
739 # which don't support OSD query from cli
740@@ -193,7 +196,7 @@
741 def image_mapped(name):
742 """Determine whether a RADOS block device is mapped locally."""
743 try:
744- out = check_output(['rbd', 'showmapped'])
745+ out = check_output(['rbd', 'showmapped']).decode('UTF-8')
746 except CalledProcessError:
747 return False
748
749@@ -361,7 +364,7 @@
750 """Retrieve the local version of ceph."""
751 if os.path.exists('/usr/bin/ceph'):
752 cmd = ['ceph', '-v']
753- output = check_output(cmd)
754+ output = check_output(cmd).decode('US-ASCII')
755 output = output.split()
756 if len(output) > 3:
757 return output[2]
758
759=== modified file 'charmhelpers/contrib/storage/linux/loopback.py'
760--- charmhelpers/contrib/storage/linux/loopback.py 2014-11-25 14:35:22 +0000
761+++ charmhelpers/contrib/storage/linux/loopback.py 2014-11-25 15:09:14 +0000
762@@ -1,12 +1,12 @@
763-
764 import os
765 import re
766-
767 from subprocess import (
768 check_call,
769 check_output,
770 )
771
772+import six
773+
774
775 ##################################################
776 # loopback device helpers.
777@@ -37,7 +37,7 @@
778 '''
779 file_path = os.path.abspath(file_path)
780 check_call(['losetup', '--find', file_path])
781- for d, f in loopback_devices().iteritems():
782+ for d, f in six.iteritems(loopback_devices()):
783 if f == file_path:
784 return d
785
786@@ -51,7 +51,7 @@
787
788 :returns: str: Full path to the ensured loopback device (eg, /dev/loop0)
789 '''
790- for d, f in loopback_devices().iteritems():
791+ for d, f in six.iteritems(loopback_devices()):
792 if f == path:
793 return d
794
795
796=== modified file 'charmhelpers/contrib/storage/linux/lvm.py'
797--- charmhelpers/contrib/storage/linux/lvm.py 2014-05-10 19:58:31 +0000
798+++ charmhelpers/contrib/storage/linux/lvm.py 2014-11-25 15:09:14 +0000
799@@ -61,6 +61,7 @@
800 vg = None
801 pvd = check_output(['pvdisplay', block_device]).splitlines()
802 for l in pvd:
803+ l = l.decode('UTF-8')
804 if l.strip().startswith('VG Name'):
805 vg = ' '.join(l.strip().split()[2:])
806 return vg
807
808=== modified file 'charmhelpers/contrib/storage/linux/utils.py'
809--- charmhelpers/contrib/storage/linux/utils.py 2014-07-31 08:17:42 +0000
810+++ charmhelpers/contrib/storage/linux/utils.py 2014-11-25 15:09:14 +0000
811@@ -30,7 +30,8 @@
812 # sometimes sgdisk exits non-zero; this is OK, dd will clean up
813 call(['sgdisk', '--zap-all', '--mbrtogpt',
814 '--clear', block_device])
815- dev_end = check_output(['blockdev', '--getsz', block_device])
816+ dev_end = check_output(['blockdev', '--getsz',
817+ block_device]).decode('UTF-8')
818 gpt_end = int(dev_end.split()[0]) - 100
819 check_call(['dd', 'if=/dev/zero', 'of=%s' % (block_device),
820 'bs=1M', 'count=1'])
821@@ -47,7 +48,7 @@
822 it doesn't.
823 '''
824 is_partition = bool(re.search(r".*[0-9]+\b", device))
825- out = check_output(['mount'])
826+ out = check_output(['mount']).decode('UTF-8')
827 if is_partition:
828 return bool(re.search(device + r"\b", out))
829 return bool(re.search(device + r"[0-9]+\b", out))
830
831=== modified file 'charmhelpers/contrib/templating/contexts.py'
832--- charmhelpers/contrib/templating/contexts.py 2014-11-25 14:35:22 +0000
833+++ charmhelpers/contrib/templating/contexts.py 2014-11-25 15:09:14 +0000
834@@ -6,6 +6,8 @@
835 import os
836 import yaml
837
838+import six
839+
840 import charmhelpers.core.hookenv
841
842
843@@ -92,9 +94,9 @@
844
845 # Don't use non-standard tags for unicode which will not
846 # work when salt uses yaml.load_safe.
847- yaml.add_representer(unicode, lambda dumper,
848- value: dumper.represent_scalar(
849- u'tag:yaml.org,2002:str', value))
850+ yaml.add_representer(six.text_type,
851+ lambda dumper, value: dumper.represent_scalar(
852+ six.u('tag:yaml.org,2002:str'), value))
853
854 yaml_dir = os.path.dirname(yaml_path)
855 if not os.path.exists(yaml_dir):
856
857=== modified file 'charmhelpers/core/fstab.py'
858--- charmhelpers/core/fstab.py 2014-11-25 14:35:22 +0000
859+++ charmhelpers/core/fstab.py 2014-11-25 15:09:14 +0000
860@@ -3,10 +3,11 @@
861
862 __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
863
864+import io
865 import os
866
867
868-class Fstab(file):
869+class Fstab(io.FileIO):
870 """This class extends file in order to implement a file reader/writer
871 for file `/etc/fstab`
872 """
873@@ -24,8 +25,8 @@
874 options = "defaults"
875
876 self.options = options
877- self.d = d
878- self.p = p
879+ self.d = int(d)
880+ self.p = int(p)
881
882 def __eq__(self, o):
883 return str(self) == str(o)
884@@ -45,7 +46,7 @@
885 self._path = path
886 else:
887 self._path = self.DEFAULT_PATH
888- file.__init__(self, self._path, 'r+')
889+ super(Fstab, self).__init__(self._path, 'rb+')
890
891 def _hydrate_entry(self, line):
892 # NOTE: use split with no arguments to split on any
893@@ -58,8 +59,9 @@
894 def entries(self):
895 self.seek(0)
896 for line in self.readlines():
897+ line = line.decode('us-ascii')
898 try:
899- if not line.startswith("#"):
900+ if line.strip() and not line.startswith("#"):
901 yield self._hydrate_entry(line)
902 except ValueError:
903 pass
904@@ -75,14 +77,14 @@
905 if self.get_entry_by_attr('device', entry.device):
906 return False
907
908- self.write(str(entry) + '\n')
909+ self.write((str(entry) + '\n').encode('us-ascii'))
910 self.truncate()
911 return entry
912
913 def remove_entry(self, entry):
914 self.seek(0)
915
916- lines = self.readlines()
917+ lines = [l.decode('us-ascii') for l in self.readlines()]
918
919 found = False
920 for index, line in enumerate(lines):
921@@ -97,7 +99,7 @@
922 lines.remove(line)
923
924 self.seek(0)
925- self.write(''.join(lines))
926+ self.write(''.join(lines).encode('us-ascii'))
927 self.truncate()
928 return True
929
930
931=== modified file 'charmhelpers/core/hookenv.py'
932--- charmhelpers/core/hookenv.py 2014-11-25 14:35:22 +0000
933+++ charmhelpers/core/hookenv.py 2014-11-25 15:09:14 +0000
934@@ -9,9 +9,14 @@
935 import yaml
936 import subprocess
937 import sys
938-import UserDict
939 from subprocess import CalledProcessError
940
941+import six
942+if not six.PY3:
943+ from UserDict import UserDict
944+else:
945+ from collections import UserDict
946+
947 CRITICAL = "CRITICAL"
948 ERROR = "ERROR"
949 WARNING = "WARNING"
950@@ -67,12 +72,12 @@
951 subprocess.call(command)
952
953
954-class Serializable(UserDict.IterableUserDict):
955+class Serializable(UserDict):
956 """Wrapper, an object that can be serialized to yaml or json"""
957
958 def __init__(self, obj):
959 # wrap the object
960- UserDict.IterableUserDict.__init__(self)
961+ UserDict.__init__(self)
962 self.data = obj
963
964 def __getattr__(self, attr):
965@@ -218,7 +223,7 @@
966 prev_keys = []
967 if self._prev_dict is not None:
968 prev_keys = self._prev_dict.keys()
969- return list(set(prev_keys + dict.keys(self)))
970+ return list(set(prev_keys + list(dict.keys(self))))
971
972 def load_previous(self, path=None):
973 """Load previous copy of config from disk.
974@@ -269,7 +274,7 @@
975
976 """
977 if self._prev_dict:
978- for k, v in self._prev_dict.iteritems():
979+ for k, v in six.iteritems(self._prev_dict):
980 if k not in self:
981 self[k] = v
982 with open(self.path, 'w') as f:
983@@ -284,7 +289,8 @@
984 config_cmd_line.append(scope)
985 config_cmd_line.append('--format=json')
986 try:
987- config_data = json.loads(subprocess.check_output(config_cmd_line))
988+ config_data = json.loads(
989+ subprocess.check_output(config_cmd_line).decode('UTF-8'))
990 if scope is not None:
991 return config_data
992 return Config(config_data)
993@@ -303,10 +309,10 @@
994 if unit:
995 _args.append(unit)
996 try:
997- return json.loads(subprocess.check_output(_args))
998+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
999 except ValueError:
1000 return None
1001- except CalledProcessError, e:
1002+ except CalledProcessError as e:
1003 if e.returncode == 2:
1004 return None
1005 raise
1006@@ -318,7 +324,7 @@
1007 relation_cmd_line = ['relation-set']
1008 if relation_id is not None:
1009 relation_cmd_line.extend(('-r', relation_id))
1010- for k, v in (relation_settings.items() + kwargs.items()):
1011+ for k, v in (list(relation_settings.items()) + list(kwargs.items())):
1012 if v is None:
1013 relation_cmd_line.append('{}='.format(k))
1014 else:
1015@@ -335,7 +341,8 @@
1016 relid_cmd_line = ['relation-ids', '--format=json']
1017 if reltype is not None:
1018 relid_cmd_line.append(reltype)
1019- return json.loads(subprocess.check_output(relid_cmd_line)) or []
1020+ return json.loads(
1021+ subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
1022 return []
1023
1024
1025@@ -346,7 +353,8 @@
1026 units_cmd_line = ['relation-list', '--format=json']
1027 if relid is not None:
1028 units_cmd_line.extend(('-r', relid))
1029- return json.loads(subprocess.check_output(units_cmd_line)) or []
1030+ return json.loads(
1031+ subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
1032
1033
1034 @cached
1035@@ -455,7 +463,7 @@
1036 """Get the unit ID for the remote unit"""
1037 _args = ['unit-get', '--format=json', attribute]
1038 try:
1039- return json.loads(subprocess.check_output(_args))
1040+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
1041 except ValueError:
1042 return None
1043
1044
1045=== modified file 'charmhelpers/core/host.py'
1046--- charmhelpers/core/host.py 2014-11-25 14:35:22 +0000
1047+++ charmhelpers/core/host.py 2014-11-25 15:09:14 +0000
1048@@ -14,11 +14,12 @@
1049 import subprocess
1050 import hashlib
1051 from contextlib import contextmanager
1052-
1053 from collections import OrderedDict
1054
1055-from hookenv import log
1056-from fstab import Fstab
1057+import six
1058+
1059+from .hookenv import log
1060+from .fstab import Fstab
1061
1062
1063 def service_start(service_name):
1064@@ -54,7 +55,9 @@
1065 def service_running(service):
1066 """Determine whether a system service is running"""
1067 try:
1068- output = subprocess.check_output(['service', service, 'status'], stderr=subprocess.STDOUT)
1069+ output = subprocess.check_output(
1070+ ['service', service, 'status'],
1071+ stderr=subprocess.STDOUT).decode('UTF-8')
1072 except subprocess.CalledProcessError:
1073 return False
1074 else:
1075@@ -67,7 +70,9 @@
1076 def service_available(service_name):
1077 """Determine whether a system service is available"""
1078 try:
1079- subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)
1080+ subprocess.check_output(
1081+ ['service', service_name, 'status'],
1082+ stderr=subprocess.STDOUT).decode('UTF-8')
1083 except subprocess.CalledProcessError as e:
1084 return 'unrecognized service' not in e.output
1085 else:
1086@@ -115,7 +120,7 @@
1087 cmd.append(from_path)
1088 cmd.append(to_path)
1089 log(" ".join(cmd))
1090- return subprocess.check_output(cmd).strip()
1091+ return subprocess.check_output(cmd).decode('UTF-8').strip()
1092
1093
1094 def symlink(source, destination):
1095@@ -130,7 +135,7 @@
1096 subprocess.check_call(cmd)
1097
1098
1099-def mkdir(path, owner='root', group='root', perms=0555, force=False):
1100+def mkdir(path, owner='root', group='root', perms=0o555, force=False):
1101 """Create a directory"""
1102 log("Making dir {} {}:{} {:o}".format(path, owner, group,
1103 perms))
1104@@ -146,7 +151,7 @@
1105 os.chown(realpath, uid, gid)
1106
1107
1108-def write_file(path, content, owner='root', group='root', perms=0444):
1109+def write_file(path, content, owner='root', group='root', perms=0o444):
1110 """Create or overwrite a file with the contents of a string"""
1111 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
1112 uid = pwd.getpwnam(owner).pw_uid
1113@@ -177,7 +182,7 @@
1114 cmd_args.extend([device, mountpoint])
1115 try:
1116 subprocess.check_output(cmd_args)
1117- except subprocess.CalledProcessError, e:
1118+ except subprocess.CalledProcessError as e:
1119 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
1120 return False
1121
1122@@ -191,7 +196,7 @@
1123 cmd_args = ['umount', mountpoint]
1124 try:
1125 subprocess.check_output(cmd_args)
1126- except subprocess.CalledProcessError, e:
1127+ except subprocess.CalledProcessError as e:
1128 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
1129 return False
1130
1131@@ -218,8 +223,8 @@
1132 """
1133 if os.path.exists(path):
1134 h = getattr(hashlib, hash_type)()
1135- with open(path, 'r') as source:
1136- h.update(source.read()) # IGNORE:E1101 - it does have update
1137+ with open(path, 'rb') as source:
1138+ h.update(source.read())
1139 return h.hexdigest()
1140 else:
1141 return None
1142@@ -297,7 +302,7 @@
1143 if length is None:
1144 length = random.choice(range(35, 45))
1145 alphanumeric_chars = [
1146- l for l in (string.letters + string.digits)
1147+ l for l in (string.ascii_letters + string.digits)
1148 if l not in 'l0QD1vAEIOUaeiou']
1149 random_chars = [
1150 random.choice(alphanumeric_chars) for _ in range(length)]
1151@@ -306,14 +311,14 @@
1152
1153 def list_nics(nic_type):
1154 '''Return a list of nics of given type(s)'''
1155- if isinstance(nic_type, basestring):
1156+ if isinstance(nic_type, six.string_types):
1157 int_types = [nic_type]
1158 else:
1159 int_types = nic_type
1160 interfaces = []
1161 for int_type in int_types:
1162 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
1163- ip_output = subprocess.check_output(cmd).split('\n')
1164+ ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
1165 ip_output = (line for line in ip_output if line)
1166 for line in ip_output:
1167 if line.split()[1].startswith(int_type):
1168@@ -335,7 +340,7 @@
1169
1170 def get_nic_mtu(nic):
1171 cmd = ['ip', 'addr', 'show', nic]
1172- ip_output = subprocess.check_output(cmd).split('\n')
1173+ ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
1174 mtu = ""
1175 for line in ip_output:
1176 words = line.split()
1177@@ -346,7 +351,7 @@
1178
1179 def get_nic_hwaddr(nic):
1180 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
1181- ip_output = subprocess.check_output(cmd)
1182+ ip_output = subprocess.check_output(cmd).decode('UTF-8')
1183 hwaddr = ""
1184 words = ip_output.split()
1185 if 'link/ether' in words:
1186
1187=== modified file 'charmhelpers/core/services/helpers.py'
1188--- charmhelpers/core/services/helpers.py 2014-11-25 14:35:22 +0000
1189+++ charmhelpers/core/services/helpers.py 2014-11-25 15:09:14 +0000
1190@@ -196,7 +196,7 @@
1191 if not os.path.isabs(file_name):
1192 file_name = os.path.join(hookenv.charm_dir(), file_name)
1193 with open(file_name, 'w') as file_stream:
1194- os.fchmod(file_stream.fileno(), 0600)
1195+ os.fchmod(file_stream.fileno(), 0o600)
1196 yaml.dump(config_data, file_stream)
1197
1198 def read_context(self, file_name):
1199@@ -211,15 +211,19 @@
1200
1201 class TemplateCallback(ManagerCallback):
1202 """
1203- Callback class that will render a Jinja2 template, for use as a ready action.
1204-
1205- :param str source: The template source file, relative to `$CHARM_DIR/templates`
1206+ Callback class that will render a Jinja2 template, for use as a ready
1207+ action.
1208+
1209+ :param str source: The template source file, relative to
1210+ `$CHARM_DIR/templates`
1211+
1212 :param str target: The target to write the rendered template to
1213 :param str owner: The owner of the rendered file
1214 :param str group: The group of the rendered file
1215 :param int perms: The permissions of the rendered file
1216 """
1217- def __init__(self, source, target, owner='root', group='root', perms=0444):
1218+ def __init__(self, source, target,
1219+ owner='root', group='root', perms=0o444):
1220 self.source = source
1221 self.target = target
1222 self.owner = owner
1223
1224=== modified file 'charmhelpers/core/templating.py'
1225--- charmhelpers/core/templating.py 2014-11-25 14:35:22 +0000
1226+++ charmhelpers/core/templating.py 2014-11-25 15:09:14 +0000
1227@@ -4,7 +4,8 @@
1228 from charmhelpers.core import hookenv
1229
1230
1231-def render(source, target, context, owner='root', group='root', perms=0444, templates_dir=None):
1232+def render(source, target, context, owner='root', group='root',
1233+ perms=0o444, templates_dir=None):
1234 """
1235 Render a template.
1236
1237
1238=== modified file 'charmhelpers/fetch/__init__.py'
1239--- charmhelpers/fetch/__init__.py 2014-11-25 14:35:22 +0000
1240+++ charmhelpers/fetch/__init__.py 2014-11-25 15:09:14 +0000
1241@@ -5,10 +5,6 @@
1242 from charmhelpers.core.host import (
1243 lsb_release
1244 )
1245-from urlparse import (
1246- urlparse,
1247- urlunparse,
1248-)
1249 import subprocess
1250 from charmhelpers.core.hookenv import (
1251 config,
1252@@ -16,6 +12,12 @@
1253 )
1254 import os
1255
1256+import six
1257+if six.PY3:
1258+ from urllib.parse import urlparse, urlunparse
1259+else:
1260+ from urlparse import urlparse, urlunparse
1261+
1262
1263 CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
1264 deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
1265@@ -149,7 +151,7 @@
1266 cmd = ['apt-get', '--assume-yes']
1267 cmd.extend(options)
1268 cmd.append('install')
1269- if isinstance(packages, basestring):
1270+ if isinstance(packages, six.string_types):
1271 cmd.append(packages)
1272 else:
1273 cmd.extend(packages)
1274@@ -182,7 +184,7 @@
1275 def apt_purge(packages, fatal=False):
1276 """Purge one or more packages"""
1277 cmd = ['apt-get', '--assume-yes', 'purge']
1278- if isinstance(packages, basestring):
1279+ if isinstance(packages, six.string_types):
1280 cmd.append(packages)
1281 else:
1282 cmd.extend(packages)
1283@@ -193,7 +195,7 @@
1284 def apt_hold(packages, fatal=False):
1285 """Hold one or more packages"""
1286 cmd = ['apt-mark', 'hold']
1287- if isinstance(packages, basestring):
1288+ if isinstance(packages, six.string_types):
1289 cmd.append(packages)
1290 else:
1291 cmd.extend(packages)
1292@@ -260,7 +262,7 @@
1293
1294 if key:
1295 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
1296- with NamedTemporaryFile() as key_file:
1297+ with NamedTemporaryFile('w+') as key_file:
1298 key_file.write(key)
1299 key_file.flush()
1300 key_file.seek(0)
1301@@ -297,14 +299,14 @@
1302 sources = safe_load((config(sources_var) or '').strip()) or []
1303 keys = safe_load((config(keys_var) or '').strip()) or None
1304
1305- if isinstance(sources, basestring):
1306+ if isinstance(sources, six.string_types):
1307 sources = [sources]
1308
1309 if keys is None:
1310 for source in sources:
1311 add_source(source, None)
1312 else:
1313- if isinstance(keys, basestring):
1314+ if isinstance(keys, six.string_types):
1315 keys = [keys]
1316
1317 if len(sources) != len(keys):
1318@@ -401,7 +403,7 @@
1319 while result is None or result == APT_NO_LOCK:
1320 try:
1321 result = subprocess.check_call(cmd, env=env)
1322- except subprocess.CalledProcessError, e:
1323+ except subprocess.CalledProcessError as e:
1324 retry_count = retry_count + 1
1325 if retry_count > APT_NO_LOCK_RETRY_COUNT:
1326 raise
1327
1328=== modified file 'charmhelpers/fetch/archiveurl.py'
1329--- charmhelpers/fetch/archiveurl.py 2014-11-25 14:35:22 +0000
1330+++ charmhelpers/fetch/archiveurl.py 2014-11-25 15:09:14 +0000
1331@@ -1,8 +1,23 @@
1332 import os
1333-import urllib2
1334-from urllib import urlretrieve
1335-import urlparse
1336 import hashlib
1337+import re
1338+
1339+import six
1340+if six.PY3:
1341+ from urllib.request import (
1342+ build_opener, install_opener, urlopen, urlretrieve,
1343+ HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
1344+ )
1345+ from urllib.parse import urlparse, urlunparse, parse_qs
1346+ from urllib.error import URLError
1347+else:
1348+ from urllib import urlretrieve
1349+ from urllib2 import (
1350+ build_opener, install_opener, urlopen,
1351+ HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
1352+ URLError
1353+ )
1354+ from urlparse import urlparse, urlunparse, parse_qs
1355
1356 from charmhelpers.fetch import (
1357 BaseFetchHandler,
1358@@ -15,6 +30,24 @@
1359 from charmhelpers.core.host import mkdir, check_hash
1360
1361
1362+def splituser(host):
1363+ '''urllib.splituser(), but six's support of this seems broken'''
1364+ _userprog = re.compile('^(.*)@(.*)$')
1365+ match = _userprog.match(host)
1366+ if match:
1367+ return match.group(1, 2)
1368+ return None, host
1369+
1370+
1371+def splitpasswd(user):
1372+ '''urllib.splitpasswd(), but six's support of this is missing'''
1373+ _passwdprog = re.compile('^([^:]*):(.*)$', re.S)
1374+ match = _passwdprog.match(user)
1375+ if match:
1376+ return match.group(1, 2)
1377+ return user, None
1378+
1379+
1380 class ArchiveUrlFetchHandler(BaseFetchHandler):
1381 """
1382 Handler to download archive files from arbitrary URLs.
1383@@ -42,20 +75,20 @@
1384 """
1385 # propogate all exceptions
1386 # URLError, OSError, etc
1387- proto, netloc, path, params, query, fragment = urlparse.urlparse(source)
1388+ proto, netloc, path, params, query, fragment = urlparse(source)
1389 if proto in ('http', 'https'):
1390- auth, barehost = urllib2.splituser(netloc)
1391+ auth, barehost = splituser(netloc)
1392 if auth is not None:
1393- source = urlparse.urlunparse((proto, barehost, path, params, query, fragment))
1394- username, password = urllib2.splitpasswd(auth)
1395- passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
1396+ source = urlunparse((proto, barehost, path, params, query, fragment))
1397+ username, password = splitpasswd(auth)
1398+ passman = HTTPPasswordMgrWithDefaultRealm()
1399 # Realm is set to None in add_password to force the username and password
1400 # to be used whatever the realm
1401 passman.add_password(None, source, username, password)
1402- authhandler = urllib2.HTTPBasicAuthHandler(passman)
1403- opener = urllib2.build_opener(authhandler)
1404- urllib2.install_opener(opener)
1405- response = urllib2.urlopen(source)
1406+ authhandler = HTTPBasicAuthHandler(passman)
1407+ opener = build_opener(authhandler)
1408+ install_opener(opener)
1409+ response = urlopen(source)
1410 try:
1411 with open(dest, 'w') as dest_file:
1412 dest_file.write(response.read())
1413@@ -91,17 +124,21 @@
1414 url_parts = self.parse_url(source)
1415 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
1416 if not os.path.exists(dest_dir):
1417- mkdir(dest_dir, perms=0755)
1418+ mkdir(dest_dir, perms=0o755)
1419 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
1420 try:
1421 self.download(source, dld_file)
1422- except urllib2.URLError as e:
1423+ except URLError as e:
1424 raise UnhandledSource(e.reason)
1425 except OSError as e:
1426 raise UnhandledSource(e.strerror)
1427- options = urlparse.parse_qs(url_parts.fragment)
1428+ options = parse_qs(url_parts.fragment)
1429 for key, value in options.items():
1430- if key in hashlib.algorithms:
1431+ if not six.PY3:
1432+ algorithms = hashlib.algorithms
1433+ else:
1434+ algorithms = hashlib.algorithms_available
1435+ if key in algorithms:
1436 check_hash(dld_file, value, key)
1437 if checksum:
1438 check_hash(dld_file, checksum, hash_type)
1439
1440=== modified file 'charmhelpers/fetch/bzrurl.py'
1441--- charmhelpers/fetch/bzrurl.py 2014-11-25 14:35:22 +0000
1442+++ charmhelpers/fetch/bzrurl.py 2014-11-25 15:09:14 +0000
1443@@ -5,6 +5,10 @@
1444 )
1445 from charmhelpers.core.host import mkdir
1446
1447+import six
1448+if six.PY3:
1449+ raise ImportError('bzrlib does not support Python3')
1450+
1451 try:
1452 from bzrlib.branch import Branch
1453 except ImportError:
1454@@ -42,7 +46,7 @@
1455 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
1456 branch_name)
1457 if not os.path.exists(dest_dir):
1458- mkdir(dest_dir, perms=0755)
1459+ mkdir(dest_dir, perms=0o755)
1460 try:
1461 self.branch(source, dest_dir)
1462 except OSError as e:
1463
1464=== modified file 'charmhelpers/fetch/giturl.py'
1465--- charmhelpers/fetch/giturl.py 2014-11-25 14:35:22 +0000
1466+++ charmhelpers/fetch/giturl.py 2014-11-25 15:09:14 +0000
1467@@ -5,6 +5,10 @@
1468 )
1469 from charmhelpers.core.host import mkdir
1470
1471+import six
1472+if six.PY3:
1473+ raise ImportError('GitPython does not support Python 3')
1474+
1475 try:
1476 from git import Repo
1477 except ImportError:
1478@@ -17,7 +21,7 @@
1479 """Handler for git branches via generic and github URLs"""
1480 def can_handle(self, source):
1481 url_parts = self.parse_url(source)
1482- #TODO (mattyw) no support for ssh git@ yet
1483+ # TODO (mattyw) no support for ssh git@ yet
1484 if url_parts.scheme not in ('http', 'https', 'git'):
1485 return False
1486 else:
1487@@ -36,7 +40,7 @@
1488 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
1489 branch_name)
1490 if not os.path.exists(dest_dir):
1491- mkdir(dest_dir, perms=0755)
1492+ mkdir(dest_dir, perms=0o755)
1493 try:
1494 self.clone(source, dest_dir, branch)
1495 except OSError as e:
1496
1497=== modified file 'test_requirements.txt'
1498--- test_requirements.txt 2014-11-25 14:35:22 +0000
1499+++ test_requirements.txt 2014-11-25 15:09:14 +0000
1500@@ -1,18 +1,17 @@
1501-coverage==3.6
1502-launchpadlib==1.10.2
1503---allow-external launchpadlib
1504---allow-unverified launchpadlib
1505-mock==1.0.1
1506-netaddr
1507-nose==1.3.1
1508-PyYAML==3.10
1509-simplejson==3.3.0
1510-testtools
1511-Tempita==0.5.1
1512-bzr+http://bazaar.launchpad.net/~yellow/python-shelltoolbox/trunk@17#egg=shelltoolbox
1513-http://alastairs-place.net/projects/netifaces/netifaces-0.6.tar.gz
1514-bzr==2.6.0
1515-GitPython>=0.3.2.RC1
1516-Jinja2==2.7.2
1517---allow-external lazr.authentication
1518---allow-unverified lazr.authentication
1519+# Test-only dependencies are unpinned.
1520+#
1521+pip
1522+distribute
1523+coverage>=3.6
1524+mock>=1.0.1
1525+nose>=1.3.1
1526+flake8
1527+testtools==0.9.14 # Before dependent on modern 'six'
1528+#
1529+# Specify precise versions of runtime dependencies where possible.
1530+netaddr==0.7.10 # trusty. precise is 0.7.5, but not in pypi.
1531+PyYAML==3.10 # precise
1532+Tempita==0.5.1 # precise
1533+netifaces==0.10 # trusty is 0.8, but using py3 compatible version for tests.
1534+Jinja2==2.6 # precise
1535+six==1.1 # precise
1536
1537=== modified file 'tests/cli/test_cmdline.py'
1538--- tests/cli/test_cmdline.py 2014-11-25 14:35:22 +0000
1539+++ tests/cli/test_cmdline.py 2014-11-25 15:09:14 +0000
1540@@ -6,15 +6,13 @@
1541 patch,
1542 MagicMock,
1543 )
1544-try:
1545- from cStringIO import StringIO
1546-except ImportError:
1547- from StringIO import StringIO
1548 import json
1549 from pprint import pformat
1550 import yaml
1551 import csv
1552
1553+from six import StringIO
1554+
1555 from charmhelpers import cli
1556
1557
1558@@ -116,7 +114,8 @@
1559 self.output_data = {"this": "is", "some": 1, "data": dict()}
1560
1561 def test_supports_formats(self):
1562- self.assertItemsEqual(self.expected_formats, self.of.supported_formats)
1563+ self.assertEqual(sorted(self.expected_formats),
1564+ sorted(self.of.supported_formats))
1565
1566 def test_adds_arguments(self):
1567 ap = MagicMock()
1568@@ -130,11 +129,12 @@
1569
1570 for call_args in add_arg.call_args_list:
1571 if "--format" in call_args[0]:
1572- self.assertItemsEqual(call_args[1]['choices'], self.expected_formats)
1573+ self.assertEqual(sorted(call_args[1]['choices']),
1574+ sorted(self.expected_formats))
1575 self.assertEqual(call_args[1]['default'], 'raw')
1576 break
1577 else:
1578- print arg_group.call_args_list
1579+ print(arg_group.call_args_list)
1580 self.fail("No --format argument was created")
1581
1582 all_args = [c[0][0] for c in add_arg.call_args_list]
1583
1584=== modified file 'tests/cli/test_function_signature_analysis.py'
1585--- tests/cli/test_function_signature_analysis.py 2013-08-21 21:40:07 +0000
1586+++ tests/cli/test_function_signature_analysis.py 2014-11-25 15:09:14 +0000
1587@@ -1,7 +1,7 @@
1588 """Tests for the commandant code that analyzes a function signature to
1589 determine the parameters to argparse."""
1590
1591-from testtools import TestCase, matchers
1592+from testtools import TestCase
1593
1594 from charmhelpers import cli
1595
1596@@ -42,5 +42,5 @@
1597 args = cli.describe_arguments(lambda x, y=3, *z, **missing: False)
1598 for opts, _ in args:
1599 # opts should be ('varname',) at this point
1600- self.assertThat(opts, matchers.HasLength(1))
1601+ self.assertTrue(len(opts) == 1)
1602 self.assertNotIn('missing', opts)
1603
1604=== modified file 'tests/contrib/charmhelpers/test_charmhelpers.py'
1605--- tests/contrib/charmhelpers/test_charmhelpers.py 2014-11-25 14:35:22 +0000
1606+++ tests/contrib/charmhelpers/test_charmhelpers.py 2014-11-25 15:09:14 +0000
1607@@ -2,10 +2,10 @@
1608
1609 import unittest
1610 import yaml
1611-
1612-from StringIO import StringIO
1613 from testtools import TestCase
1614
1615+from six import StringIO
1616+
1617 import sys
1618 # Path hack to ensure we test the local code, not a version installed in
1619 # /usr/local/lib. This is necessary since /usr/local/lib is prepended before
1620@@ -265,11 +265,11 @@
1621 # wait_for_page_contents() will wait until a given string is
1622 # contained within the results of a given url and will return
1623 # once it does.
1624- # We need to patch the charmhelpers instance of urllib2 so that
1625+ # We need to patch the charmhelpers instance of urlopen so that
1626 # it doesn't try to connect out.
1627 test_content = "Hello, world."
1628 new_urlopen = lambda *args: StringIO(test_content)
1629- self.patch(charmhelpers.urllib2, 'urlopen', new_urlopen)
1630+ self.patch(charmhelpers, 'urlopen', new_urlopen)
1631 charmhelpers.wait_for_page_contents(
1632 'http://example.com', test_content, timeout=0)
1633
1634@@ -277,10 +277,10 @@
1635 # If the desired contents do not appear within the page before
1636 # the specified timeout, wait_for_page_contents() will raise a
1637 # RuntimeError.
1638- # We need to patch the charmhelpers instance of urllib2 so that
1639+ # We need to patch the charmhelpers instance of urlopen so that
1640 # it doesn't try to connect out.
1641 new_urlopen = lambda *args: StringIO("This won't work.")
1642- self.patch(charmhelpers.urllib2, 'urlopen', new_urlopen)
1643+ self.patch(charmhelpers, 'urlopen', new_urlopen)
1644 self.assertRaises(
1645 RuntimeError, charmhelpers.wait_for_page_contents,
1646 'http://example.com', "This will error", timeout=0)
1647
1648=== modified file 'tests/contrib/hahelpers/test_cluster_utils.py'
1649--- tests/contrib/hahelpers/test_cluster_utils.py 2014-09-24 09:42:52 +0000
1650+++ tests/contrib/hahelpers/test_cluster_utils.py 2014-11-25 15:09:14 +0000
1651@@ -44,7 +44,7 @@
1652 def test_is_crm_leader(self, check_output):
1653 '''It determines its unit is leader'''
1654 self.get_unit_hostname.return_value = 'node1'
1655- crm = 'resource vip is running on: node1'
1656+ crm = b'resource vip is running on: node1'
1657 check_output.return_value = crm
1658 self.assertTrue(cluster_utils.is_crm_leader('vip'))
1659
1660@@ -52,7 +52,7 @@
1661 def test_is_not_leader(self, check_output):
1662 '''It determines its unit is not leader'''
1663 self.get_unit_hostname.return_value = 'node1'
1664- crm = 'resource vip is running on: node2'
1665+ crm = b'resource vip is running on: node2'
1666 check_output.return_value = crm
1667 self.assertFalse(cluster_utils.is_crm_leader('some_resource'))
1668
1669
1670=== modified file 'tests/contrib/network/test_ip.py'
1671--- tests/contrib/network/test_ip.py 2014-11-25 14:35:22 +0000
1672+++ tests/contrib/network/test_ip.py 2014-11-25 15:09:14 +0000
1673@@ -59,7 +59,7 @@
1674 },
1675 }
1676
1677-IP_OUTPUT = """link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
1678+IP_OUTPUT = b"""link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
1679 inet 10.5.16.93/16 brd 10.5.255.255 scope global eth0
1680 valid_lft forever preferred_lft forever
1681 inet6 2001:db8:1:0:d0cf:528c:23eb:6000/64 scope global
1682@@ -72,7 +72,7 @@
1683 valid_lft forever preferred_lft forever
1684 """
1685
1686-IP_OUTPUT_NO_VALID = """link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
1687+IP_OUTPUT_NO_VALID = b"""link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
1688 inet 10.5.16.93/16 brd 10.5.255.255 scope global eth0
1689 valid_lft forever preferred_lft forever
1690 inet6 2001:db8:1:0:2918:3444:852:5b8a/64 scope global temporary dynamic
1691@@ -100,13 +100,14 @@
1692 return DUMMY_ADDRESSES[iface]
1693
1694 with mock.patch.object(netifaces, 'interfaces') as interfaces:
1695- interfaces.return_value = DUMMY_ADDRESSES.keys()
1696+ interfaces.return_value = sorted(DUMMY_ADDRESSES.keys())
1697 with mock.patch.object(netifaces, 'ifaddresses') as ifaddresses:
1698 ifaddresses.side_effect = side_effect
1699 if not fatal:
1700 self.assertEqual(expect_ip_addr,
1701- net_ip.get_address_in_network(
1702- network, fallback, fatal))
1703+ net_ip.get_address_in_network(network,
1704+ fallback,
1705+ fatal))
1706 else:
1707 net_ip.get_address_in_network(network, fallback, fatal)
1708
1709@@ -122,7 +123,7 @@
1710 None, None, fatal=True)
1711
1712 def test_get_address_in_network_ipv4(self):
1713- self._test_get_address_in_network('192.168.1.56', '192.168.1.0/24')
1714+ self._test_get_address_in_network('192.168.1.55', '192.168.1.0/24')
1715
1716 def test_get_address_in_network_ipv6(self):
1717 self._test_get_address_in_network('2a01:348:2f4:0:685e:5748:ae62:209f',
1718@@ -260,7 +261,7 @@
1719 mock_get_iface_from_addr):
1720 mock_get_iface_from_addr.return_value = 'eth0'
1721 mock_check_out.return_value = \
1722- "inet6 2a01:348:2f4:0:685e:5748:ae62:209f/64 scope global dynamic"
1723+ b"inet6 2a01:348:2f4:0:685e:5748:ae62:209f/64 scope global dynamic"
1724 _interfaces.return_value = DUMMY_ADDRESSES.keys()
1725 _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
1726 result = net_ip.get_ipv6_addr(dynamic_only=False)
1727@@ -275,7 +276,7 @@
1728 mock_get_iface_from_addr):
1729 mock_get_iface_from_addr.return_value = 'eth0'
1730 mock_check_out.return_value = \
1731- "inet6 2a01:348:2f4:0:685e:5748:ae62:209f/64 scope global dynamic"
1732+ b"inet6 2a01:348:2f4:0:685e:5748:ae62:209f/64 scope global dynamic"
1733 _interfaces.return_value = DUMMY_ADDRESSES.keys()
1734 _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
1735 result = net_ip.get_ipv6_addr(dynamic_only=False)
1736@@ -494,9 +495,9 @@
1737 def test_get_iface_from_addr(self, mock_log, mock_ifaddresses,
1738 mock_interfaces):
1739 mock_ifaddresses.side_effect = lambda iface: DUMMY_ADDRESSES[iface]
1740- mock_interfaces.return_value = DUMMY_ADDRESSES.keys()
1741+ mock_interfaces.return_value = sorted(DUMMY_ADDRESSES.keys())
1742 addr = 'fe80::3e97:eff:fe8b:1cf7'
1743- self.assertEqual(net_ip.get_iface_from_addr(addr), 'eth1')
1744+ self.assertEqual(net_ip.get_iface_from_addr(addr), 'eth0')
1745
1746 with nose.tools.assert_raises(Exception):
1747 net_ip.get_iface_from_addr('1.2.3.4')
1748
1749=== modified file 'tests/contrib/openstack/test_neutron_utils.py'
1750--- tests/contrib/openstack/test_neutron_utils.py 2014-11-07 09:54:33 +0000
1751+++ tests/contrib/openstack/test_neutron_utils.py 2014-11-25 15:09:14 +0000
1752@@ -23,12 +23,12 @@
1753 return mock
1754
1755 def test_headers_package(self):
1756- self.check_output.return_value = '3.13.0-19-generic'
1757+ self.check_output.return_value = b'3.13.0-19-generic'
1758 kname = neutron.headers_package()
1759 self.assertEquals(kname, 'linux-headers-3.13.0-19-generic')
1760
1761 def test_kernel_version(self):
1762- self.check_output.return_value = '3.13.0-19-generic'
1763+ self.check_output.return_value = b'3.13.0-19-generic'
1764 kver_maj, kver_min = neutron.kernel_version()
1765 self.assertEquals((kver_maj, kver_min), (3, 13))
1766
1767
1768=== modified file 'tests/contrib/openstack/test_openstack_utils.py'
1769--- tests/contrib/openstack/test_openstack_utils.py 2014-11-25 14:35:22 +0000
1770+++ tests/contrib/openstack/test_openstack_utils.py 2014-11-25 15:09:14 +0000
1771@@ -1,13 +1,22 @@
1772+import io
1773 import os
1774 import subprocess
1775 import unittest
1776-
1777 from copy import copy
1778 from testtools import TestCase
1779 from mock import MagicMock, patch, call
1780
1781 import charmhelpers.contrib.openstack.utils as openstack
1782
1783+import six
1784+
1785+if not six.PY3:
1786+ builtin_open = '__builtin__.open'
1787+ builtin_import = '__builtin__.__import__'
1788+else:
1789+ builtin_open = 'builtins.open'
1790+ builtin_import = 'builtins.__import__'
1791+
1792 # mocked return of openstack.lsb_release()
1793 FAKE_RELEASE = {
1794 'DISTRIB_CODENAME': 'precise',
1795@@ -222,7 +231,7 @@
1796 '''Test deriving OpenStack codename from an installed package'''
1797 with patch('apt_pkg.Cache') as cache:
1798 cache.return_value = self._apt_cache()
1799- for pkg, vers in FAKE_REPO.iteritems():
1800+ for pkg, vers in six.iteritems(FAKE_REPO):
1801 # test fake repo for all "installed" packages
1802 if pkg.startswith('bad-'):
1803 continue
1804@@ -291,7 +300,7 @@
1805 '''Test deriving OpenStack version from an installed package'''
1806 with patch('apt_pkg.Cache') as cache:
1807 cache.return_value = self._apt_cache()
1808- for pkg, vers in FAKE_REPO.iteritems():
1809+ for pkg, vers in six.iteritems(FAKE_REPO):
1810 if pkg.startswith('bad-'):
1811 continue
1812 if 'pkg_vers' not in vers:
1813@@ -354,12 +363,12 @@
1814 ex_cmd = ['add-apt-repository', '-y', 'ppa:gandelman-a/openstack']
1815 mock.assert_called_with(ex_cmd)
1816
1817- @patch('__builtin__.open')
1818+ @patch(builtin_open)
1819 @patch('charmhelpers.contrib.openstack.utils.juju_log')
1820 @patch('charmhelpers.contrib.openstack.utils.import_key')
1821 def test_configure_install_source_deb_url(self, _import, _log, _open):
1822 '''Test configuring installation source from deb repo url'''
1823- _file = MagicMock(spec=file)
1824+ _file = MagicMock(spec=io.FileIO)
1825 _open.return_value = _file
1826 src = ('deb http://ubuntu-cloud.archive.canonical.com/ubuntu '
1827 'precise-havana main|KEYID')
1828@@ -372,12 +381,12 @@
1829 _file.__enter__().write.assert_called_with(src)
1830
1831 @patch('charmhelpers.contrib.openstack.utils.lsb_release')
1832- @patch('__builtin__.open')
1833+ @patch(builtin_open)
1834 @patch('charmhelpers.contrib.openstack.utils.juju_log')
1835 def test_configure_install_source_distro_proposed(self, _log, _open, _lsb):
1836 '''Test configuring installation source from deb repo url'''
1837 _lsb.return_value = FAKE_RELEASE
1838- _file = MagicMock(spec=file)
1839+ _file = MagicMock(spec=io.FileIO)
1840 _open.return_value = _file
1841 openstack.configure_installation_source('distro-proposed')
1842 src = ('deb http://archive.ubuntu.com/ubuntu/ precise-proposed '
1843@@ -402,13 +411,13 @@
1844 'ppa:ubuntu-cloud-archive/folsom-staging']
1845 _subp.assert_called_with(cmd)
1846
1847- @patch('__builtin__.open')
1848+ @patch(builtin_open)
1849 @patch('charmhelpers.contrib.openstack.utils.apt_install')
1850 @patch('charmhelpers.contrib.openstack.utils.lsb_release')
1851 def test_configure_install_source_uca_repos(self, _lsb, _install, _open):
1852 '''Test configuring installation source from UCA sources'''
1853 _lsb.return_value = FAKE_RELEASE
1854- _file = MagicMock(spec=file)
1855+ _file = MagicMock(spec=io.FileIO)
1856 _open.return_value = _file
1857 for src, url in UCA_SOURCES:
1858 openstack.configure_installation_source(src)
1859@@ -455,13 +464,13 @@
1860 @patch('os.mkdir')
1861 @patch('os.path.exists')
1862 @patch('charmhelpers.contrib.openstack.utils.charm_dir')
1863- @patch('__builtin__.open')
1864+ @patch(builtin_open)
1865 def test_save_scriptrc(self, _open, _charm_dir, _exists, _mkdir):
1866 '''Test generation of scriptrc from environment'''
1867 scriptrc = ['#!/bin/bash\n',
1868 'export setting1=foo\n',
1869 'export setting2=bar\n']
1870- _file = MagicMock(spec=file)
1871+ _file = MagicMock(spec=io.FileIO)
1872 _open.return_value = _file
1873 _charm_dir.return_value = '/var/lib/juju/units/testing-foo-0/charm'
1874 _exists.return_value = False
1875@@ -583,21 +592,21 @@
1876 @patch.object(openstack, 'apt_install')
1877 def test_get_host_ip_with_hostname(self, apt_install):
1878 fake_dns = FakeDNS('10.0.0.1')
1879- with patch('__builtin__.__import__', side_effect=[fake_dns]):
1880+ with patch(builtin_import, side_effect=[fake_dns]):
1881 ip = openstack.get_host_ip('www.ubuntu.com')
1882 self.assertEquals(ip, '10.0.0.1')
1883
1884 @patch.object(openstack, 'apt_install')
1885 def test_get_host_ip_with_ip(self, apt_install):
1886 fake_dns = FakeDNS('5.5.5.5')
1887- with patch('__builtin__.__import__', side_effect=[fake_dns]):
1888+ with patch(builtin_import, side_effect=[fake_dns]):
1889 ip = openstack.get_host_ip('4.2.2.1')
1890 self.assertEquals(ip, '4.2.2.1')
1891
1892 @patch.object(openstack, 'apt_install')
1893 def test_ns_query_trigger_apt_install(self, apt_install):
1894 fake_dns = FakeDNS('5.5.5.5')
1895- with patch('__builtin__.__import__', side_effect=[ImportError, fake_dns]):
1896+ with patch(builtin_import, side_effect=[ImportError, fake_dns]):
1897 nsq = openstack.ns_query('5.5.5.5')
1898 apt_install.assert_called_with('python-dnspython')
1899 self.assertEquals(nsq, '5.5.5.5')
1900@@ -605,7 +614,7 @@
1901 @patch.object(openstack, 'apt_install')
1902 def test_ns_query_ptr_record(self, apt_install):
1903 fake_dns = FakeDNS('127.0.0.1')
1904- with patch('__builtin__.__import__', side_effect=[fake_dns]):
1905+ with patch(builtin_import, side_effect=[fake_dns]):
1906 nsq = openstack.ns_query('127.0.0.1')
1907 self.assertEquals(nsq, '127.0.0.1')
1908
1909@@ -613,35 +622,35 @@
1910 def test_ns_query_a_record(self, apt_install):
1911 fake_dns = FakeDNS('127.0.0.1')
1912 fake_dns_name = FakeDNSName('www.somedomain.tld')
1913- with patch('__builtin__.__import__', side_effect=[fake_dns]):
1914+ with patch(builtin_import, side_effect=[fake_dns]):
1915 nsq = openstack.ns_query(fake_dns_name)
1916 self.assertEquals(nsq, '127.0.0.1')
1917
1918 @patch.object(openstack, 'apt_install')
1919 def test_ns_query_blank_record(self, apt_install):
1920 fake_dns = FakeDNS(None)
1921- with patch('__builtin__.__import__', side_effect=[fake_dns, fake_dns]):
1922+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
1923 nsq = openstack.ns_query(None)
1924 self.assertEquals(nsq, None)
1925
1926 @patch.object(openstack, 'apt_install')
1927 def test_ns_query_lookup_fail(self, apt_install):
1928 fake_dns = FakeDNS('')
1929- with patch('__builtin__.__import__', side_effect=[fake_dns, fake_dns]):
1930+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
1931 nsq = openstack.ns_query('nonexistant')
1932 self.assertEquals(nsq, None)
1933
1934 @patch.object(openstack, 'apt_install')
1935 def test_get_hostname_with_ip(self, apt_install):
1936 fake_dns = FakeDNS('www.ubuntu.com')
1937- with patch('__builtin__.__import__', side_effect=[fake_dns, fake_dns]):
1938+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
1939 hn = openstack.get_hostname('4.2.2.1')
1940 self.assertEquals(hn, 'www.ubuntu.com')
1941
1942 @patch.object(openstack, 'apt_install')
1943 def test_get_hostname_with_ip_not_fqdn(self, apt_install):
1944 fake_dns = FakeDNS('packages.ubuntu.com')
1945- with patch('__builtin__.__import__', side_effect=[fake_dns, fake_dns]):
1946+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
1947 hn = openstack.get_hostname('4.2.2.1', fqdn=False)
1948 self.assertEquals(hn, 'packages')
1949
1950@@ -663,7 +672,7 @@
1951 @patch.object(openstack, 'apt_install')
1952 def test_get_hostname_trigger_apt_install(self, apt_install):
1953 fake_dns = FakeDNS('www.ubuntu.com')
1954- with patch('__builtin__.__import__', side_effect=[ImportError, fake_dns, fake_dns]):
1955+ with patch(builtin_import, side_effect=[ImportError, fake_dns, fake_dns]):
1956 hn = openstack.get_hostname('4.2.2.1')
1957 apt_install.assert_called_with('python-dnspython')
1958 self.assertEquals(hn, 'www.ubuntu.com')
1959@@ -673,12 +682,12 @@
1960 def test_get_hostname_lookup_fail(self, apt_install, ns_query):
1961 fake_dns = FakeDNS('www.ubuntu.com')
1962 ns_query.return_value = []
1963- with patch('__builtin__.__import__', side_effect=[fake_dns, fake_dns]):
1964+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
1965 hn = openstack.get_hostname('4.2.2.1')
1966 self.assertEquals(hn, None)
1967
1968 @patch('os.path.isfile')
1969- @patch('__builtin__.open')
1970+ @patch(builtin_open)
1971 def test_get_matchmaker_map(self, _open, _isfile):
1972 _isfile.return_value = True
1973 mm_data = """
1974@@ -696,7 +705,7 @@
1975 )
1976
1977 @patch('os.path.isfile')
1978- @patch('__builtin__.open')
1979+ @patch(builtin_open)
1980 def test_get_matchmaker_map_nofile(self, _open, _isfile):
1981 _isfile.return_value = False
1982 self.assertEqual(
1983
1984=== modified file 'tests/contrib/openstack/test_os_contexts.py'
1985--- tests/contrib/openstack/test_os_contexts.py 2014-11-25 14:35:22 +0000
1986+++ tests/contrib/openstack/test_os_contexts.py 2014-11-25 15:09:14 +0000
1987@@ -11,6 +11,13 @@
1988 )
1989 from tests.helpers import patch_open
1990
1991+import six
1992+
1993+if not six.PY3:
1994+ open_builtin = '__builtin__.open'
1995+else:
1996+ open_builtin = 'builtins.open'
1997+
1998
1999 class FakeRelation(object):
2000
2001@@ -472,7 +479,7 @@
2002 self.assertEquals(result, expected)
2003
2004 @patch('os.path.exists')
2005- @patch('__builtin__.open')
2006+ @patch(open_builtin)
2007 def test_db_ssl(self, _open, osexists):
2008 osexists.return_value = False
2009 ssl_dir = '/etc/dbssl'
2010@@ -725,7 +732,7 @@
2011 }
2012 self.assertEquals(result, expected)
2013
2014- @patch('__builtin__.open')
2015+ @patch(open_builtin)
2016 def test_amqp_context_with_data_ssl(self, _open):
2017 '''Test amqp context with all required data and ssl'''
2018 relation = FakeRelation(relation_data=AMQP_RELATION_WITH_SSL)
2019@@ -799,7 +806,7 @@
2020 'rabbitmq_password': 'foobar',
2021 'rabbitmq_user': 'adam',
2022 'rabbitmq_virtual_host': 'foo',
2023- 'rabbitmq_hosts': 'rabbithost2,rabbithost1',
2024+ 'rabbitmq_hosts': 'rabbithost1,rabbithost2',
2025 }
2026 self.assertEquals(result, expected)
2027
2028@@ -868,7 +875,7 @@
2029 ceph = context.CephContext()
2030 result = ceph()
2031 expected = {
2032- 'mon_hosts': 'ceph_node2 ceph_node1',
2033+ 'mon_hosts': 'ceph_node1 ceph_node2',
2034 'auth': 'foo',
2035 'key': 'bar',
2036 'use_syslog': 'true'
2037@@ -882,8 +889,8 @@
2038 def test_ceph_context_with_missing_data(self, ensure_packages, mkdir):
2039 '''Test ceph context with missing relation data'''
2040 relation = copy(CEPH_RELATION)
2041- for k, v in relation.iteritems():
2042- for u in v.iterkeys():
2043+ for k, v in six.iteritems(relation):
2044+ for u in six.iterkeys(v):
2045 del relation[k][u]['auth']
2046 relation = FakeRelation(relation_data=relation)
2047 self.relation_get.side_effect = relation.get
2048@@ -911,7 +918,7 @@
2049 ceph = context.CephContext()
2050 result = ceph()
2051 expected = {
2052- 'mon_hosts': '192.168.1.11 192.168.1.10',
2053+ 'mon_hosts': '192.168.1.10 192.168.1.11',
2054 'auth': 'foo',
2055 'key': 'bar',
2056 'use_syslog': 'true',
2057@@ -1264,12 +1271,9 @@
2058 if len(vips) > 1:
2059 ex = {
2060 'namespace': 'cinder',
2061- 'endpoints': [('10.5.1.100', '10.5.1.1',
2062- 8766, 8756),
2063- ('10.5.2.100', '10.5.2.1',
2064- 8766, 8756),
2065- ('10.5.3.100', '10.5.3.1',
2066- 8766, 8756)],
2067+ 'endpoints': [('10.5.1.100', '10.5.1.1', 8766, 8756),
2068+ ('10.5.2.100', '10.5.2.1', 8766, 8756),
2069+ ('10.5.3.100', '10.5.3.1', 8766, 8756)],
2070 'ext_ports': [8766]
2071 }
2072 else:
2073@@ -1283,12 +1287,10 @@
2074 if multinet:
2075 ex = {
2076 'namespace': 'cinder',
2077- 'endpoints': [('10.5.3.100', '10.5.3.100',
2078- 8776, 8766),
2079- ('10.5.2.100', '10.5.2.100',
2080- 8776, 8766),
2081- ('10.5.1.100', '10.5.1.100',
2082- 8776, 8766)],
2083+ 'endpoints': sorted([
2084+ ('10.5.3.100', '10.5.3.100', 8776, 8766),
2085+ ('10.5.2.100', '10.5.2.100', 8776, 8766),
2086+ ('10.5.1.100', '10.5.1.100', 8776, 8766)]),
2087 'ext_ports': [8776]
2088 }
2089 else:
2090@@ -1391,9 +1393,10 @@
2091 apache = context.ApacheSSLContext()
2092 self.assertEquals(apache.canonical_names(), ['cinderhost1'])
2093 rel.relation_data = IDENTITY_RELATION_MULTIPLE_CERT
2094- self.assertEquals(apache.canonical_names(), ['cinderhost1-adm-network',
2095- 'cinderhost1-int-network',
2096- 'cinderhost1-pub-network'])
2097+ self.assertEquals(apache.canonical_names(),
2098+ sorted(['cinderhost1-adm-network',
2099+ 'cinderhost1-int-network',
2100+ 'cinderhost1-pub-network']))
2101 rel.relation_data = IDENTITY_RELATION_NO_CERT
2102 self.assertEquals(apache.canonical_names(), [])
2103
2104@@ -1903,17 +1906,15 @@
2105
2106 def test_workerconfig_context_noconfig(self):
2107 self.config.return_value = None
2108- with patch.object(context.WorkerConfigContext, 'num_cpus') as cpus:
2109- cpus.__get__ = Mock(return_value=2)
2110+ with patch.object(context.WorkerConfigContext, 'num_cpus', 2):
2111 worker = context.WorkerConfigContext()
2112- self.assertEqual({'workers': 2}, worker())
2113+ self.assertEqual({'workers': 0}, worker())
2114
2115 def test_workerconfig_context_withconfig(self):
2116 self.config.side_effect = fake_config({
2117 'worker-multiplier': 4,
2118 })
2119- with patch.object(context.WorkerConfigContext, 'num_cpus') as cpus:
2120- cpus.__get__ = Mock(return_value=2)
2121+ with patch.object(context.WorkerConfigContext, 'num_cpus', 2):
2122 worker = context.WorkerConfigContext()
2123 self.assertEqual({'workers': 8}, worker())
2124
2125
2126=== modified file 'tests/contrib/openstack/test_os_templating.py'
2127--- tests/contrib/openstack/test_os_templating.py 2014-11-25 14:35:22 +0000
2128+++ tests/contrib/openstack/test_os_templating.py 2014-11-25 15:09:14 +0000
2129@@ -1,7 +1,13 @@
2130
2131 import os
2132-
2133 import unittest
2134+
2135+import six
2136+if not six.PY3:
2137+ builtin_open = '__builtin__.open'
2138+else:
2139+ builtin_open = 'builtins.open'
2140+
2141 from mock import patch, call, MagicMock
2142
2143 import charmhelpers.contrib.openstack.templating as templating
2144@@ -160,7 +166,7 @@
2145 def test_render_template_by_basename(self):
2146 '''It renders template if it finds it by config file basename'''
2147
2148- @patch('__builtin__.open')
2149+ @patch(builtin_open)
2150 @patch.object(templating, 'get_loader')
2151 def test_write_out_config(self, loader, _open):
2152 '''It writes a templated config when provided a complete context'''
2153@@ -183,7 +189,7 @@
2154 ]
2155 with patch.object(self.renderer, 'write') as _write:
2156 self.renderer.write_all()
2157- self.assertEquals(ex_calls, _write.call_args_list)
2158+ self.assertEquals(sorted(ex_calls), sorted(_write.call_args_list))
2159 pass
2160
2161 @patch.object(templating, 'get_loader')
2162
2163=== modified file 'tests/contrib/storage/test_linux_ceph.py'
2164--- tests/contrib/storage/test_linux_ceph.py 2014-11-19 22:27:26 +0000
2165+++ tests/contrib/storage/test_linux_ceph.py 2014-11-25 15:09:14 +0000
2166@@ -14,19 +14,19 @@
2167 import time
2168
2169
2170-LS_POOLS = """
2171+LS_POOLS = b"""
2172 images
2173 volumes
2174 rbd
2175 """
2176
2177-LS_RBDS = """
2178+LS_RBDS = b"""
2179 rbd1
2180 rbd2
2181 rbd3
2182 """
2183
2184-IMG_MAP = """
2185+IMG_MAP = b"""
2186 bar
2187 baz
2188 """
2189@@ -93,7 +93,7 @@
2190 @patch.object(ceph_utils, 'ceph_version')
2191 def test_get_osds(self, version):
2192 version.return_value = '0.56.2'
2193- self.check_output.return_value = json.dumps([1, 2, 3])
2194+ self.check_output.return_value = json.dumps([1, 2, 3]).encode('UTF-8')
2195 self.assertEquals(ceph_utils.get_osds('test'), [1, 2, 3])
2196
2197 @patch.object(ceph_utils, 'ceph_version')
2198@@ -104,7 +104,7 @@
2199 @patch.object(ceph_utils, 'ceph_version')
2200 def test_get_osds_none(self, version):
2201 version.return_value = '0.56.2'
2202- self.check_output.return_value = json.dumps(None)
2203+ self.check_output.return_value = json.dumps(None).encode('UTF-8')
2204 self.assertEquals(ceph_utils.get_osds('test'), None)
2205
2206 @patch.object(ceph_utils, 'get_osds')
2207@@ -534,13 +534,13 @@
2208 @patch('os.path.exists')
2209 def test_ceph_version_error(self, path, output):
2210 path.return_value = True
2211- output.return_value = ''
2212+ output.return_value = b''
2213 self.assertEquals(ceph_utils.ceph_version(), None)
2214
2215 @patch.object(ceph_utils, 'check_output')
2216 @patch('os.path.exists')
2217 def test_ceph_version_ok(self, path, output):
2218 path.return_value = True
2219- output.return_value = 'ceph version 0.67.4'\
2220- ' (ad85b8bfafea6232d64cb7ba76a8b6e8252fa0c7)'
2221+ output.return_value = \
2222+ b'ceph version 0.67.4 (ad85b8bfafea6232d64cb7ba76a8b6e8252fa0c7)'
2223 self.assertEquals(ceph_utils.ceph_version(), '0.67.4')
2224
2225=== modified file 'tests/contrib/storage/test_linux_storage_lvm.py'
2226--- tests/contrib/storage/test_linux_storage_lvm.py 2014-07-03 12:36:50 +0000
2227+++ tests/contrib/storage/test_linux_storage_lvm.py 2014-11-25 15:09:14 +0000
2228@@ -5,7 +5,7 @@
2229
2230 import charmhelpers.contrib.storage.linux.lvm as lvm
2231
2232-PVDISPLAY = """
2233+PVDISPLAY = b"""
2234 --- Physical volume ---
2235 PV Name /dev/loop0
2236 VG Name foo
2237@@ -19,7 +19,7 @@
2238
2239 """
2240
2241-EMPTY_VG_IN_PVDISPLAY = """
2242+EMPTY_VG_IN_PVDISPLAY = b"""
2243 --- Physical volume ---
2244 PV Name /dev/loop0
2245 VG Name
2246
2247=== modified file 'tests/contrib/storage/test_linux_storage_utils.py'
2248--- tests/contrib/storage/test_linux_storage_utils.py 2014-07-31 08:55:15 +0000
2249+++ tests/contrib/storage/test_linux_storage_utils.py 2014-11-25 15:09:14 +0000
2250@@ -14,7 +14,7 @@
2251 @patch(STORAGE_LINUX_UTILS + '.check_call')
2252 def test_zap_disk(self, check_call, call, check_output):
2253 '''It calls sgdisk correctly to zap disk'''
2254- check_output.return_value = '200\n'
2255+ check_output.return_value = b'200\n'
2256 storage_utils.zap_disk('/dev/foo')
2257 call.assert_any_call(['sgdisk', '--zap-all', '--mbrtogpt',
2258 '--clear', '/dev/foo'])
2259@@ -50,7 +50,7 @@
2260 def test_is_device_mounted(self, check_output):
2261 '''It detects mounted devices as mounted.'''
2262 check_output.return_value = (
2263- "/dev/sda1 on / type ext4 (rw,errors=remount-ro)\n")
2264+ b"/dev/sda1 on / type ext4 (rw,errors=remount-ro)\n")
2265 result = storage_utils.is_device_mounted('/dev/sda')
2266 self.assertTrue(result)
2267
2268@@ -58,7 +58,7 @@
2269 def test_is_device_mounted_partition(self, check_output):
2270 '''It detects mounted partitions as mounted.'''
2271 check_output.return_value = (
2272- "/dev/sda1 on / type ext4 (rw,errors=remount-ro)\n")
2273+ b"/dev/sda1 on / type ext4 (rw,errors=remount-ro)\n")
2274 result = storage_utils.is_device_mounted('/dev/sda1')
2275 self.assertTrue(result)
2276
2277@@ -67,7 +67,7 @@
2278 '''It detects mounted devices as mounted if "mount" shows only a
2279 partition as mounted.'''
2280 check_output.return_value = (
2281- "/dev/sda1 on / type ext4 (rw,errors=remount-ro)\n")
2282+ b"/dev/sda1 on / type ext4 (rw,errors=remount-ro)\n")
2283 result = storage_utils.is_device_mounted('/dev/sda')
2284 self.assertTrue(result)
2285
2286@@ -75,7 +75,7 @@
2287 def test_is_device_mounted_not_mounted(self, check_output):
2288 '''It detects unmounted devices as not mounted.'''
2289 check_output.return_value = (
2290- "/dev/foo on / type ext4 (rw,errors=remount-ro)\n")
2291+ b"/dev/foo on / type ext4 (rw,errors=remount-ro)\n")
2292 result = storage_utils.is_device_mounted('/dev/sda')
2293 self.assertFalse(result)
2294
2295@@ -83,7 +83,7 @@
2296 def test_is_device_mounted_not_mounted_partition(self, check_output):
2297 '''It detects unmounted partitions as not mounted.'''
2298 check_output.return_value = (
2299- "/dev/foo on / type ext4 (rw,errors=remount-ro)\n")
2300+ b"/dev/foo on / type ext4 (rw,errors=remount-ro)\n")
2301 result = storage_utils.is_device_mounted('/dev/sda1')
2302 self.assertFalse(result)
2303
2304@@ -91,7 +91,7 @@
2305 def test_is_device_mounted_cciss(self, check_output):
2306 '''It detects mounted cciss partitions as mounted.'''
2307 check_output.return_value = (
2308- "/dev/cciss/c0d0 on / type ext4 (rw,errors=remount-ro)\n")
2309+ b"/dev/cciss/c0d0 on / type ext4 (rw,errors=remount-ro)\n")
2310 result = storage_utils.is_device_mounted('/dev/cciss/c0d0')
2311 self.assertTrue(result)
2312
2313@@ -99,6 +99,6 @@
2314 def test_is_device_mounted_cciss_not_mounted(self, check_output):
2315 '''It detects unmounted cciss partitions as not mounted.'''
2316 check_output.return_value = (
2317- "/dev/cciss/c0d1 on / type ext4 (rw,errors=remount-ro)\n")
2318+ b"/dev/cciss/c0d1 on / type ext4 (rw,errors=remount-ro)\n")
2319 result = storage_utils.is_device_mounted('/dev/cciss/c0d0')
2320 self.assertFalse(result)
2321
2322=== modified file 'tests/contrib/templating/test_contexts.py'
2323--- tests/contrib/templating/test_contexts.py 2014-08-13 20:52:09 +0000
2324+++ tests/contrib/templating/test_contexts.py 2014-11-25 15:09:14 +0000
2325@@ -9,6 +9,8 @@
2326 import unittest
2327 import yaml
2328
2329+import six
2330+
2331 import charmhelpers.contrib.templating.contexts
2332
2333
2334@@ -134,12 +136,12 @@
2335 }
2336 self.mock_relations.return_value = {
2337 'wsgi-file': {
2338- u'wsgi-file:0': {
2339- u'gunicorn/1': {
2340- u'private-address': u'10.0.3.99',
2341+ six.u('wsgi-file:0'): {
2342+ six.u('gunicorn/1'): {
2343+ six.u('private-address'): six.u('10.0.3.99'),
2344 },
2345 'click-index/3': {
2346- u'wsgi_group': u'ubunet',
2347+ six.u('wsgi_group'): six.u('ubunet'),
2348 },
2349 },
2350 },
2351@@ -162,8 +164,9 @@
2352 expected["wsgi_file:relation_key2"] = "relation_value2"
2353 expected["relations_full"]['wsgi-file'] = {
2354 'wsgi-file:0': {
2355- 'gunicorn/1': {u'private-address': u'10.0.3.99'},
2356- 'click-index/3': {u'wsgi_group': u'ubunet'},
2357+ 'gunicorn/1': {
2358+ six.u('private-address'): six.u('10.0.3.99')},
2359+ 'click-index/3': {six.u('wsgi_group'): six.u('ubunet')},
2360 },
2361 }
2362 expected["relations"]["wsgi-file"] = [
2363
2364=== modified file 'tests/contrib/unison/test_unison.py'
2365--- tests/contrib/unison/test_unison.py 2014-10-22 06:17:55 +0000
2366+++ tests/contrib/unison/test_unison.py 2014-11-25 15:09:14 +0000
2367@@ -110,13 +110,13 @@
2368
2369 isfile.return_value = False
2370 with patch_open() as (_open, _file):
2371- self.check_output.return_value = 'fookey'
2372+ self.check_output.return_value = b'fookey'
2373 unison.create_public_key(
2374 user='foo', priv_key_path='/home/foo/.ssh/id_rsa',
2375 pub_key_path='/home/foo/.ssh/id_rsa.pub')
2376 self.assertIn(call(create_cmd), self.check_output.call_args_list)
2377 _open.assert_called_with('/home/foo/.ssh/id_rsa.pub', 'wb')
2378- _file.write.assert_called_with('fookey')
2379+ _file.write.assert_called_with(b'fookey')
2380
2381 @patch('os.mkdir')
2382 @patch('os.path.isdir')
2383
2384=== modified file 'tests/core/test_fstab.py'
2385--- tests/core/test_fstab.py 2014-11-25 14:35:22 +0000
2386+++ tests/core/test_fstab.py 2014-11-25 15:09:14 +0000
2387@@ -24,7 +24,7 @@
2388 class FstabTest(unittest.TestCase):
2389
2390 def setUp(self):
2391- self.tempfile = tempfile.NamedTemporaryFile(delete=False)
2392+ self.tempfile = tempfile.NamedTemporaryFile('w+', delete=False)
2393 self.tempfile.write(DEFAULT_FSTAB_FILE)
2394 self.tempfile.close()
2395 self.fstab = Fstab(path=self.tempfile.name)
2396
2397=== modified file 'tests/core/test_hookenv.py'
2398--- tests/core/test_hookenv.py 2014-11-25 14:35:22 +0000
2399+++ tests/core/test_hookenv.py 2014-11-25 15:09:14 +0000
2400@@ -3,14 +3,18 @@
2401 from subprocess import CalledProcessError
2402 import shutil
2403 import tempfile
2404-
2405-import cPickle as pickle
2406 from mock import patch, call, mock_open
2407-from StringIO import StringIO
2408 from mock import MagicMock
2409 from testtools import TestCase
2410 import yaml
2411
2412+import six
2413+from six.moves import StringIO
2414+if six.PY3:
2415+ import pickle
2416+else:
2417+ import cPickle as pickle
2418+
2419 from charmhelpers.core import hookenv
2420
2421 CHARM_METADATA = """name: testmock
2422@@ -125,7 +129,7 @@
2423 def test_keys(self):
2424 c = hookenv.Config(dict(foo='bar'))
2425 c["baz"] = "bar"
2426- self.assertEqual([u"foo", "baz"], c.keys())
2427+ self.assertEqual(sorted([six.u("foo"), "baz"]), sorted(c.keys()))
2428
2429
2430 class SerializableTest(TestCase):
2431@@ -165,11 +169,9 @@
2432 }
2433 wrapped = hookenv.Serializable(foo)
2434 for meth in ('keys', 'values', 'items'):
2435- self.assertEqual(getattr(wrapped, meth)(), getattr(foo, meth)())
2436+ self.assertEqual(sorted(list(getattr(wrapped, meth)())),
2437+ sorted(list(getattr(foo, meth)())))
2438
2439- for meth in ('iterkeys', 'itervalues', 'iteritems'):
2440- self.assertEqual(list(getattr(wrapped, meth)()),
2441- list(getattr(foo, meth)()))
2442 self.assertEqual(wrapped.get('bar'), foo.get('bar'))
2443 self.assertEqual(wrapped.get('baz', 42), foo.get('baz', 42))
2444 self.assertIn('bar', wrapped)
2445@@ -246,7 +248,7 @@
2446 @patch('subprocess.check_output')
2447 def test_gets_charm_config_with_scope(self, check_output):
2448 config_data = 'bar'
2449- check_output.return_value = json.dumps(config_data)
2450+ check_output.return_value = json.dumps(config_data).encode('UTF-8')
2451
2452 result = hookenv.config(scope='baz')
2453
2454@@ -257,11 +259,11 @@
2455 self.assertEqual(result[1], 'a')
2456
2457 # ... because the result is actually a string
2458- self.assert_(isinstance(result, basestring))
2459+ self.assert_(isinstance(result, six.string_types))
2460
2461 @patch('subprocess.check_output')
2462 def test_gets_missing_charm_config_with_scope(self, check_output):
2463- check_output.return_value = ''
2464+ check_output.return_value = b''
2465
2466 result = hookenv.config(scope='baz')
2467
2468@@ -271,7 +273,7 @@
2469 @patch('charmhelpers.core.hookenv.charm_dir')
2470 @patch('subprocess.check_output')
2471 def test_gets_config_without_scope(self, check_output, charm_dir):
2472- check_output.return_value = json.dumps(dict(foo='bar'))
2473+ check_output.return_value = json.dumps(dict(foo='bar')).encode('UTF-8')
2474 charm_dir.side_effect = tempfile.mkdtemp
2475
2476 result = hookenv.config()
2477@@ -327,7 +329,7 @@
2478 @patch('charmhelpers.core.hookenv.relation_type')
2479 def test_gets_relation_ids(self, relation_type, check_output):
2480 ids = [1, 2, 3]
2481- check_output.return_value = json.dumps(ids)
2482+ check_output.return_value = json.dumps(ids).encode('UTF-8')
2483 reltype = 'foo'
2484 relation_type.return_value = reltype
2485
2486@@ -341,7 +343,7 @@
2487 @patch('charmhelpers.core.hookenv.relation_type')
2488 def test_gets_relation_ids_empty_array(self, relation_type, check_output):
2489 ids = []
2490- check_output.return_value = json.dumps(None)
2491+ check_output.return_value = json.dumps(None).encode('UTF-8')
2492 reltype = 'foo'
2493 relation_type.return_value = reltype
2494
2495@@ -355,7 +357,7 @@
2496 @patch('charmhelpers.core.hookenv.relation_type')
2497 def test_relation_ids_no_relation_type(self, relation_type, check_output):
2498 ids = [1, 2, 3]
2499- check_output.return_value = json.dumps(ids)
2500+ check_output.return_value = json.dumps(ids).encode('UTF-8')
2501 relation_type.return_value = None
2502
2503 result = hookenv.relation_ids()
2504@@ -366,7 +368,7 @@
2505 @patch('charmhelpers.core.hookenv.relation_type')
2506 def test_gets_relation_ids_for_type(self, relation_type, check_output):
2507 ids = [1, 2, 3]
2508- check_output.return_value = json.dumps(ids)
2509+ check_output.return_value = json.dumps(ids).encode('UTF-8')
2510 reltype = 'foo'
2511
2512 result = hookenv.relation_ids(reltype)
2513@@ -382,7 +384,7 @@
2514 relid = 123
2515 units = ['foo', 'bar']
2516 relation_id.return_value = relid
2517- check_output.return_value = json.dumps(units)
2518+ check_output.return_value = json.dumps(units).encode('UTF-8')
2519
2520 result = hookenv.related_units()
2521
2522@@ -393,10 +395,10 @@
2523 @patch('subprocess.check_output')
2524 @patch('charmhelpers.core.hookenv.relation_id')
2525 def test_gets_related_units_empty_array(self, relation_id, check_output):
2526- relid = 123
2527+ relid = str(123)
2528 units = []
2529 relation_id.return_value = relid
2530- check_output.return_value = json.dumps(None)
2531+ check_output.return_value = json.dumps(None).encode('UTF-8')
2532
2533 result = hookenv.related_units()
2534
2535@@ -409,7 +411,7 @@
2536 def test_related_units_no_relation(self, relation_id, check_output):
2537 units = ['foo', 'bar']
2538 relation_id.return_value = None
2539- check_output.return_value = json.dumps(units)
2540+ check_output.return_value = json.dumps(units).encode('UTF-8')
2541
2542 result = hookenv.related_units()
2543
2544@@ -421,7 +423,7 @@
2545 def test_gets_related_units_for_id(self, relation_id, check_output):
2546 relid = 123
2547 units = ['foo', 'bar']
2548- check_output.return_value = json.dumps(units)
2549+ check_output.return_value = json.dumps(units).encode('UTF-8')
2550
2551 result = hookenv.related_units(relid)
2552
2553@@ -765,7 +767,7 @@
2554 @patch('subprocess.check_output')
2555 def test_gets_relation(self, check_output):
2556 data = {"foo": "BAR"}
2557- check_output.return_value = json.dumps(data)
2558+ check_output.return_value = json.dumps(data).encode('UTF-8')
2559 result = hookenv.relation_get()
2560
2561 self.assertEqual(result['foo'], 'BAR')
2562@@ -773,7 +775,7 @@
2563
2564 @patch('charmhelpers.core.hookenv.subprocess')
2565 def test_relation_get_none(self, mock_subprocess):
2566- mock_subprocess.check_output.return_value = 'null'
2567+ mock_subprocess.check_output.return_value = b'null'
2568
2569 result = hookenv.relation_get()
2570
2571@@ -801,7 +803,7 @@
2572
2573 @patch('subprocess.check_output')
2574 def test_gets_relation_with_scope(self, check_output):
2575- check_output.return_value = json.dumps('bar')
2576+ check_output.return_value = json.dumps('bar').encode('UTF-8')
2577
2578 result = hookenv.relation_get(attribute='baz-scope')
2579
2580@@ -811,7 +813,7 @@
2581
2582 @patch('subprocess.check_output')
2583 def test_gets_missing_relation_with_scope(self, check_output):
2584- check_output.return_value = ""
2585+ check_output.return_value = b""
2586
2587 result = hookenv.relation_get(attribute='baz-scope')
2588
2589@@ -821,7 +823,7 @@
2590
2591 @patch('subprocess.check_output')
2592 def test_gets_relation_with_unit_name(self, check_output):
2593- check_output.return_value = json.dumps('BAR')
2594+ check_output.return_value = json.dumps('BAR').encode('UTF-8')
2595
2596 result = hookenv.relation_get(attribute='baz-scope', unit='baz-unit')
2597
2598@@ -834,7 +836,7 @@
2599 @patch('subprocess.check_output')
2600 def test_relation_set_flushes_local_unit_cache(self, check_output,
2601 check_call, local_unit):
2602- check_output.return_value = json.dumps('BAR')
2603+ check_output.return_value = json.dumps('BAR').encode('UTF-8')
2604 local_unit.return_value = 'baz_unit'
2605 hookenv.relation_get(attribute='baz_scope', unit='baz_unit')
2606 hookenv.relation_get(attribute='bar_scope')
2607@@ -845,7 +847,7 @@
2608
2609 @patch('subprocess.check_output')
2610 def test_gets_relation_with_relation_id(self, check_output):
2611- check_output.return_value = json.dumps('BAR')
2612+ check_output.return_value = json.dumps('BAR').encode('UTF-8')
2613
2614 result = hookenv.relation_get(attribute='baz-scope', unit='baz-unit',
2615 rid=123)
2616@@ -910,13 +912,13 @@
2617
2618 @patch('subprocess.check_output')
2619 def test_gets_unit_attribute(self, check_output_):
2620- check_output_.return_value = json.dumps('bar')
2621+ check_output_.return_value = json.dumps('bar').encode('UTF-8')
2622 self.assertEqual(hookenv.unit_get('foo'), 'bar')
2623 check_output_.assert_called_with(['unit-get', '--format=json', 'foo'])
2624
2625 @patch('subprocess.check_output')
2626 def test_gets_missing_unit_attribute(self, check_output_):
2627- check_output_.return_value = ""
2628+ check_output_.return_value = b""
2629 self.assertEqual(hookenv.unit_get('foo'), None)
2630 check_output_.assert_called_with(['unit-get', '--format=json', 'foo'])
2631
2632@@ -925,8 +927,8 @@
2633 values = {
2634 'hello': 'world',
2635 'foo': 'bar',
2636- 'baz': None
2637- }
2638+ 'baz': None,
2639+ }
2640
2641 @hookenv.cached
2642 def cache_function(attribute):
2643
2644=== modified file 'tests/core/test_host.py'
2645--- tests/core/test_host.py 2014-11-25 14:35:22 +0000
2646+++ tests/core/test_host.py 2014-11-25 15:09:14 +0000
2647@@ -19,30 +19,30 @@
2648 """rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
2649 """).strip().split('\n')
2650
2651-LSB_RELEASE = u'''DISTRIB_ID=Ubuntu
2652+LSB_RELEASE = '''DISTRIB_ID=Ubuntu
2653 DISTRIB_RELEASE=13.10
2654 DISTRIB_CODENAME=saucy
2655 DISTRIB_DESCRIPTION="Ubuntu Saucy Salamander (development branch)"
2656 '''
2657
2658-IP_LINE_ETH0 = ("""
2659+IP_LINE_ETH0 = b"""
2660 2: eth0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP qlen 1000
2661 link/ether e4:11:5b:ab:a7:3c brd ff:ff:ff:ff:ff:ff
2662-""")
2663+"""
2664
2665-IP_LINE_ETH1 = ("""
2666+IP_LINE_ETH1 = b"""
2667 3: eth1: <BROADCAST,MULTICAST> mtu 1546 qdisc noop state DOWN qlen 1000
2668 link/ether e4:11:5b:ab:a7:3c brd ff:ff:ff:ff:ff:ff
2669-""")
2670+"""
2671
2672-IP_LINE_HWADDR = ("""2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\ link/ether e4:11:5b:ab:a7:3c brd ff:ff:ff:ff:ff:ff""")
2673+IP_LINE_HWADDR = b"""2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\ link/ether e4:11:5b:ab:a7:3c brd ff:ff:ff:ff:ff:ff"""
2674
2675 IP_LINES = IP_LINE_ETH0 + IP_LINE_ETH1
2676
2677-IP_LINE_BONDS = ("""
2678+IP_LINE_BONDS = b"""
2679 6: bond0.10@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
2680 link/ether 08:00:27:16:b9:5f brd ff:ff:ff:ff:ff:ff
2681-""")
2682+"""
2683
2684
2685 class HelpersTest(TestCase):
2686@@ -166,12 +166,12 @@
2687
2688 @patch('subprocess.check_output')
2689 def test_service_running_on_stopped_service(self, check_output):
2690- check_output.return_value = 'foo stop/waiting'
2691+ check_output.return_value = b'foo stop/waiting'
2692 self.assertFalse(host.service_running('foo'))
2693
2694 @patch('subprocess.check_output')
2695 def test_service_running_on_running_service(self, check_output):
2696- check_output.return_value = 'foo start/running, process 23871'
2697+ check_output.return_value = b'foo start/running, process 23871'
2698 self.assertTrue(host.service_running('foo'))
2699
2700 @patch('subprocess.check_output')
2701@@ -284,7 +284,7 @@
2702 def test_rsyncs_a_path(self, log, check_output):
2703 from_path = '/from/this/path/foo'
2704 to_path = '/to/this/path/bar'
2705- check_output.return_value = ' some output '
2706+ check_output.return_value = b' some output ' # Spaces will be stripped
2707
2708 result = host.rsync(from_path, to_path)
2709
2710@@ -319,7 +319,7 @@
2711 path = '/some/other/path/from/link'
2712 realpath = '/some/path'
2713 path_exists = False
2714- perms = 0644
2715+ perms = 0o644
2716
2717 getpwnam.return_value.pw_uid = uid
2718 getgrnam.return_value.gr_gid = gid
2719@@ -343,7 +343,7 @@
2720 path = '/some/other/path/from/link'
2721 realpath = '/some/path'
2722 path_exists = False
2723- perms = 0555
2724+ perms = 0o555
2725
2726 os_.path.abspath.return_value = realpath
2727 os_.path.exists.return_value = path_exists
2728@@ -370,7 +370,7 @@
2729 path_exists = True
2730 force = True
2731 is_dir = False
2732- perms = 0644
2733+ perms = 0o644
2734
2735 getpwnam.return_value.pw_uid = uid
2736 getgrnam.return_value.gr_gid = gid
2737@@ -402,7 +402,7 @@
2738 group = 'some-group-{bar}'
2739 path = '/some/path/{baz}'
2740 contents = 'what is {juju}'
2741- perms = 0644
2742+ perms = 0o644
2743 fileno = 'some-fileno'
2744
2745 getpwnam.return_value.pw_uid = uid
2746@@ -428,7 +428,7 @@
2747 gid = 0
2748 path = '/some/path/{baz}'
2749 fmtstr = 'what is {juju}'
2750- perms = 0444
2751+ perms = 0o444
2752 fileno = 'some-fileno'
2753
2754 with patch_open() as (mock_open, mock_file):
2755@@ -636,7 +636,7 @@
2756
2757 @host.restart_on_change(restart_map)
2758 def make_some_changes(mock_file):
2759- mock_file.read.return_value = "newstuff"
2760+ mock_file.read.return_value = b"newstuff"
2761
2762 with patch_open() as (mock_open, mock_file):
2763 make_some_changes(mock_file)
2764@@ -664,7 +664,7 @@
2765 pass
2766
2767 with patch_open() as (mock_open, mock_file):
2768- mock_file.read.side_effect = ['exists', 'missing', 'exists2']
2769+ mock_file.read.side_effect = [b'exists', b'missing', b'exists2']
2770 make_some_changes()
2771
2772 # Restart should only happen once per service
2773@@ -691,7 +691,7 @@
2774 pass
2775
2776 with patch_open() as (mock_open, mock_file):
2777- mock_file.read.side_effect = ['exists', 'missing', 'exists2']
2778+ mock_file.read.side_effect = [b'exists', b'missing', b'exists2']
2779 make_some_changes()
2780
2781 # Restarts should happen in the order they are described in the
2782@@ -713,7 +713,6 @@
2783 with mocked_open('/etc/lsb-release', LSB_RELEASE):
2784 lsb_release = host.lsb_release()
2785 for key in result:
2786- print lsb_release
2787 self.assertEqual(result[key], lsb_release[key])
2788
2789 def test_pwgen(self):
2790
2791=== modified file 'tests/core/test_services.py'
2792--- tests/core/test_services.py 2014-11-25 14:35:22 +0000
2793+++ tests/core/test_services.py 2014-11-25 15:09:14 +0000
2794@@ -573,7 +573,7 @@
2795 with mock.patch.object(services.helpers, 'open', mopen, create=True):
2796 services.helpers.StoredContext('foo.yaml', {'key': 'val'})
2797 mopen.assert_called_once_with('charm_dir/foo.yaml', 'w')
2798- fchmod.assert_called_once_with(mopen.return_value.fileno(), 0600)
2799+ fchmod.assert_called_once_with(mopen.return_value.fileno(), 0o600)
2800 yaml.dump.assert_called_once_with({'key': 'val'}, mopen.return_value)
2801
2802 @mock.patch.object(hookenv, 'charm_dir', lambda: 'charm_dir')
2803@@ -637,7 +637,7 @@
2804 callback(manager, 'test', 'event')
2805 mtemplating.render.assert_called_once_with(
2806 'foo.yml', 'bar.yml', {'foo': 'bar'},
2807- 'root', 'root', 0444)
2808+ 'root', 'root', 0o444)
2809
2810 @mock.patch.object(services.helpers, 'templating')
2811 def test_template_explicit(self, mtemplating):
2812@@ -645,14 +645,14 @@
2813 'required_data': [{'foo': 'bar'}]}})
2814 callback = services.template(
2815 source='foo.yml', target='bar.yml',
2816- owner='user', group='group', perms=0555
2817+ owner='user', group='group', perms=0o555
2818 )
2819 assert isinstance(callback, services.ManagerCallback)
2820 assert not mtemplating.render.called
2821 callback(manager, 'test', 'event')
2822 mtemplating.render.assert_called_once_with(
2823 'foo.yml', 'bar.yml', {'foo': 'bar'},
2824- 'user', 'group', 0555)
2825+ 'user', 'group', 0o555)
2826
2827
2828 class TestPortsCallback(unittest.TestCase):
2829
2830=== modified file 'tests/core/test_sysctl.py'
2831--- tests/core/test_sysctl.py 2014-11-25 14:35:22 +0000
2832+++ tests/core/test_sysctl.py 2014-11-25 15:09:14 +0000
2833@@ -4,11 +4,18 @@
2834 __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
2835
2836 from charmhelpers.core.sysctl import create
2837+import io
2838 from mock import patch, MagicMock
2839-
2840 import unittest
2841 import tempfile
2842
2843+import six
2844+if not six.PY3:
2845+ builtin_open = '__builtin__.open'
2846+else:
2847+ builtin_open = 'builtins.open'
2848+
2849+
2850 TO_PATCH = [
2851 'log',
2852 'check_call',
2853@@ -27,10 +34,10 @@
2854 self.addCleanup(_m.stop)
2855 return mock
2856
2857- @patch('__builtin__.open')
2858+ @patch(builtin_open)
2859 def test_create(self, mock_open):
2860 """Test create sysctl method"""
2861- _file = MagicMock(spec=file)
2862+ _file = MagicMock(spec=io.FileIO)
2863 mock_open.return_value = _file
2864
2865 create('{"kernel.max_pid": 1337}', "/etc/sysctl.d/test-sysctl.conf")
2866
2867=== modified file 'tests/fetch/test_archiveurl.py'
2868--- tests/fetch/test_archiveurl.py 2014-11-25 14:35:22 +0000
2869+++ tests/fetch/test_archiveurl.py 2014-11-25 15:09:14 +0000
2870@@ -1,6 +1,14 @@
2871 import os
2872-from testtools import TestCase
2873-from urlparse import urlparse
2874+
2875+import six
2876+if six.PY3:
2877+ from urllib.parse import urlparse
2878+ from urllib.error import URLError
2879+else:
2880+ from urllib2 import URLError
2881+ from urlparse import urlparse
2882+
2883+from unittest import TestCase
2884 from mock import (
2885 MagicMock,
2886 patch,
2887@@ -11,7 +19,6 @@
2888 archiveurl,
2889 UnhandledSource,
2890 )
2891-import urllib2
2892
2893
2894 class ArchiveUrlFetchHandlerTest(TestCase):
2895@@ -50,7 +57,7 @@
2896 result = self.fh.can_handle(url)
2897 self.assertNotEqual(result, True, url)
2898
2899- @patch('urllib2.urlopen')
2900+ @patch('charmhelpers.fetch.archiveurl.urlopen')
2901 def test_downloads(self, _urlopen):
2902 for url in self.valid_urls:
2903 response = MagicMock()
2904@@ -87,7 +94,7 @@
2905
2906 url = "http://www.example.com/archive.tar.gz"
2907
2908- self.fh.download.side_effect = urllib2.URLError('fail')
2909+ self.fh.download.side_effect = URLError('fail')
2910 with patch.dict('os.environ', {'CHARM_DIR': 'foo'}):
2911 self.assertRaises(UnhandledSource, self.fh.install, url)
2912
2913
2914=== modified file 'tests/fetch/test_bzrurl.py'
2915--- tests/fetch/test_bzrurl.py 2014-11-25 14:35:22 +0000
2916+++ tests/fetch/test_bzrurl.py 2014-11-25 15:09:14 +0000
2917@@ -1,20 +1,34 @@
2918 import os
2919 from testtools import TestCase
2920-from urlparse import urlparse
2921 from mock import (
2922 MagicMock,
2923 patch,
2924 )
2925-from charmhelpers.fetch import (
2926- bzrurl,
2927- UnhandledSource,
2928-)
2929-
2930-
2931+import unittest
2932+
2933+import six
2934+if six.PY3:
2935+ from urllib.parse import urlparse
2936+else:
2937+ from urlparse import urlparse
2938+
2939+try:
2940+ from charmhelpers.fetch import (
2941+ bzrurl,
2942+ UnhandledSource,
2943+ )
2944+except ImportError:
2945+ bzrurl = None
2946+ UnhandledSource = None
2947+
2948+
2949+@unittest.skipIf(six.PY3, 'bzr does not support Python 3')
2950 class BzrUrlFetchHandlerTest(TestCase):
2951
2952 def setUp(self):
2953 super(BzrUrlFetchHandlerTest, self).setUp()
2954+ if six.PY3:
2955+ return
2956 self.valid_urls = (
2957 "bzr+ssh://example.com/branch-name",
2958 "bzr+ssh://example.com/branch-name/",
2959@@ -41,6 +55,7 @@
2960 )
2961 self.fh = bzrurl.BzrUrlFetchHandler()
2962
2963+ @unittest.skipIf(six.PY3, 'bzr does not support Python 3')
2964 def test_handles_bzr_urls(self):
2965 for url in self.valid_urls:
2966 result = self.fh.can_handle(url)
2967@@ -49,6 +64,7 @@
2968 result = self.fh.can_handle(url)
2969 self.assertNotEqual(result, True, url)
2970
2971+ @unittest.skipIf(six.PY3, 'bzr does not support Python 3')
2972 @patch('bzrlib.branch.Branch.open')
2973 def test_branch(self, _open):
2974 dest_path = "/destination/path"
2975@@ -63,6 +79,7 @@
2976 with patch.dict('os.environ', {'CHARM_DIR': 'foo'}):
2977 self.assertRaises(UnhandledSource, self.fh.branch, url, dest_path)
2978
2979+ @unittest.skipIf(six.PY3, 'bzr does not support Python 3')
2980 @patch('charmhelpers.fetch.bzrurl.mkdir')
2981 def test_installs(self, _mkdir):
2982 self.fh.branch = MagicMock()
2983@@ -73,4 +90,4 @@
2984 with patch.dict('os.environ', {'CHARM_DIR': 'foo'}):
2985 where = self.fh.install(url)
2986 self.assertEqual(where, dest)
2987- _mkdir.assert_called_with(where, perms=0755)
2988+ _mkdir.assert_called_with(where, perms=0o755)
2989
2990=== modified file 'tests/fetch/test_fetch.py'
2991--- tests/fetch/test_fetch.py 2014-11-25 14:35:22 +0000
2992+++ tests/fetch/test_fetch.py 2014-11-25 15:09:14 +0000
2993@@ -1,4 +1,3 @@
2994-from cStringIO import StringIO
2995 import subprocess
2996
2997 from tests.helpers import patch_open
2998@@ -8,11 +7,18 @@
2999 MagicMock,
3000 call,
3001 )
3002-from urlparse import urlparse
3003 from charmhelpers import fetch
3004 import os
3005 import yaml
3006
3007+import six
3008+from six.moves import StringIO
3009+if six.PY3:
3010+ from urllib.parse import urlparse
3011+else:
3012+ from urlparse import urlparse
3013+
3014+
3015 FAKE_APT_CACHE = {
3016 # an installed package
3017 'vim': {
3018@@ -404,7 +410,11 @@
3019 @patch('charmhelpers.fetch.log')
3020 def test_plugins_are_valid(self, log_):
3021 plugins = fetch.plugins()
3022- self.assertEqual(len(fetch.FETCH_HANDLERS), len(plugins))
3023+ if not six.PY3:
3024+ self.assertEqual(len(fetch.FETCH_HANDLERS), len(plugins))
3025+ else:
3026+ # No bzr or git libraries for Python3.
3027+ self.assertEqual(len(fetch.FETCH_HANDLERS) - 2, len(plugins))
3028
3029
3030 class BaseFetchHandlerTest(TestCase):
3031
3032=== modified file 'tests/fetch/test_giturl.py'
3033--- tests/fetch/test_giturl.py 2014-11-25 14:35:22 +0000
3034+++ tests/fetch/test_giturl.py 2014-11-25 15:09:14 +0000
3035@@ -1,18 +1,31 @@
3036 import os
3037 from testtools import TestCase
3038-from urlparse import urlparse
3039 from mock import (
3040 MagicMock,
3041 patch,
3042 )
3043-from charmhelpers.fetch import (
3044- giturl,
3045- UnhandledSource,
3046-)
3047-
3048-
3049+import unittest
3050+
3051+import six
3052+if six.PY3:
3053+ from urllib.parse import urlparse
3054+else:
3055+ from urlparse import urlparse
3056+
3057+try:
3058+ from charmhelpers.fetch import (
3059+ giturl,
3060+ UnhandledSource,
3061+ )
3062+except ImportError:
3063+ giturl = None
3064+ UnhandledSource = None
3065+
3066+
3067+@unittest.skipIf(six.PY3, 'git does not support Python 3')
3068 class GitUrlFetchHandlerTest(TestCase):
3069
3070+ @unittest.skipIf(six.PY3, 'git does not support Python 3')
3071 def setUp(self):
3072 super(GitUrlFetchHandlerTest, self).setUp()
3073 self.valid_urls = (
3074@@ -27,6 +40,7 @@
3075 )
3076 self.fh = giturl.GitUrlFetchHandler()
3077
3078+ @unittest.skipIf(six.PY3, 'git does not support Python 3')
3079 def test_handles_git_urls(self):
3080 for url in self.valid_urls:
3081 result = self.fh.can_handle(url)
3082@@ -35,6 +49,7 @@
3083 result = self.fh.can_handle(url)
3084 self.assertNotEqual(result, True, url)
3085
3086+ @unittest.skipIf(six.PY3, 'git does not support Python 3')
3087 @patch('git.Repo.clone_from')
3088 def test_branch(self, _clone_from):
3089 dest_path = "/destination/path"
3090@@ -52,6 +67,7 @@
3091 dest_path,
3092 branch)
3093
3094+ @unittest.skipIf(six.PY3, 'git does not support Python 3')
3095 @patch('charmhelpers.fetch.giturl.mkdir')
3096 def test_installs(self, _mkdir):
3097 self.fh.clone = MagicMock()
3098@@ -63,4 +79,4 @@
3099 with patch.dict('os.environ', {'CHARM_DIR': 'foo'}):
3100 where = self.fh.install(url)
3101 self.assertEqual(where, dest)
3102- _mkdir.assert_called_with(where, perms=0755)
3103+ _mkdir.assert_called_with(where, perms=0o755)
3104
3105=== modified file 'tests/helpers.py'
3106--- tests/helpers.py 2014-11-25 14:35:22 +0000
3107+++ tests/helpers.py 2014-11-25 15:09:14 +0000
3108@@ -3,6 +3,12 @@
3109 from mock import patch, MagicMock
3110 import io
3111
3112+import six
3113+if not six.PY3:
3114+ builtin_open = '__builtin__.open'
3115+else:
3116+ builtin_open = 'builtins.open'
3117+
3118
3119 @contextmanager
3120 def patch_open():
3121@@ -11,26 +17,29 @@
3122
3123 Yields the mock for "open" and "file", respectively.'''
3124 mock_open = MagicMock(spec=open)
3125- mock_file = MagicMock(spec=file)
3126+ mock_file = MagicMock(spec=io.FileIO)
3127
3128 @contextmanager
3129 def stub_open(*args, **kwargs):
3130 mock_open(*args, **kwargs)
3131 yield mock_file
3132
3133- with patch('__builtin__.open', stub_open):
3134+ with patch(builtin_open, stub_open):
3135 yield mock_open, mock_file
3136
3137
3138 @contextmanager
3139 def mock_open(filename, contents=None):
3140 ''' Slightly simpler mock of open to return contents for filename '''
3141- def mock_file(*args):
3142- if args[0] == filename:
3143+ def mock_file(name, mode='r', buffering=-1): # Python 2 signature.
3144+ if name == filename:
3145+ if (not six.PY3) or 'b' in mode:
3146+ return io.BytesIO(contents)
3147 return io.StringIO(contents)
3148 else:
3149- return open(*args)
3150- with patch('__builtin__.open', mock_file):
3151+ return open(name, mode, buffering)
3152+
3153+ with patch(builtin_open, mock_file):
3154 yield
3155
3156
3157
3158=== modified file 'tests/payload/test_archive.py'
3159--- tests/payload/test_archive.py 2014-11-25 14:35:22 +0000
3160+++ tests/payload/test_archive.py 2014-11-25 15:09:14 +0000
3161@@ -84,7 +84,7 @@
3162 self.addCleanup(rmtree, destdir)
3163 try:
3164 zip_file, contents = self.create_archive("zip")
3165- except subprocess.CalledProcessError, e:
3166+ except subprocess.CalledProcessError as e:
3167 if e.returncode == 127:
3168 self.skip("Skipping - zip is not installed")
3169 else:
3170
3171=== modified file 'tests/payload/test_execd.py'
3172--- tests/payload/test_execd.py 2013-07-11 08:31:49 +0000
3173+++ tests/payload/test_execd.py 2014-11-25 15:09:14 +0000
3174@@ -111,7 +111,7 @@
3175
3176 expected = [os.path.join(self.test_charm_dir, 'exec.d', mod,
3177 'charm-pre-install') for mod in modules]
3178- self.assertItemsEqual(submodules, expected)
3179+ self.assertEqual(sorted(submodules), sorted(expected))
3180
3181 def test_execd_run(self):
3182 modules = ['basenode', 'mod2', 'c']
3183
3184=== modified file 'tests/tools/test_charm_helper_sync.py'
3185--- tests/tools/test_charm_helper_sync.py 2014-11-25 14:35:22 +0000
3186+++ tests/tools/test_charm_helper_sync.py 2014-11-25 15:09:14 +0000
3187@@ -2,6 +2,13 @@
3188 from mock import call, patch
3189 import yaml
3190
3191+import six
3192+if not six.PY3:
3193+ builtin_open = '__builtin__.open'
3194+else:
3195+ builtin_open = 'builtins.open'
3196+
3197+
3198 import tools.charm_helpers_sync.charm_helpers_sync as sync
3199
3200 INCLUDE = """
3201@@ -46,7 +53,7 @@
3202 self.assertEquals('/tmp/mycharm/hooks/charmhelpers/contrib/openstack',
3203 path)
3204
3205- @patch('__builtin__.open')
3206+ @patch(builtin_open)
3207 @patch('os.path.exists')
3208 @patch('os.walk')
3209 def test_ensure_init(self, walk, exists, _open):
3210@@ -114,7 +121,7 @@
3211 isfile.side_effect = _isfile
3212 isdir.side_effect = _isdir
3213 result = sync.get_filter(opts)(dir='/tmp/charm-helpers/core',
3214- ls=files.iterkeys())
3215+ ls=six.iterkeys(files))
3216 return result
3217
3218 @patch('os.path.isdir')
3219@@ -123,15 +130,15 @@
3220 '''It filters out all non-py files by default'''
3221 result = self._test_filter_dir(opts=None, isfile=isfile, isdir=isdir)
3222 ex = ['bad_file.bin', 'bad_file.img', 'some_dir']
3223- self.assertEquals(ex, result)
3224+ self.assertEquals(sorted(ex), sorted(result))
3225
3226 @patch('os.path.isdir')
3227 @patch('os.path.isfile')
3228 def test_filter_dir_with_include(self, isfile, isdir):
3229 '''It includes non-py files if specified as an include opt'''
3230- result = self._test_filter_dir(opts=['inc=*.img'],
3231- isfile=isfile, isdir=isdir)
3232- ex = ['bad_file.bin', 'some_dir']
3233+ result = sorted(self._test_filter_dir(opts=['inc=*.img'],
3234+ isfile=isfile, isdir=isdir))
3235+ ex = sorted(['bad_file.bin', 'some_dir'])
3236 self.assertEquals(ex, result)
3237
3238 @patch('os.path.isdir')
3239
3240=== modified file 'tools/charm_helpers_sync/charm_helpers_sync.py'
3241--- tools/charm_helpers_sync/charm_helpers_sync.py 2014-11-25 14:35:22 +0000
3242+++ tools/charm_helpers_sync/charm_helpers_sync.py 2014-11-25 15:09:14 +0000
3243@@ -14,9 +14,10 @@
3244 import sys
3245 import tempfile
3246 import yaml
3247-
3248 from fnmatch import fnmatch
3249
3250+import six
3251+
3252 CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
3253
3254
3255@@ -139,7 +140,7 @@
3256
3257 def extract_options(inc, global_options=None):
3258 global_options = global_options or []
3259- if global_options and isinstance(global_options, basestring):
3260+ if global_options and isinstance(global_options, six.string_types):
3261 global_options = [global_options]
3262 if '|' not in inc:
3263 return (inc, global_options)
3264@@ -159,7 +160,7 @@
3265 sync(src, dest, inc, opts)
3266 elif isinstance(inc, dict):
3267 # could also do nested dicts here.
3268- for k, v in inc.iteritems():
3269+ for k, v in six.iteritems(inc):
3270 if isinstance(v, list):
3271 for m in v:
3272 inc, opts = extract_options(m, global_options)
3273@@ -217,7 +218,7 @@
3274 checkout = clone_helpers(tmpd, config['branch'])
3275 sync_helpers(config['include'], checkout, config['destination'],
3276 options=sync_options)
3277- except Exception, e:
3278+ except Exception as e:
3279 logging.error("Could not sync: %s" % e)
3280 raise e
3281 finally:

Subscribers

People subscribed via source and target branches