Merge lp:~niedbalski/charms/trusty/ceph-osd/sysctl-param into lp:~openstack-charmers-archive/charms/trusty/ceph-osd/next

Proposed by Jorge Niedbalski
Status: Merged
Merged at revision: 32
Proposed branch: lp:~niedbalski/charms/trusty/ceph-osd/sysctl-param
Merge into: lp:~openstack-charmers-archive/charms/trusty/ceph-osd/next
Diff against target: 1132 lines (+217/-168)
18 files modified
config.yaml (+6/-0)
hooks/charmhelpers/contrib/network/ip.py (+52/-50)
hooks/charmhelpers/contrib/storage/linux/utils.py (+3/-2)
hooks/charmhelpers/core/fstab.py (+10/-8)
hooks/charmhelpers/core/hookenv.py (+19/-11)
hooks/charmhelpers/core/host.py (+22/-17)
hooks/charmhelpers/core/services/__init__.py (+2/-2)
hooks/charmhelpers/core/services/helpers.py (+9/-5)
hooks/charmhelpers/core/sysctl.py (+0/-34)
hooks/charmhelpers/core/templating.py (+2/-1)
hooks/charmhelpers/fetch/__init__.py (+14/-12)
hooks/charmhelpers/fetch/archiveurl.py (+53/-16)
hooks/charmhelpers/fetch/bzrurl.py (+5/-1)
hooks/hooks.py (+6/-0)
tests/charmhelpers/contrib/amulet/deployment.py (+3/-3)
tests/charmhelpers/contrib/amulet/utils.py (+6/-4)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+2/-1)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+3/-1)
To merge this branch: bzr merge lp:~niedbalski/charms/trusty/ceph-osd/sysctl-param
Reviewer Review Type Date Requested Status
James Page Pending
Corey Bryant Pending
Edward Hope-Morley Pending
OpenStack Charmers Pending
Review via email: mp+242813@code.launchpad.net

This proposal supersedes a proposal from 2014-11-25.

Description of the change

- Exposes the sysctl config option for update /etc/sysctl.d/50-charm-ceph-osd.conf using charmhelpers.core.sysctl.

- Linked with https://code.launchpad.net/~niedbalski/charms/trusty/ceph/sysctl-update/+merge/237379

To post a comment you must log in.
Revision history for this message
Corey Bryant (corey.bryant) wrote : Posted in a previous version of this proposal

Looks good. In terms of testing, lint passed. I didn't test anything else since I've already tested the same code for the ceph patch: https://code.launchpad.net/~niedbalski/charms/trusty/ceph/sysctl-update/+merge/237379.

review: Approve
Revision history for this message
James Page (james-page) wrote : Posted in a previous version of this proposal

Feel like this is only part of what we need to make ceph do opininated things when deployed via charms - do we have some sane default tuning for sysctl that we can apply here?

review: Needs Fixing
Revision history for this message
Ryan Beisner (1chb1n) wrote : Posted in a previous version of this proposal

UOSCI bot says:
This MP triggered a test on the Ubuntu OSCI system. Here is a summary of results.

#464 ceph-osd-next for niedbalski mp237381
charm_unit_test

This build:
http://10.98.191.181:8080/job/charm_unit_test/464/

MP URL:
https://code.launchpad.net/~niedbalski/charms/trusty/ceph-osd/sysctl-param/+merge/237381

Proposed branch:
lp:~niedbalski/charms/trusty/ceph-osd/sysctl-param

Results summary:
    UNIT FAIL: unit-test missing

UNIT Results not found.

Ubuntu OSCI Jenkins is currently in development on a Canonical private network, but we plan to publish results to a public instance soon. Tests are triggered if the proposed branch rev changes, or if the MP is placed into "Needs review" status after being otherwise for >= 1hr. Human review of results is still recommended.
http://10.98.191.181:8080/

Revision history for this message
Ryan Beisner (1chb1n) wrote : Posted in a previous version of this proposal

UOSCI bot says:
This MP triggered a test on the Ubuntu OSCI system. Here is a summary of results.

#658 ceph-osd-next for niedbalski mp237381
charm_lint_check

This build:
http://10.98.191.181:8080/job/charm_lint_check/658/

MP URL:
https://code.launchpad.net/~niedbalski/charms/trusty/ceph-osd/sysctl-param/+merge/237381

Proposed branch:
lp:~niedbalski/charms/trusty/ceph-osd/sysctl-param

Results summary:
    LINT OK: believed to pass, but you should confirm results

LINT Results (max last 25 lines) from
/var/lib/jenkins/workspace/charm_lint_check/make-lint.658:
I: all charms should provide at least one thing
I: config.yaml: option osd-journal has no default value
I: config.yaml: option ephemeral-unmount has no default value
I: config.yaml: option osd-reformat has no default value
I: config.yaml: option ceph-public-network has no default value
I: config.yaml: option ceph-cluster-network has no default value
I: config.yaml: option key has no default value

Ubuntu OSCI Jenkins is currently in development on a Canonical private network, but we plan to publish results to a public instance soon. Tests are triggered if the proposed branch rev changes, or if the MP is placed into "Needs review" status after being otherwise for >= 1hr. Human review of results is still recommended.
http://10.98.191.181:8080/

Revision history for this message
Ryan Beisner (1chb1n) wrote : Posted in a previous version of this proposal

UOSCI bot says:
This MP triggered a test on the Ubuntu OSCI system. Here is a summary of results.

#236 ceph-osd-next for niedbalski mp237381
charm_amulet_test

This build:
http://10.98.191.181:8080/job/charm_amulet_test/236/

MP URL:
https://code.launchpad.net/~niedbalski/charms/trusty/ceph-osd/sysctl-param/+merge/237381

Proposed branch:
lp:~niedbalski/charms/trusty/ceph-osd/sysctl-param

Results summary:
    AMULET FAIL: amulet-test failed

AMULET Results (max last 25 lines) from
/var/lib/jenkins/workspace/charm_amulet_test/make-test.236:
  File "/usr/lib/python2.7/dist-packages/jujuclient.py", line 899, in run
    for change_set in self.watch:
  File "/usr/lib/python2.7/dist-packages/jujuclient.py", line 266, in next
    return super(TimeoutWatcher, self).next()
  File "/usr/lib/python2.7/dist-packages/jujuclient.py", line 211, in next
    'Id': self.watcher_id})
  File "/usr/lib/python2.7/dist-packages/jujuclient.py", line 140, in _rpc
    raw = self.conn.recv()
  File "/usr/lib/python2.7/dist-packages/websocket/_core.py", line 663, in recv
    opcode, data = self.recv_data()
  File "/usr/lib/python2.7/dist-packages/websocket/_core.py", line 678, in recv_data
    frame = self.recv_frame()
  File "/usr/lib/python2.7/dist-packages/websocket/_core.py", line 753, in recv_frame
    frame_buffer.recv_header(self._recv_strict)
  File "/usr/lib/python2.7/dist-packages/websocket/_core.py", line 257, in recv_header
    header = recv_fn(2)
  File "/usr/lib/python2.7/dist-packages/websocket/_core.py", line 860, in _recv_strict
    bytes = self._recv(shortage)
  File "/usr/lib/python2.7/dist-packages/websocket/_core.py", line 842, in _recv
    bytes = self.sock.recv(bufsize)
  File "/usr/lib/python2.7/ssl.py", line 341, in recv
    return self.read(buflen)
  File "/usr/lib/python2.7/ssl.py", line 260, in read
    return self._sslobj.read(len)
socket.error: [Errno 110] Connection timed out

Ubuntu OSCI Jenkins is currently in development on a Canonical private network, but we plan to publish results to a public instance soon. Tests are triggered if the proposed branch rev changes, or if the MP is placed into "Needs review" status after being otherwise for >= 1hr. Human review of results is still recommended.
http://10.98.191.181:8080/

Revision history for this message
Jorge Niedbalski (niedbalski) wrote : Posted in a previous version of this proposal

Hello,

Any chance to get this merged?

Revision history for this message
James Page (james-page) wrote : Posted in a previous version of this proposal

Not until feedback has been resolved! marked wip.

Revision history for this message
Jorge Niedbalski (niedbalski) wrote : Posted in a previous version of this proposal

@james-page

Re-submitted according to your observations.

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

UOSCI bot says:
charm_unit_test #995 ceph-osd-next for niedbalski mp242424
    UNIT FAIL: unit-test missing

UNIT Results (max last 5 lines):
INFO:root:Workspace dir: /var/lib/jenkins/workspace/charm_unit_test
INFO:root:Reading file: Makefile
INFO:root:Searching for: ['nosetest', 'unit.test']
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.

Full unit test output: http://paste.ubuntu.com/9132357/
Build: http://10.98.191.181:8080/job/charm_unit_test/995/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

UOSCI bot says:
charm_lint_check #1161 ceph-osd-next for niedbalski mp242424
    LINT OK: passed

LINT Results (max last 5 lines):
  I: config.yaml: option ephemeral-unmount has no default value
  I: config.yaml: option osd-reformat has no default value
  I: config.yaml: option ceph-public-network has no default value
  I: config.yaml: option ceph-cluster-network has no default value
  I: config.yaml: option key has no default value

Full lint test output: http://paste.ubuntu.com/9132358/
Build: http://10.98.191.181:8080/job/charm_lint_check/1161/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

UOSCI bot says:
charm_amulet_test #503 ceph-osd-next for niedbalski mp242424
    AMULET FAIL: amulet-test failed

AMULET Results (max last 5 lines):
  ERROR waited for 10m0s without being able to connect: ssh: connect to host 10.217.3.218 port 22: No route to host
  juju-test.conductor WARNING : Could not bootstrap osci-sv07, got Bootstrap returned with an exit > 0. Skipping
  juju-test INFO : Results: 0 passed, 0 failed, 3 errored
  ERROR subprocess encountered error code 124
  make: *** [test] Error 124

Full amulet test output: http://paste.ubuntu.com/9132916/
Build: http://10.98.191.181:8080/job/charm_amulet_test/503/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

UOSCI bot says:
charm_unit_test #1052 ceph-osd-next for niedbalski mp242424
    UNIT FAIL: unit-test missing

UNIT Results (max last 5 lines):
INFO:root:Workspace dir: /var/lib/jenkins/workspace/charm_unit_test
INFO:root:Reading file: Makefile
INFO:root:Searching for: ['nosetest', 'unit.test']
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.

Full unit test output: http://paste.ubuntu.com/9222288/
Build: http://10.98.191.181:8080/job/charm_unit_test/1052/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

UOSCI bot says:
charm_lint_check #1218 ceph-osd-next for niedbalski mp242424
    LINT OK: passed

LINT Results (max last 5 lines):
  I: config.yaml: option ephemeral-unmount has no default value
  I: config.yaml: option osd-reformat has no default value
  I: config.yaml: option ceph-public-network has no default value
  I: config.yaml: option ceph-cluster-network has no default value
  I: config.yaml: option key has no default value

Full lint test output: http://paste.ubuntu.com/9222289/
Build: http://10.98.191.181:8080/job/charm_lint_check/1218/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

UOSCI bot says:
charm_amulet_test #522 ceph-osd-next for niedbalski mp242424
    AMULET FAIL: amulet-test failed

AMULET Results (max last 5 lines):
  juju-test.conductor DEBUG : Calling "juju destroy-environment -y osci-sv07"
  WARNING cannot delete security group "juju-osci-sv07-0". Used by another environment?
  juju-test INFO : Results: 1 passed, 1 failed, 1 errored
  ERROR subprocess encountered error code 1
  make: *** [test] Error 1

Full amulet test output: http://paste.ubuntu.com/9222651/
Build: http://10.98.191.181:8080/job/charm_amulet_test/522/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2014-10-06 22:11:14 +0000
3+++ config.yaml 2014-11-25 16:59:28 +0000
4@@ -121,3 +121,9 @@
5 order for this charm to function correctly, the privacy extension must be
6 disabled and a non-temporary address must be configured/available on
7 your network interface.
8+ sysctl:
9+ type: string
10+ default:
11+ description: |
12+ YAML formatted associative array of sysctl values, e.g.:
13+ '{ kernel.pid_max : 4194303 }'
14
15=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
16--- hooks/charmhelpers/contrib/network/ip.py 2014-10-21 07:56:51 +0000
17+++ hooks/charmhelpers/contrib/network/ip.py 2014-11-25 16:59:28 +0000
18@@ -1,15 +1,12 @@
19 import glob
20 import re
21 import subprocess
22-import sys
23
24 from functools import partial
25
26 from charmhelpers.core.hookenv import unit_get
27 from charmhelpers.fetch import apt_install
28 from charmhelpers.core.hookenv import (
29- WARNING,
30- ERROR,
31 log
32 )
33
34@@ -34,31 +31,28 @@
35 network)
36
37
38+def no_ip_found_error_out(network):
39+ errmsg = ("No IP address found in network: %s" % network)
40+ raise ValueError(errmsg)
41+
42+
43 def get_address_in_network(network, fallback=None, fatal=False):
44- """
45- Get an IPv4 or IPv6 address within the network from the host.
46+ """Get an IPv4 or IPv6 address within the network from the host.
47
48 :param network (str): CIDR presentation format. For example,
49 '192.168.1.0/24'.
50 :param fallback (str): If no address is found, return fallback.
51 :param fatal (boolean): If no address is found, fallback is not
52 set and fatal is True then exit(1).
53-
54 """
55-
56- def not_found_error_out():
57- log("No IP address found in network: %s" % network,
58- level=ERROR)
59- sys.exit(1)
60-
61 if network is None:
62 if fallback is not None:
63 return fallback
64+
65+ if fatal:
66+ no_ip_found_error_out(network)
67 else:
68- if fatal:
69- not_found_error_out()
70- else:
71- return None
72+ return None
73
74 _validate_cidr(network)
75 network = netaddr.IPNetwork(network)
76@@ -70,6 +64,7 @@
77 cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
78 if cidr in network:
79 return str(cidr.ip)
80+
81 if network.version == 6 and netifaces.AF_INET6 in addresses:
82 for addr in addresses[netifaces.AF_INET6]:
83 if not addr['addr'].startswith('fe80'):
84@@ -82,20 +77,20 @@
85 return fallback
86
87 if fatal:
88- not_found_error_out()
89+ no_ip_found_error_out(network)
90
91 return None
92
93
94 def is_ipv6(address):
95- '''Determine whether provided address is IPv6 or not'''
96+ """Determine whether provided address is IPv6 or not."""
97 try:
98 address = netaddr.IPAddress(address)
99 except netaddr.AddrFormatError:
100 # probably a hostname - so not an address at all!
101 return False
102- else:
103- return address.version == 6
104+
105+ return address.version == 6
106
107
108 def is_address_in_network(network, address):
109@@ -113,11 +108,13 @@
110 except (netaddr.core.AddrFormatError, ValueError):
111 raise ValueError("Network (%s) is not in CIDR presentation format" %
112 network)
113+
114 try:
115 address = netaddr.IPAddress(address)
116 except (netaddr.core.AddrFormatError, ValueError):
117 raise ValueError("Address (%s) is not in correct presentation format" %
118 address)
119+
120 if address in network:
121 return True
122 else:
123@@ -147,6 +144,7 @@
124 return iface
125 else:
126 return addresses[netifaces.AF_INET][0][key]
127+
128 if address.version == 6 and netifaces.AF_INET6 in addresses:
129 for addr in addresses[netifaces.AF_INET6]:
130 if not addr['addr'].startswith('fe80'):
131@@ -160,41 +158,42 @@
132 return str(cidr).split('/')[1]
133 else:
134 return addr[key]
135+
136 return None
137
138
139 get_iface_for_address = partial(_get_for_address, key='iface')
140
141+
142 get_netmask_for_address = partial(_get_for_address, key='netmask')
143
144
145 def format_ipv6_addr(address):
146- """
147- IPv6 needs to be wrapped with [] in url link to parse correctly.
148+ """If address is IPv6, wrap it in '[]' otherwise return None.
149+
150+ This is required by most configuration files when specifying IPv6
151+ addresses.
152 """
153 if is_ipv6(address):
154- address = "[%s]" % address
155- else:
156- log("Not a valid ipv6 address: %s" % address, level=WARNING)
157- address = None
158+ return "[%s]" % address
159
160- return address
161+ return None
162
163
164 def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
165 fatal=True, exc_list=None):
166- """
167- Return the assigned IP address for a given interface, if any, or [].
168- """
169+ """Return the assigned IP address for a given interface, if any."""
170 # Extract nic if passed /dev/ethX
171 if '/' in iface:
172 iface = iface.split('/')[-1]
173+
174 if not exc_list:
175 exc_list = []
176+
177 try:
178 inet_num = getattr(netifaces, inet_type)
179 except AttributeError:
180- raise Exception('Unknown inet type ' + str(inet_type))
181+ raise Exception("Unknown inet type '%s'" % str(inet_type))
182
183 interfaces = netifaces.interfaces()
184 if inc_aliases:
185@@ -202,15 +201,18 @@
186 for _iface in interfaces:
187 if iface == _iface or _iface.split(':')[0] == iface:
188 ifaces.append(_iface)
189+
190 if fatal and not ifaces:
191 raise Exception("Invalid interface '%s'" % iface)
192+
193 ifaces.sort()
194 else:
195 if iface not in interfaces:
196 if fatal:
197- raise Exception("%s not found " % (iface))
198+ raise Exception("Interface '%s' not found " % (iface))
199 else:
200 return []
201+
202 else:
203 ifaces = [iface]
204
205@@ -221,10 +223,13 @@
206 for entry in net_info[inet_num]:
207 if 'addr' in entry and entry['addr'] not in exc_list:
208 addresses.append(entry['addr'])
209+
210 if fatal and not addresses:
211 raise Exception("Interface '%s' doesn't have any %s addresses." %
212 (iface, inet_type))
213- return addresses
214+
215+ return sorted(addresses)
216+
217
218 get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')
219
220@@ -241,6 +246,7 @@
221 raw = re.match(ll_key, _addr)
222 if raw:
223 _addr = raw.group(1)
224+
225 if _addr == addr:
226 log("Address '%s' is configured on iface '%s'" %
227 (addr, iface))
228@@ -251,8 +257,9 @@
229
230
231 def sniff_iface(f):
232- """If no iface provided, inject net iface inferred from unit private
233- address.
234+ """Ensure decorated function is called with a value for iface.
235+
236+ If no iface provided, inject net iface inferred from unit private address.
237 """
238 def iface_sniffer(*args, **kwargs):
239 if not kwargs.get('iface', None):
240@@ -295,7 +302,7 @@
241 if global_addrs:
242 # Make sure any found global addresses are not temporary
243 cmd = ['ip', 'addr', 'show', iface]
244- out = subprocess.check_output(cmd)
245+ out = subprocess.check_output(cmd).decode('UTF-8')
246 if dynamic_only:
247 key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
248 else:
249@@ -317,33 +324,28 @@
250 return addrs
251
252 if fatal:
253- raise Exception("Interface '%s' doesn't have a scope global "
254+ raise Exception("Interface '%s' does not have a scope global "
255 "non-temporary ipv6 address." % iface)
256
257 return []
258
259
260 def get_bridges(vnic_dir='/sys/devices/virtual/net'):
261- """
262- Return a list of bridges on the system or []
263- """
264- b_rgex = vnic_dir + '/*/bridge'
265- return [x.replace(vnic_dir, '').split('/')[1] for x in glob.glob(b_rgex)]
266+ """Return a list of bridges on the system."""
267+ b_regex = "%s/*/bridge" % vnic_dir
268+ return [x.replace(vnic_dir, '').split('/')[1] for x in glob.glob(b_regex)]
269
270
271 def get_bridge_nics(bridge, vnic_dir='/sys/devices/virtual/net'):
272- """
273- Return a list of nics comprising a given bridge on the system or []
274- """
275- brif_rgex = "%s/%s/brif/*" % (vnic_dir, bridge)
276- return [x.split('/')[-1] for x in glob.glob(brif_rgex)]
277+ """Return a list of nics comprising a given bridge on the system."""
278+ brif_regex = "%s/%s/brif/*" % (vnic_dir, bridge)
279+ return [x.split('/')[-1] for x in glob.glob(brif_regex)]
280
281
282 def is_bridge_member(nic):
283- """
284- Check if a given nic is a member of a bridge
285- """
286+ """Check if a given nic is a member of a bridge."""
287 for bridge in get_bridges():
288 if nic in get_bridge_nics(bridge):
289 return True
290+
291 return False
292
293=== modified file 'hooks/charmhelpers/contrib/storage/linux/utils.py'
294--- hooks/charmhelpers/contrib/storage/linux/utils.py 2014-09-28 21:26:48 +0000
295+++ hooks/charmhelpers/contrib/storage/linux/utils.py 2014-11-25 16:59:28 +0000
296@@ -30,7 +30,8 @@
297 # sometimes sgdisk exits non-zero; this is OK, dd will clean up
298 call(['sgdisk', '--zap-all', '--mbrtogpt',
299 '--clear', block_device])
300- dev_end = check_output(['blockdev', '--getsz', block_device])
301+ dev_end = check_output(['blockdev', '--getsz',
302+ block_device]).decode('UTF-8')
303 gpt_end = int(dev_end.split()[0]) - 100
304 check_call(['dd', 'if=/dev/zero', 'of=%s' % (block_device),
305 'bs=1M', 'count=1'])
306@@ -47,7 +48,7 @@
307 it doesn't.
308 '''
309 is_partition = bool(re.search(r".*[0-9]+\b", device))
310- out = check_output(['mount'])
311+ out = check_output(['mount']).decode('UTF-8')
312 if is_partition:
313 return bool(re.search(device + r"\b", out))
314 return bool(re.search(device + r"[0-9]+\b", out))
315
316=== modified file 'hooks/charmhelpers/core/fstab.py'
317--- hooks/charmhelpers/core/fstab.py 2014-07-24 09:41:12 +0000
318+++ hooks/charmhelpers/core/fstab.py 2014-11-25 16:59:28 +0000
319@@ -3,10 +3,11 @@
320
321 __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
322
323+import io
324 import os
325
326
327-class Fstab(file):
328+class Fstab(io.FileIO):
329 """This class extends file in order to implement a file reader/writer
330 for file `/etc/fstab`
331 """
332@@ -24,8 +25,8 @@
333 options = "defaults"
334
335 self.options = options
336- self.d = d
337- self.p = p
338+ self.d = int(d)
339+ self.p = int(p)
340
341 def __eq__(self, o):
342 return str(self) == str(o)
343@@ -45,7 +46,7 @@
344 self._path = path
345 else:
346 self._path = self.DEFAULT_PATH
347- file.__init__(self, self._path, 'r+')
348+ super(Fstab, self).__init__(self._path, 'rb+')
349
350 def _hydrate_entry(self, line):
351 # NOTE: use split with no arguments to split on any
352@@ -58,8 +59,9 @@
353 def entries(self):
354 self.seek(0)
355 for line in self.readlines():
356+ line = line.decode('us-ascii')
357 try:
358- if not line.startswith("#"):
359+ if line.strip() and not line.startswith("#"):
360 yield self._hydrate_entry(line)
361 except ValueError:
362 pass
363@@ -75,14 +77,14 @@
364 if self.get_entry_by_attr('device', entry.device):
365 return False
366
367- self.write(str(entry) + '\n')
368+ self.write((str(entry) + '\n').encode('us-ascii'))
369 self.truncate()
370 return entry
371
372 def remove_entry(self, entry):
373 self.seek(0)
374
375- lines = self.readlines()
376+ lines = [l.decode('us-ascii') for l in self.readlines()]
377
378 found = False
379 for index, line in enumerate(lines):
380@@ -97,7 +99,7 @@
381 lines.remove(line)
382
383 self.seek(0)
384- self.write(''.join(lines))
385+ self.write(''.join(lines).encode('us-ascii'))
386 self.truncate()
387 return True
388
389
390=== modified file 'hooks/charmhelpers/core/hookenv.py'
391--- hooks/charmhelpers/core/hookenv.py 2014-10-21 07:56:51 +0000
392+++ hooks/charmhelpers/core/hookenv.py 2014-11-25 16:59:28 +0000
393@@ -9,9 +9,14 @@
394 import yaml
395 import subprocess
396 import sys
397-import UserDict
398 from subprocess import CalledProcessError
399
400+import six
401+if not six.PY3:
402+ from UserDict import UserDict
403+else:
404+ from collections import UserDict
405+
406 CRITICAL = "CRITICAL"
407 ERROR = "ERROR"
408 WARNING = "WARNING"
409@@ -67,12 +72,12 @@
410 subprocess.call(command)
411
412
413-class Serializable(UserDict.IterableUserDict):
414+class Serializable(UserDict):
415 """Wrapper, an object that can be serialized to yaml or json"""
416
417 def __init__(self, obj):
418 # wrap the object
419- UserDict.IterableUserDict.__init__(self)
420+ UserDict.__init__(self)
421 self.data = obj
422
423 def __getattr__(self, attr):
424@@ -269,7 +274,7 @@
425
426 """
427 if self._prev_dict:
428- for k, v in self._prev_dict.iteritems():
429+ for k, v in six.iteritems(self._prev_dict):
430 if k not in self:
431 self[k] = v
432 with open(self.path, 'w') as f:
433@@ -284,7 +289,8 @@
434 config_cmd_line.append(scope)
435 config_cmd_line.append('--format=json')
436 try:
437- config_data = json.loads(subprocess.check_output(config_cmd_line))
438+ config_data = json.loads(
439+ subprocess.check_output(config_cmd_line).decode('UTF-8'))
440 if scope is not None:
441 return config_data
442 return Config(config_data)
443@@ -303,10 +309,10 @@
444 if unit:
445 _args.append(unit)
446 try:
447- return json.loads(subprocess.check_output(_args))
448+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
449 except ValueError:
450 return None
451- except CalledProcessError, e:
452+ except CalledProcessError as e:
453 if e.returncode == 2:
454 return None
455 raise
456@@ -318,7 +324,7 @@
457 relation_cmd_line = ['relation-set']
458 if relation_id is not None:
459 relation_cmd_line.extend(('-r', relation_id))
460- for k, v in (relation_settings.items() + kwargs.items()):
461+ for k, v in (list(relation_settings.items()) + list(kwargs.items())):
462 if v is None:
463 relation_cmd_line.append('{}='.format(k))
464 else:
465@@ -335,7 +341,8 @@
466 relid_cmd_line = ['relation-ids', '--format=json']
467 if reltype is not None:
468 relid_cmd_line.append(reltype)
469- return json.loads(subprocess.check_output(relid_cmd_line)) or []
470+ return json.loads(
471+ subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
472 return []
473
474
475@@ -346,7 +353,8 @@
476 units_cmd_line = ['relation-list', '--format=json']
477 if relid is not None:
478 units_cmd_line.extend(('-r', relid))
479- return json.loads(subprocess.check_output(units_cmd_line)) or []
480+ return json.loads(
481+ subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
482
483
484 @cached
485@@ -455,7 +463,7 @@
486 """Get the unit ID for the remote unit"""
487 _args = ['unit-get', '--format=json', attribute]
488 try:
489- return json.loads(subprocess.check_output(_args))
490+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
491 except ValueError:
492 return None
493
494
495=== modified file 'hooks/charmhelpers/core/host.py'
496--- hooks/charmhelpers/core/host.py 2014-10-21 07:56:51 +0000
497+++ hooks/charmhelpers/core/host.py 2014-11-25 16:59:28 +0000
498@@ -14,11 +14,12 @@
499 import subprocess
500 import hashlib
501 from contextlib import contextmanager
502-
503 from collections import OrderedDict
504
505-from hookenv import log
506-from fstab import Fstab
507+import six
508+
509+from .hookenv import log
510+from .fstab import Fstab
511
512
513 def service_start(service_name):
514@@ -54,7 +55,9 @@
515 def service_running(service):
516 """Determine whether a system service is running"""
517 try:
518- output = subprocess.check_output(['service', service, 'status'], stderr=subprocess.STDOUT)
519+ output = subprocess.check_output(
520+ ['service', service, 'status'],
521+ stderr=subprocess.STDOUT).decode('UTF-8')
522 except subprocess.CalledProcessError:
523 return False
524 else:
525@@ -67,7 +70,9 @@
526 def service_available(service_name):
527 """Determine whether a system service is available"""
528 try:
529- subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)
530+ subprocess.check_output(
531+ ['service', service_name, 'status'],
532+ stderr=subprocess.STDOUT).decode('UTF-8')
533 except subprocess.CalledProcessError as e:
534 return 'unrecognized service' not in e.output
535 else:
536@@ -115,7 +120,7 @@
537 cmd.append(from_path)
538 cmd.append(to_path)
539 log(" ".join(cmd))
540- return subprocess.check_output(cmd).strip()
541+ return subprocess.check_output(cmd).decode('UTF-8').strip()
542
543
544 def symlink(source, destination):
545@@ -130,7 +135,7 @@
546 subprocess.check_call(cmd)
547
548
549-def mkdir(path, owner='root', group='root', perms=0555, force=False):
550+def mkdir(path, owner='root', group='root', perms=0o555, force=False):
551 """Create a directory"""
552 log("Making dir {} {}:{} {:o}".format(path, owner, group,
553 perms))
554@@ -146,7 +151,7 @@
555 os.chown(realpath, uid, gid)
556
557
558-def write_file(path, content, owner='root', group='root', perms=0444):
559+def write_file(path, content, owner='root', group='root', perms=0o444):
560 """Create or overwrite a file with the contents of a string"""
561 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
562 uid = pwd.getpwnam(owner).pw_uid
563@@ -177,7 +182,7 @@
564 cmd_args.extend([device, mountpoint])
565 try:
566 subprocess.check_output(cmd_args)
567- except subprocess.CalledProcessError, e:
568+ except subprocess.CalledProcessError as e:
569 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
570 return False
571
572@@ -191,7 +196,7 @@
573 cmd_args = ['umount', mountpoint]
574 try:
575 subprocess.check_output(cmd_args)
576- except subprocess.CalledProcessError, e:
577+ except subprocess.CalledProcessError as e:
578 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
579 return False
580
581@@ -218,8 +223,8 @@
582 """
583 if os.path.exists(path):
584 h = getattr(hashlib, hash_type)()
585- with open(path, 'r') as source:
586- h.update(source.read()) # IGNORE:E1101 - it does have update
587+ with open(path, 'rb') as source:
588+ h.update(source.read())
589 return h.hexdigest()
590 else:
591 return None
592@@ -297,7 +302,7 @@
593 if length is None:
594 length = random.choice(range(35, 45))
595 alphanumeric_chars = [
596- l for l in (string.letters + string.digits)
597+ l for l in (string.ascii_letters + string.digits)
598 if l not in 'l0QD1vAEIOUaeiou']
599 random_chars = [
600 random.choice(alphanumeric_chars) for _ in range(length)]
601@@ -306,14 +311,14 @@
602
603 def list_nics(nic_type):
604 '''Return a list of nics of given type(s)'''
605- if isinstance(nic_type, basestring):
606+ if isinstance(nic_type, six.string_types):
607 int_types = [nic_type]
608 else:
609 int_types = nic_type
610 interfaces = []
611 for int_type in int_types:
612 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
613- ip_output = subprocess.check_output(cmd).split('\n')
614+ ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
615 ip_output = (line for line in ip_output if line)
616 for line in ip_output:
617 if line.split()[1].startswith(int_type):
618@@ -335,7 +340,7 @@
619
620 def get_nic_mtu(nic):
621 cmd = ['ip', 'addr', 'show', nic]
622- ip_output = subprocess.check_output(cmd).split('\n')
623+ ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
624 mtu = ""
625 for line in ip_output:
626 words = line.split()
627@@ -346,7 +351,7 @@
628
629 def get_nic_hwaddr(nic):
630 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
631- ip_output = subprocess.check_output(cmd)
632+ ip_output = subprocess.check_output(cmd).decode('UTF-8')
633 hwaddr = ""
634 words = ip_output.split()
635 if 'link/ether' in words:
636
637=== modified file 'hooks/charmhelpers/core/services/__init__.py'
638--- hooks/charmhelpers/core/services/__init__.py 2014-09-26 08:25:57 +0000
639+++ hooks/charmhelpers/core/services/__init__.py 2014-11-25 16:59:28 +0000
640@@ -1,2 +1,2 @@
641-from .base import *
642-from .helpers import *
643+from .base import * # NOQA
644+from .helpers import * # NOQA
645
646=== modified file 'hooks/charmhelpers/core/services/helpers.py'
647--- hooks/charmhelpers/core/services/helpers.py 2014-09-26 08:25:57 +0000
648+++ hooks/charmhelpers/core/services/helpers.py 2014-11-25 16:59:28 +0000
649@@ -196,7 +196,7 @@
650 if not os.path.isabs(file_name):
651 file_name = os.path.join(hookenv.charm_dir(), file_name)
652 with open(file_name, 'w') as file_stream:
653- os.fchmod(file_stream.fileno(), 0600)
654+ os.fchmod(file_stream.fileno(), 0o600)
655 yaml.dump(config_data, file_stream)
656
657 def read_context(self, file_name):
658@@ -211,15 +211,19 @@
659
660 class TemplateCallback(ManagerCallback):
661 """
662- Callback class that will render a Jinja2 template, for use as a ready action.
663-
664- :param str source: The template source file, relative to `$CHARM_DIR/templates`
665+ Callback class that will render a Jinja2 template, for use as a ready
666+ action.
667+
668+ :param str source: The template source file, relative to
669+ `$CHARM_DIR/templates`
670+
671 :param str target: The target to write the rendered template to
672 :param str owner: The owner of the rendered file
673 :param str group: The group of the rendered file
674 :param int perms: The permissions of the rendered file
675 """
676- def __init__(self, source, target, owner='root', group='root', perms=0444):
677+ def __init__(self, source, target,
678+ owner='root', group='root', perms=0o444):
679 self.source = source
680 self.target = target
681 self.owner = owner
682
683=== added file 'hooks/charmhelpers/core/sysctl.py'
684--- hooks/charmhelpers/core/sysctl.py 1970-01-01 00:00:00 +0000
685+++ hooks/charmhelpers/core/sysctl.py 2014-11-25 16:59:28 +0000
686@@ -0,0 +1,34 @@
687+#!/usr/bin/env python
688+# -*- coding: utf-8 -*-
689+
690+__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
691+
692+import yaml
693+
694+from subprocess import check_call
695+
696+from charmhelpers.core.hookenv import (
697+ log,
698+ DEBUG,
699+)
700+
701+
702+def create(sysctl_dict, sysctl_file):
703+ """Creates a sysctl.conf file from a YAML associative array
704+
705+ :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 }
706+ :type sysctl_dict: dict
707+ :param sysctl_file: path to the sysctl file to be saved
708+ :type sysctl_file: str or unicode
709+ :returns: None
710+ """
711+ sysctl_dict = yaml.load(sysctl_dict)
712+
713+ with open(sysctl_file, "w") as fd:
714+ for key, value in sysctl_dict.items():
715+ fd.write("{}={}\n".format(key, value))
716+
717+ log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict),
718+ level=DEBUG)
719+
720+ check_call(["sysctl", "-p", sysctl_file])
721
722=== removed file 'hooks/charmhelpers/core/sysctl.py'
723--- hooks/charmhelpers/core/sysctl.py 2014-10-21 07:56:51 +0000
724+++ hooks/charmhelpers/core/sysctl.py 1970-01-01 00:00:00 +0000
725@@ -1,34 +0,0 @@
726-#!/usr/bin/env python
727-# -*- coding: utf-8 -*-
728-
729-__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
730-
731-import yaml
732-
733-from subprocess import check_call
734-
735-from charmhelpers.core.hookenv import (
736- log,
737- DEBUG,
738-)
739-
740-
741-def create(sysctl_dict, sysctl_file):
742- """Creates a sysctl.conf file from a YAML associative array
743-
744- :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 }
745- :type sysctl_dict: dict
746- :param sysctl_file: path to the sysctl file to be saved
747- :type sysctl_file: str or unicode
748- :returns: None
749- """
750- sysctl_dict = yaml.load(sysctl_dict)
751-
752- with open(sysctl_file, "w") as fd:
753- for key, value in sysctl_dict.items():
754- fd.write("{}={}\n".format(key, value))
755-
756- log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict),
757- level=DEBUG)
758-
759- check_call(["sysctl", "-p", sysctl_file])
760
761=== modified file 'hooks/charmhelpers/core/templating.py'
762--- hooks/charmhelpers/core/templating.py 2014-09-26 08:25:57 +0000
763+++ hooks/charmhelpers/core/templating.py 2014-11-25 16:59:28 +0000
764@@ -4,7 +4,8 @@
765 from charmhelpers.core import hookenv
766
767
768-def render(source, target, context, owner='root', group='root', perms=0444, templates_dir=None):
769+def render(source, target, context, owner='root', group='root',
770+ perms=0o444, templates_dir=None):
771 """
772 Render a template.
773
774
775=== modified file 'hooks/charmhelpers/fetch/__init__.py'
776--- hooks/charmhelpers/fetch/__init__.py 2014-10-21 07:56:51 +0000
777+++ hooks/charmhelpers/fetch/__init__.py 2014-11-25 16:59:28 +0000
778@@ -5,10 +5,6 @@
779 from charmhelpers.core.host import (
780 lsb_release
781 )
782-from urlparse import (
783- urlparse,
784- urlunparse,
785-)
786 import subprocess
787 from charmhelpers.core.hookenv import (
788 config,
789@@ -16,6 +12,12 @@
790 )
791 import os
792
793+import six
794+if six.PY3:
795+ from urllib.parse import urlparse, urlunparse
796+else:
797+ from urlparse import urlparse, urlunparse
798+
799
800 CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
801 deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
802@@ -149,7 +151,7 @@
803 cmd = ['apt-get', '--assume-yes']
804 cmd.extend(options)
805 cmd.append('install')
806- if isinstance(packages, basestring):
807+ if isinstance(packages, six.string_types):
808 cmd.append(packages)
809 else:
810 cmd.extend(packages)
811@@ -182,7 +184,7 @@
812 def apt_purge(packages, fatal=False):
813 """Purge one or more packages"""
814 cmd = ['apt-get', '--assume-yes', 'purge']
815- if isinstance(packages, basestring):
816+ if isinstance(packages, six.string_types):
817 cmd.append(packages)
818 else:
819 cmd.extend(packages)
820@@ -193,7 +195,7 @@
821 def apt_hold(packages, fatal=False):
822 """Hold one or more packages"""
823 cmd = ['apt-mark', 'hold']
824- if isinstance(packages, basestring):
825+ if isinstance(packages, six.string_types):
826 cmd.append(packages)
827 else:
828 cmd.extend(packages)
829@@ -256,11 +258,11 @@
830 elif source == 'distro':
831 pass
832 else:
833- raise SourceConfigError("Unknown source: {!r}".format(source))
834+ log("Unknown source: {!r}".format(source))
835
836 if key:
837 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
838- with NamedTemporaryFile() as key_file:
839+ with NamedTemporaryFile('w+') as key_file:
840 key_file.write(key)
841 key_file.flush()
842 key_file.seek(0)
843@@ -297,14 +299,14 @@
844 sources = safe_load((config(sources_var) or '').strip()) or []
845 keys = safe_load((config(keys_var) or '').strip()) or None
846
847- if isinstance(sources, basestring):
848+ if isinstance(sources, six.string_types):
849 sources = [sources]
850
851 if keys is None:
852 for source in sources:
853 add_source(source, None)
854 else:
855- if isinstance(keys, basestring):
856+ if isinstance(keys, six.string_types):
857 keys = [keys]
858
859 if len(sources) != len(keys):
860@@ -401,7 +403,7 @@
861 while result is None or result == APT_NO_LOCK:
862 try:
863 result = subprocess.check_call(cmd, env=env)
864- except subprocess.CalledProcessError, e:
865+ except subprocess.CalledProcessError as e:
866 retry_count = retry_count + 1
867 if retry_count > APT_NO_LOCK_RETRY_COUNT:
868 raise
869
870=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
871--- hooks/charmhelpers/fetch/archiveurl.py 2014-09-28 21:26:48 +0000
872+++ hooks/charmhelpers/fetch/archiveurl.py 2014-11-25 16:59:28 +0000
873@@ -1,8 +1,23 @@
874 import os
875-import urllib2
876-from urllib import urlretrieve
877-import urlparse
878 import hashlib
879+import re
880+
881+import six
882+if six.PY3:
883+ from urllib.request import (
884+ build_opener, install_opener, urlopen, urlretrieve,
885+ HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
886+ )
887+ from urllib.parse import urlparse, urlunparse, parse_qs
888+ from urllib.error import URLError
889+else:
890+ from urllib import urlretrieve
891+ from urllib2 import (
892+ build_opener, install_opener, urlopen,
893+ HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
894+ URLError
895+ )
896+ from urlparse import urlparse, urlunparse, parse_qs
897
898 from charmhelpers.fetch import (
899 BaseFetchHandler,
900@@ -15,6 +30,24 @@
901 from charmhelpers.core.host import mkdir, check_hash
902
903
904+def splituser(host):
905+ '''urllib.splituser(), but six's support of this seems broken'''
906+ _userprog = re.compile('^(.*)@(.*)$')
907+ match = _userprog.match(host)
908+ if match:
909+ return match.group(1, 2)
910+ return None, host
911+
912+
913+def splitpasswd(user):
914+ '''urllib.splitpasswd(), but six's support of this is missing'''
915+ _passwdprog = re.compile('^([^:]*):(.*)$', re.S)
916+ match = _passwdprog.match(user)
917+ if match:
918+ return match.group(1, 2)
919+ return user, None
920+
921+
922 class ArchiveUrlFetchHandler(BaseFetchHandler):
923 """
924 Handler to download archive files from arbitrary URLs.
925@@ -42,20 +75,20 @@
926 """
927 # propogate all exceptions
928 # URLError, OSError, etc
929- proto, netloc, path, params, query, fragment = urlparse.urlparse(source)
930+ proto, netloc, path, params, query, fragment = urlparse(source)
931 if proto in ('http', 'https'):
932- auth, barehost = urllib2.splituser(netloc)
933+ auth, barehost = splituser(netloc)
934 if auth is not None:
935- source = urlparse.urlunparse((proto, barehost, path, params, query, fragment))
936- username, password = urllib2.splitpasswd(auth)
937- passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
938+ source = urlunparse((proto, barehost, path, params, query, fragment))
939+ username, password = splitpasswd(auth)
940+ passman = HTTPPasswordMgrWithDefaultRealm()
941 # Realm is set to None in add_password to force the username and password
942 # to be used whatever the realm
943 passman.add_password(None, source, username, password)
944- authhandler = urllib2.HTTPBasicAuthHandler(passman)
945- opener = urllib2.build_opener(authhandler)
946- urllib2.install_opener(opener)
947- response = urllib2.urlopen(source)
948+ authhandler = HTTPBasicAuthHandler(passman)
949+ opener = build_opener(authhandler)
950+ install_opener(opener)
951+ response = urlopen(source)
952 try:
953 with open(dest, 'w') as dest_file:
954 dest_file.write(response.read())
955@@ -91,17 +124,21 @@
956 url_parts = self.parse_url(source)
957 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
958 if not os.path.exists(dest_dir):
959- mkdir(dest_dir, perms=0755)
960+ mkdir(dest_dir, perms=0o755)
961 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
962 try:
963 self.download(source, dld_file)
964- except urllib2.URLError as e:
965+ except URLError as e:
966 raise UnhandledSource(e.reason)
967 except OSError as e:
968 raise UnhandledSource(e.strerror)
969- options = urlparse.parse_qs(url_parts.fragment)
970+ options = parse_qs(url_parts.fragment)
971 for key, value in options.items():
972- if key in hashlib.algorithms:
973+ if not six.PY3:
974+ algorithms = hashlib.algorithms
975+ else:
976+ algorithms = hashlib.algorithms_available
977+ if key in algorithms:
978 check_hash(dld_file, value, key)
979 if checksum:
980 check_hash(dld_file, checksum, hash_type)
981
982=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
983--- hooks/charmhelpers/fetch/bzrurl.py 2014-07-25 08:07:41 +0000
984+++ hooks/charmhelpers/fetch/bzrurl.py 2014-11-25 16:59:28 +0000
985@@ -5,6 +5,10 @@
986 )
987 from charmhelpers.core.host import mkdir
988
989+import six
990+if six.PY3:
991+ raise ImportError('bzrlib does not support Python3')
992+
993 try:
994 from bzrlib.branch import Branch
995 except ImportError:
996@@ -42,7 +46,7 @@
997 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
998 branch_name)
999 if not os.path.exists(dest_dir):
1000- mkdir(dest_dir, perms=0755)
1001+ mkdir(dest_dir, perms=0o755)
1002 try:
1003 self.branch(source, dest_dir)
1004 except OSError as e:
1005
1006=== modified file 'hooks/hooks.py'
1007--- hooks/hooks.py 2014-09-30 03:41:06 +0000
1008+++ hooks/hooks.py 2014-11-25 16:59:28 +0000
1009@@ -36,6 +36,8 @@
1010 filter_installed_packages,
1011 )
1012
1013+from charmhelpers.core.sysctl import create as create_sysctl
1014+
1015 from utils import (
1016 render_template,
1017 get_host_ip,
1018@@ -110,6 +112,10 @@
1019 if config('prefer-ipv6'):
1020 assert_charm_supports_ipv6()
1021
1022+ sysctl_dict = config('sysctl')
1023+ if sysctl_dict:
1024+ create_sysctl(sysctl_dict, '/etc/sysctl.d/50-ceph-osd-charm.conf')
1025+
1026 e_mountpoint = config('ephemeral-unmount')
1027 if (e_mountpoint and ceph.filesystem_mounted(e_mountpoint)):
1028 umount(e_mountpoint)
1029
1030=== modified file 'tests/charmhelpers/contrib/amulet/deployment.py'
1031--- tests/charmhelpers/contrib/amulet/deployment.py 2014-09-27 17:18:26 +0000
1032+++ tests/charmhelpers/contrib/amulet/deployment.py 2014-11-25 16:59:28 +0000
1033@@ -1,6 +1,6 @@
1034 import amulet
1035-
1036 import os
1037+import six
1038
1039
1040 class AmuletDeployment(object):
1041@@ -52,12 +52,12 @@
1042
1043 def _add_relations(self, relations):
1044 """Add all of the relations for the services."""
1045- for k, v in relations.iteritems():
1046+ for k, v in six.iteritems(relations):
1047 self.d.relate(k, v)
1048
1049 def _configure_services(self, configs):
1050 """Configure all of the services."""
1051- for service, config in configs.iteritems():
1052+ for service, config in six.iteritems(configs):
1053 self.d.configure(service, config)
1054
1055 def _deploy(self):
1056
1057=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
1058--- tests/charmhelpers/contrib/amulet/utils.py 2014-09-27 17:18:26 +0000
1059+++ tests/charmhelpers/contrib/amulet/utils.py 2014-11-25 16:59:28 +0000
1060@@ -5,6 +5,8 @@
1061 import sys
1062 import time
1063
1064+import six
1065+
1066
1067 class AmuletUtils(object):
1068 """Amulet utilities.
1069@@ -58,7 +60,7 @@
1070 Verify the specified services are running on the corresponding
1071 service units.
1072 """
1073- for k, v in commands.iteritems():
1074+ for k, v in six.iteritems(commands):
1075 for cmd in v:
1076 output, code = k.run(cmd)
1077 if code != 0:
1078@@ -100,11 +102,11 @@
1079 longs, or can be a function that evaluate a variable and returns a
1080 bool.
1081 """
1082- for k, v in expected.iteritems():
1083+ for k, v in six.iteritems(expected):
1084 if k in actual:
1085- if (isinstance(v, basestring) or
1086+ if (isinstance(v, six.string_types) or
1087 isinstance(v, bool) or
1088- isinstance(v, (int, long))):
1089+ isinstance(v, six.integer_types)):
1090 if v != actual[k]:
1091 return "{}:{}".format(k, actual[k])
1092 elif not v(actual[k]):
1093
1094=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
1095--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-09-29 20:39:40 +0000
1096+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-11-25 16:59:28 +0000
1097@@ -1,3 +1,4 @@
1098+import six
1099 from charmhelpers.contrib.amulet.deployment import (
1100 AmuletDeployment
1101 )
1102@@ -69,7 +70,7 @@
1103
1104 def _configure_services(self, configs):
1105 """Configure all of the services."""
1106- for service, config in configs.iteritems():
1107+ for service, config in six.iteritems(configs):
1108 self.d.configure(service, config)
1109
1110 def _get_openstack_release(self):
1111
1112=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
1113--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-09-27 17:18:26 +0000
1114+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-11-25 16:59:28 +0000
1115@@ -7,6 +7,8 @@
1116 import keystoneclient.v2_0 as keystone_client
1117 import novaclient.v1_1.client as nova_client
1118
1119+import six
1120+
1121 from charmhelpers.contrib.amulet.utils import (
1122 AmuletUtils
1123 )
1124@@ -60,7 +62,7 @@
1125 expected service catalog endpoints.
1126 """
1127 self.log.debug('actual: {}'.format(repr(actual)))
1128- for k, v in expected.iteritems():
1129+ for k, v in six.iteritems(expected):
1130 if k in actual:
1131 ret = self._validate_dict_data(expected[k][0], actual[k][0])
1132 if ret:

Subscribers

People subscribed via source and target branches