Merge lp:~corey.bryant/charms/trusty/percona-cluster/render into lp:~openstack-charmers-archive/charms/trusty/percona-cluster/trunk

Proposed by Corey Bryant
Status: Merged
Merged at revision: 42
Proposed branch: lp:~corey.bryant/charms/trusty/percona-cluster/render
Merge into: lp:~openstack-charmers-archive/charms/trusty/percona-cluster/trunk
Diff against target: 995 lines (+246/-128)
15 files modified
hooks/charmhelpers/__init__.py (+22/-0)
hooks/charmhelpers/contrib/hahelpers/cluster.py (+16/-7)
hooks/charmhelpers/contrib/network/ip.py (+2/-2)
hooks/charmhelpers/contrib/peerstorage/__init__.py (+4/-3)
hooks/charmhelpers/core/fstab.py (+10/-8)
hooks/charmhelpers/core/hookenv.py (+36/-16)
hooks/charmhelpers/core/host.py (+52/-24)
hooks/charmhelpers/core/services/helpers.py (+9/-5)
hooks/charmhelpers/core/templating.py (+3/-2)
hooks/charmhelpers/fetch/__init__.py (+13/-11)
hooks/charmhelpers/fetch/archiveurl.py (+53/-16)
hooks/charmhelpers/fetch/bzrurl.py (+5/-1)
hooks/charmhelpers/fetch/giturl.py (+12/-5)
hooks/percona_hooks.py (+2/-4)
hooks/percona_utils.py (+7/-24)
To merge this branch: bzr merge lp:~corey.bryant/charms/trusty/percona-cluster/render
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
OpenStack Charmers Pending
Review via email: mp+245040@code.launchpad.net

Description of the change

This is just simplifying the code and using render() from charm-helpers instead of the roll-your-own render_template().

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

This merge also fixes a pre-existing unit test failure.

Revision history for this message
Liam Young (gnuoy) wrote :

Approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/charmhelpers/__init__.py'
--- hooks/charmhelpers/__init__.py 2013-09-03 16:52:02 +0000
+++ hooks/charmhelpers/__init__.py 2014-12-17 21:09:26 +0000
@@ -0,0 +1,22 @@
1# Bootstrap charm-helpers, installing its dependencies if necessary using
2# only standard libraries.
3import subprocess
4import sys
5
6try:
7 import six # flake8: noqa
8except ImportError:
9 if sys.version_info.major == 2:
10 subprocess.check_call(['apt-get', 'install', '-y', 'python-six'])
11 else:
12 subprocess.check_call(['apt-get', 'install', '-y', 'python3-six'])
13 import six # flake8: noqa
14
15try:
16 import yaml # flake8: noqa
17except ImportError:
18 if sys.version_info.major == 2:
19 subprocess.check_call(['apt-get', 'install', '-y', 'python-yaml'])
20 else:
21 subprocess.check_call(['apt-get', 'install', '-y', 'python3-yaml'])
22 import yaml # flake8: noqa
023
=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-10-22 10:34:07 +0000
+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-12-17 21:09:26 +0000
@@ -13,9 +13,10 @@
1313
14import subprocess14import subprocess
15import os15import os
16
17from socket import gethostname as get_unit_hostname16from socket import gethostname as get_unit_hostname
1817
18import six
19
19from charmhelpers.core.hookenv import (20from charmhelpers.core.hookenv import (
20 log,21 log,
21 relation_ids,22 relation_ids,
@@ -77,7 +78,7 @@
77 "show", resource78 "show", resource
78 ]79 ]
79 try:80 try:
80 status = subprocess.check_output(cmd)81 status = subprocess.check_output(cmd).decode('UTF-8')
81 except subprocess.CalledProcessError:82 except subprocess.CalledProcessError:
82 return False83 return False
83 else:84 else:
@@ -150,34 +151,42 @@
150 return False151 return False
151152
152153
153def determine_api_port(public_port):154def determine_api_port(public_port, singlenode_mode=False):
154 '''155 '''
155 Determine correct API server listening port based on156 Determine correct API server listening port based on
156 existence of HTTPS reverse proxy and/or haproxy.157 existence of HTTPS reverse proxy and/or haproxy.
157158
158 public_port: int: standard public port for given service159 public_port: int: standard public port for given service
159160
161 singlenode_mode: boolean: Shuffle ports when only a single unit is present
162
160 returns: int: the correct listening port for the API service163 returns: int: the correct listening port for the API service
161 '''164 '''
162 i = 0165 i = 0
163 if len(peer_units()) > 0 or is_clustered():166 if singlenode_mode:
167 i += 1
168 elif len(peer_units()) > 0 or is_clustered():
164 i += 1169 i += 1
165 if https():170 if https():
166 i += 1171 i += 1
167 return public_port - (i * 10)172 return public_port - (i * 10)
168173
169174
170def determine_apache_port(public_port):175def determine_apache_port(public_port, singlenode_mode=False):
171 '''176 '''
172 Description: Determine correct apache listening port based on public IP +177 Description: Determine correct apache listening port based on public IP +
173 state of the cluster.178 state of the cluster.
174179
175 public_port: int: standard public port for given service180 public_port: int: standard public port for given service
176181
182 singlenode_mode: boolean: Shuffle ports when only a single unit is present
183
177 returns: int: the correct listening port for the HAProxy service184 returns: int: the correct listening port for the HAProxy service
178 '''185 '''
179 i = 0186 i = 0
180 if len(peer_units()) > 0 or is_clustered():187 if singlenode_mode:
188 i += 1
189 elif len(peer_units()) > 0 or is_clustered():
181 i += 1190 i += 1
182 return public_port - (i * 10)191 return public_port - (i * 10)
183192
@@ -197,7 +206,7 @@
197 for setting in settings:206 for setting in settings:
198 conf[setting] = config_get(setting)207 conf[setting] = config_get(setting)
199 missing = []208 missing = []
200 [missing.append(s) for s, v in conf.iteritems() if v is None]209 [missing.append(s) for s, v in six.iteritems(conf) if v is None]
201 if missing:210 if missing:
202 log('Insufficient config data to configure hacluster.', level=ERROR)211 log('Insufficient config data to configure hacluster.', level=ERROR)
203 raise HAIncompleteConfig212 raise HAIncompleteConfig
204213
=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
--- hooks/charmhelpers/contrib/network/ip.py 2014-11-21 19:25:26 +0000
+++ hooks/charmhelpers/contrib/network/ip.py 2014-12-17 21:09:26 +0000
@@ -228,7 +228,7 @@
228 raise Exception("Interface '%s' doesn't have any %s addresses." %228 raise Exception("Interface '%s' doesn't have any %s addresses." %
229 (iface, inet_type))229 (iface, inet_type))
230230
231 return addresses231 return sorted(addresses)
232232
233233
234get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')234get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')
@@ -302,7 +302,7 @@
302 if global_addrs:302 if global_addrs:
303 # Make sure any found global addresses are not temporary303 # Make sure any found global addresses are not temporary
304 cmd = ['ip', 'addr', 'show', iface]304 cmd = ['ip', 'addr', 'show', iface]
305 out = subprocess.check_output(cmd)305 out = subprocess.check_output(cmd).decode('UTF-8')
306 if dynamic_only:306 if dynamic_only:
307 key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")307 key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
308 else:308 else:
309309
=== modified file 'hooks/charmhelpers/contrib/peerstorage/__init__.py'
--- hooks/charmhelpers/contrib/peerstorage/__init__.py 2014-10-22 10:34:07 +0000
+++ hooks/charmhelpers/contrib/peerstorage/__init__.py 2014-12-17 21:09:26 +0000
@@ -1,3 +1,4 @@
1import six
1from charmhelpers.core.hookenv import relation_id as current_relation_id2from charmhelpers.core.hookenv import relation_id as current_relation_id
2from charmhelpers.core.hookenv import (3from charmhelpers.core.hookenv import (
3 is_relation_made,4 is_relation_made,
@@ -93,7 +94,7 @@
93 if ex in echo_data:94 if ex in echo_data:
94 echo_data.pop(ex)95 echo_data.pop(ex)
95 else:96 else:
96 for attribute, value in rdata.iteritems():97 for attribute, value in six.iteritems(rdata):
97 for include in includes:98 for include in includes:
98 if include in attribute:99 if include in attribute:
99 echo_data[attribute] = value100 echo_data[attribute] = value
@@ -119,8 +120,8 @@
119 relation_settings=relation_settings,120 relation_settings=relation_settings,
120 **kwargs)121 **kwargs)
121 if is_relation_made(peer_relation_name):122 if is_relation_made(peer_relation_name):
122 for key, value in dict(kwargs.items() +123 for key, value in six.iteritems(dict(list(kwargs.items()) +
123 relation_settings.items()).iteritems():124 list(relation_settings.items()))):
124 key_prefix = relation_id or current_relation_id()125 key_prefix = relation_id or current_relation_id()
125 peer_store(key_prefix + delimiter + key,126 peer_store(key_prefix + delimiter + key,
126 value,127 value,
127128
=== modified file 'hooks/charmhelpers/core/fstab.py'
--- hooks/charmhelpers/core/fstab.py 2014-06-23 10:01:12 +0000
+++ hooks/charmhelpers/core/fstab.py 2014-12-17 21:09:26 +0000
@@ -3,10 +3,11 @@
33
4__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'4__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
55
6import io
6import os7import os
78
89
9class Fstab(file):10class Fstab(io.FileIO):
10 """This class extends file in order to implement a file reader/writer11 """This class extends file in order to implement a file reader/writer
11 for file `/etc/fstab`12 for file `/etc/fstab`
12 """13 """
@@ -24,8 +25,8 @@
24 options = "defaults"25 options = "defaults"
2526
26 self.options = options27 self.options = options
27 self.d = d28 self.d = int(d)
28 self.p = p29 self.p = int(p)
2930
30 def __eq__(self, o):31 def __eq__(self, o):
31 return str(self) == str(o)32 return str(self) == str(o)
@@ -45,7 +46,7 @@
45 self._path = path46 self._path = path
46 else:47 else:
47 self._path = self.DEFAULT_PATH48 self._path = self.DEFAULT_PATH
48 file.__init__(self, self._path, 'r+')49 super(Fstab, self).__init__(self._path, 'rb+')
4950
50 def _hydrate_entry(self, line):51 def _hydrate_entry(self, line):
51 # NOTE: use split with no arguments to split on any52 # NOTE: use split with no arguments to split on any
@@ -58,8 +59,9 @@
58 def entries(self):59 def entries(self):
59 self.seek(0)60 self.seek(0)
60 for line in self.readlines():61 for line in self.readlines():
62 line = line.decode('us-ascii')
61 try:63 try:
62 if not line.startswith("#"):64 if line.strip() and not line.startswith("#"):
63 yield self._hydrate_entry(line)65 yield self._hydrate_entry(line)
64 except ValueError:66 except ValueError:
65 pass67 pass
@@ -75,14 +77,14 @@
75 if self.get_entry_by_attr('device', entry.device):77 if self.get_entry_by_attr('device', entry.device):
76 return False78 return False
7779
78 self.write(str(entry) + '\n')80 self.write((str(entry) + '\n').encode('us-ascii'))
79 self.truncate()81 self.truncate()
80 return entry82 return entry
8183
82 def remove_entry(self, entry):84 def remove_entry(self, entry):
83 self.seek(0)85 self.seek(0)
8486
85 lines = self.readlines()87 lines = [l.decode('us-ascii') for l in self.readlines()]
8688
87 found = False89 found = False
88 for index, line in enumerate(lines):90 for index, line in enumerate(lines):
@@ -97,7 +99,7 @@
97 lines.remove(line)99 lines.remove(line)
98100
99 self.seek(0)101 self.seek(0)
100 self.write(''.join(lines))102 self.write(''.join(lines).encode('us-ascii'))
101 self.truncate()103 self.truncate()
102 return True104 return True
103105
104106
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2014-11-21 19:25:26 +0000
+++ hooks/charmhelpers/core/hookenv.py 2014-12-17 21:09:26 +0000
@@ -9,9 +9,14 @@
9import yaml9import yaml
10import subprocess10import subprocess
11import sys11import sys
12import UserDict
13from subprocess import CalledProcessError12from subprocess import CalledProcessError
1413
14import six
15if not six.PY3:
16 from UserDict import UserDict
17else:
18 from collections import UserDict
19
15CRITICAL = "CRITICAL"20CRITICAL = "CRITICAL"
16ERROR = "ERROR"21ERROR = "ERROR"
17WARNING = "WARNING"22WARNING = "WARNING"
@@ -63,16 +68,18 @@
63 command = ['juju-log']68 command = ['juju-log']
64 if level:69 if level:
65 command += ['-l', level]70 command += ['-l', level]
71 if not isinstance(message, six.string_types):
72 message = repr(message)
66 command += [message]73 command += [message]
67 subprocess.call(command)74 subprocess.call(command)
6875
6976
70class Serializable(UserDict.IterableUserDict):77class Serializable(UserDict):
71 """Wrapper, an object that can be serialized to yaml or json"""78 """Wrapper, an object that can be serialized to yaml or json"""
7279
73 def __init__(self, obj):80 def __init__(self, obj):
74 # wrap the object81 # wrap the object
75 UserDict.IterableUserDict.__init__(self)82 UserDict.__init__(self)
76 self.data = obj83 self.data = obj
7784
78 def __getattr__(self, attr):85 def __getattr__(self, attr):
@@ -218,7 +225,7 @@
218 prev_keys = []225 prev_keys = []
219 if self._prev_dict is not None:226 if self._prev_dict is not None:
220 prev_keys = self._prev_dict.keys()227 prev_keys = self._prev_dict.keys()
221 return list(set(prev_keys + dict.keys(self)))228 return list(set(prev_keys + list(dict.keys(self))))
222229
223 def load_previous(self, path=None):230 def load_previous(self, path=None):
224 """Load previous copy of config from disk.231 """Load previous copy of config from disk.
@@ -269,7 +276,7 @@
269276
270 """277 """
271 if self._prev_dict:278 if self._prev_dict:
272 for k, v in self._prev_dict.iteritems():279 for k, v in six.iteritems(self._prev_dict):
273 if k not in self:280 if k not in self:
274 self[k] = v281 self[k] = v
275 with open(self.path, 'w') as f:282 with open(self.path, 'w') as f:
@@ -284,7 +291,8 @@
284 config_cmd_line.append(scope)291 config_cmd_line.append(scope)
285 config_cmd_line.append('--format=json')292 config_cmd_line.append('--format=json')
286 try:293 try:
287 config_data = json.loads(subprocess.check_output(config_cmd_line))294 config_data = json.loads(
295 subprocess.check_output(config_cmd_line).decode('UTF-8'))
288 if scope is not None:296 if scope is not None:
289 return config_data297 return config_data
290 return Config(config_data)298 return Config(config_data)
@@ -303,10 +311,10 @@
303 if unit:311 if unit:
304 _args.append(unit)312 _args.append(unit)
305 try:313 try:
306 return json.loads(subprocess.check_output(_args))314 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
307 except ValueError:315 except ValueError:
308 return None316 return None
309 except CalledProcessError, e:317 except CalledProcessError as e:
310 if e.returncode == 2:318 if e.returncode == 2:
311 return None319 return None
312 raise320 raise
@@ -318,7 +326,7 @@
318 relation_cmd_line = ['relation-set']326 relation_cmd_line = ['relation-set']
319 if relation_id is not None:327 if relation_id is not None:
320 relation_cmd_line.extend(('-r', relation_id))328 relation_cmd_line.extend(('-r', relation_id))
321 for k, v in (relation_settings.items() + kwargs.items()):329 for k, v in (list(relation_settings.items()) + list(kwargs.items())):
322 if v is None:330 if v is None:
323 relation_cmd_line.append('{}='.format(k))331 relation_cmd_line.append('{}='.format(k))
324 else:332 else:
@@ -335,7 +343,8 @@
335 relid_cmd_line = ['relation-ids', '--format=json']343 relid_cmd_line = ['relation-ids', '--format=json']
336 if reltype is not None:344 if reltype is not None:
337 relid_cmd_line.append(reltype)345 relid_cmd_line.append(reltype)
338 return json.loads(subprocess.check_output(relid_cmd_line)) or []346 return json.loads(
347 subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
339 return []348 return []
340349
341350
@@ -346,7 +355,8 @@
346 units_cmd_line = ['relation-list', '--format=json']355 units_cmd_line = ['relation-list', '--format=json']
347 if relid is not None:356 if relid is not None:
348 units_cmd_line.extend(('-r', relid))357 units_cmd_line.extend(('-r', relid))
349 return json.loads(subprocess.check_output(units_cmd_line)) or []358 return json.loads(
359 subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
350360
351361
352@cached362@cached
@@ -386,21 +396,31 @@
386396
387397
388@cached398@cached
399def metadata():
400 """Get the current charm metadata.yaml contents as a python object"""
401 with open(os.path.join(charm_dir(), 'metadata.yaml')) as md:
402 return yaml.safe_load(md)
403
404
405@cached
389def relation_types():406def relation_types():
390 """Get a list of relation types supported by this charm"""407 """Get a list of relation types supported by this charm"""
391 charmdir = os.environ.get('CHARM_DIR', '')
392 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
393 md = yaml.safe_load(mdf)
394 rel_types = []408 rel_types = []
409 md = metadata()
395 for key in ('provides', 'requires', 'peers'):410 for key in ('provides', 'requires', 'peers'):
396 section = md.get(key)411 section = md.get(key)
397 if section:412 if section:
398 rel_types.extend(section.keys())413 rel_types.extend(section.keys())
399 mdf.close()
400 return rel_types414 return rel_types
401415
402416
403@cached417@cached
418def charm_name():
419 """Get the name of the current charm as is specified on metadata.yaml"""
420 return metadata().get('name')
421
422
423@cached
404def relations():424def relations():
405 """Get a nested dictionary of relation data for all related units"""425 """Get a nested dictionary of relation data for all related units"""
406 rels = {}426 rels = {}
@@ -455,7 +475,7 @@
455 """Get the unit ID for the remote unit"""475 """Get the unit ID for the remote unit"""
456 _args = ['unit-get', '--format=json', attribute]476 _args = ['unit-get', '--format=json', attribute]
457 try:477 try:
458 return json.loads(subprocess.check_output(_args))478 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
459 except ValueError:479 except ValueError:
460 return None480 return None
461481
462482
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2014-11-21 19:25:26 +0000
+++ hooks/charmhelpers/core/host.py 2014-12-17 21:09:26 +0000
@@ -14,11 +14,12 @@
14import subprocess14import subprocess
15import hashlib15import hashlib
16from contextlib import contextmanager16from contextlib import contextmanager
17
18from collections import OrderedDict17from collections import OrderedDict
1918
20from hookenv import log19import six
21from fstab import Fstab20
21from .hookenv import log
22from .fstab import Fstab
2223
2324
24def service_start(service_name):25def service_start(service_name):
@@ -54,7 +55,9 @@
54def service_running(service):55def service_running(service):
55 """Determine whether a system service is running"""56 """Determine whether a system service is running"""
56 try:57 try:
57 output = subprocess.check_output(['service', service, 'status'], stderr=subprocess.STDOUT)58 output = subprocess.check_output(
59 ['service', service, 'status'],
60 stderr=subprocess.STDOUT).decode('UTF-8')
58 except subprocess.CalledProcessError:61 except subprocess.CalledProcessError:
59 return False62 return False
60 else:63 else:
@@ -67,7 +70,9 @@
67def service_available(service_name):70def service_available(service_name):
68 """Determine whether a system service is available"""71 """Determine whether a system service is available"""
69 try:72 try:
70 subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)73 subprocess.check_output(
74 ['service', service_name, 'status'],
75 stderr=subprocess.STDOUT).decode('UTF-8')
71 except subprocess.CalledProcessError as e:76 except subprocess.CalledProcessError as e:
72 return 'unrecognized service' not in e.output77 return 'unrecognized service' not in e.output
73 else:78 else:
@@ -96,6 +101,26 @@
96 return user_info101 return user_info
97102
98103
104def add_group(group_name, system_group=False):
105 """Add a group to the system"""
106 try:
107 group_info = grp.getgrnam(group_name)
108 log('group {0} already exists!'.format(group_name))
109 except KeyError:
110 log('creating group {0}'.format(group_name))
111 cmd = ['addgroup']
112 if system_group:
113 cmd.append('--system')
114 else:
115 cmd.extend([
116 '--group',
117 ])
118 cmd.append(group_name)
119 subprocess.check_call(cmd)
120 group_info = grp.getgrnam(group_name)
121 return group_info
122
123
99def add_user_to_group(username, group):124def add_user_to_group(username, group):
100 """Add a user to a group"""125 """Add a user to a group"""
101 cmd = [126 cmd = [
@@ -115,7 +140,7 @@
115 cmd.append(from_path)140 cmd.append(from_path)
116 cmd.append(to_path)141 cmd.append(to_path)
117 log(" ".join(cmd))142 log(" ".join(cmd))
118 return subprocess.check_output(cmd).strip()143 return subprocess.check_output(cmd).decode('UTF-8').strip()
119144
120145
121def symlink(source, destination):146def symlink(source, destination):
@@ -130,23 +155,26 @@
130 subprocess.check_call(cmd)155 subprocess.check_call(cmd)
131156
132157
133def mkdir(path, owner='root', group='root', perms=0555, force=False):158def mkdir(path, owner='root', group='root', perms=0o555, force=False):
134 """Create a directory"""159 """Create a directory"""
135 log("Making dir {} {}:{} {:o}".format(path, owner, group,160 log("Making dir {} {}:{} {:o}".format(path, owner, group,
136 perms))161 perms))
137 uid = pwd.getpwnam(owner).pw_uid162 uid = pwd.getpwnam(owner).pw_uid
138 gid = grp.getgrnam(group).gr_gid163 gid = grp.getgrnam(group).gr_gid
139 realpath = os.path.abspath(path)164 realpath = os.path.abspath(path)
140 if os.path.exists(realpath):165 path_exists = os.path.exists(realpath)
141 if force and not os.path.isdir(realpath):166 if path_exists and force:
167 if not os.path.isdir(realpath):
142 log("Removing non-directory file {} prior to mkdir()".format(path))168 log("Removing non-directory file {} prior to mkdir()".format(path))
143 os.unlink(realpath)169 os.unlink(realpath)
144 else:170 os.makedirs(realpath, perms)
171 os.chown(realpath, uid, gid)
172 elif not path_exists:
145 os.makedirs(realpath, perms)173 os.makedirs(realpath, perms)
146 os.chown(realpath, uid, gid)174 os.chown(realpath, uid, gid)
147175
148176
149def write_file(path, content, owner='root', group='root', perms=0444):177def write_file(path, content, owner='root', group='root', perms=0o444):
150 """Create or overwrite a file with the contents of a string"""178 """Create or overwrite a file with the contents of a string"""
151 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))179 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
152 uid = pwd.getpwnam(owner).pw_uid180 uid = pwd.getpwnam(owner).pw_uid
@@ -177,7 +205,7 @@
177 cmd_args.extend([device, mountpoint])205 cmd_args.extend([device, mountpoint])
178 try:206 try:
179 subprocess.check_output(cmd_args)207 subprocess.check_output(cmd_args)
180 except subprocess.CalledProcessError, e:208 except subprocess.CalledProcessError as e:
181 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))209 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
182 return False210 return False
183211
@@ -191,7 +219,7 @@
191 cmd_args = ['umount', mountpoint]219 cmd_args = ['umount', mountpoint]
192 try:220 try:
193 subprocess.check_output(cmd_args)221 subprocess.check_output(cmd_args)
194 except subprocess.CalledProcessError, e:222 except subprocess.CalledProcessError as e:
195 log('Error unmounting {}\n{}'.format(mountpoint, e.output))223 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
196 return False224 return False
197225
@@ -218,8 +246,8 @@
218 """246 """
219 if os.path.exists(path):247 if os.path.exists(path):
220 h = getattr(hashlib, hash_type)()248 h = getattr(hashlib, hash_type)()
221 with open(path, 'r') as source:249 with open(path, 'rb') as source:
222 h.update(source.read()) # IGNORE:E1101 - it does have update250 h.update(source.read())
223 return h.hexdigest()251 return h.hexdigest()
224 else:252 else:
225 return None253 return None
@@ -297,7 +325,7 @@
297 if length is None:325 if length is None:
298 length = random.choice(range(35, 45))326 length = random.choice(range(35, 45))
299 alphanumeric_chars = [327 alphanumeric_chars = [
300 l for l in (string.letters + string.digits)328 l for l in (string.ascii_letters + string.digits)
301 if l not in 'l0QD1vAEIOUaeiou']329 if l not in 'l0QD1vAEIOUaeiou']
302 random_chars = [330 random_chars = [
303 random.choice(alphanumeric_chars) for _ in range(length)]331 random.choice(alphanumeric_chars) for _ in range(length)]
@@ -306,14 +334,14 @@
306334
307def list_nics(nic_type):335def list_nics(nic_type):
308 '''Return a list of nics of given type(s)'''336 '''Return a list of nics of given type(s)'''
309 if isinstance(nic_type, basestring):337 if isinstance(nic_type, six.string_types):
310 int_types = [nic_type]338 int_types = [nic_type]
311 else:339 else:
312 int_types = nic_type340 int_types = nic_type
313 interfaces = []341 interfaces = []
314 for int_type in int_types:342 for int_type in int_types:
315 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']343 cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
316 ip_output = subprocess.check_output(cmd).split('\n')344 ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
317 ip_output = (line for line in ip_output if line)345 ip_output = (line for line in ip_output if line)
318 for line in ip_output:346 for line in ip_output:
319 if line.split()[1].startswith(int_type):347 if line.split()[1].startswith(int_type):
@@ -335,7 +363,7 @@
335363
336def get_nic_mtu(nic):364def get_nic_mtu(nic):
337 cmd = ['ip', 'addr', 'show', nic]365 cmd = ['ip', 'addr', 'show', nic]
338 ip_output = subprocess.check_output(cmd).split('\n')366 ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
339 mtu = ""367 mtu = ""
340 for line in ip_output:368 for line in ip_output:
341 words = line.split()369 words = line.split()
@@ -346,7 +374,7 @@
346374
347def get_nic_hwaddr(nic):375def get_nic_hwaddr(nic):
348 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]376 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
349 ip_output = subprocess.check_output(cmd)377 ip_output = subprocess.check_output(cmd).decode('UTF-8')
350 hwaddr = ""378 hwaddr = ""
351 words = ip_output.split()379 words = ip_output.split()
352 if 'link/ether' in words:380 if 'link/ether' in words:
@@ -363,8 +391,8 @@
363391
364 '''392 '''
365 import apt_pkg393 import apt_pkg
366 from charmhelpers.fetch import apt_cache
367 if not pkgcache:394 if not pkgcache:
395 from charmhelpers.fetch import apt_cache
368 pkgcache = apt_cache()396 pkgcache = apt_cache()
369 pkg = pkgcache[package]397 pkg = pkgcache[package]
370 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)398 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
371399
=== modified file 'hooks/charmhelpers/core/services/helpers.py'
--- hooks/charmhelpers/core/services/helpers.py 2014-10-22 10:34:07 +0000
+++ hooks/charmhelpers/core/services/helpers.py 2014-12-17 21:09:26 +0000
@@ -196,7 +196,7 @@
196 if not os.path.isabs(file_name):196 if not os.path.isabs(file_name):
197 file_name = os.path.join(hookenv.charm_dir(), file_name)197 file_name = os.path.join(hookenv.charm_dir(), file_name)
198 with open(file_name, 'w') as file_stream:198 with open(file_name, 'w') as file_stream:
199 os.fchmod(file_stream.fileno(), 0600)199 os.fchmod(file_stream.fileno(), 0o600)
200 yaml.dump(config_data, file_stream)200 yaml.dump(config_data, file_stream)
201201
202 def read_context(self, file_name):202 def read_context(self, file_name):
@@ -211,15 +211,19 @@
211211
212class TemplateCallback(ManagerCallback):212class TemplateCallback(ManagerCallback):
213 """213 """
214 Callback class that will render a Jinja2 template, for use as a ready action.214 Callback class that will render a Jinja2 template, for use as a ready
215215 action.
216 :param str source: The template source file, relative to `$CHARM_DIR/templates`216
217 :param str source: The template source file, relative to
218 `$CHARM_DIR/templates`
219
217 :param str target: The target to write the rendered template to220 :param str target: The target to write the rendered template to
218 :param str owner: The owner of the rendered file221 :param str owner: The owner of the rendered file
219 :param str group: The group of the rendered file222 :param str group: The group of the rendered file
220 :param int perms: The permissions of the rendered file223 :param int perms: The permissions of the rendered file
221 """224 """
222 def __init__(self, source, target, owner='root', group='root', perms=0444):225 def __init__(self, source, target,
226 owner='root', group='root', perms=0o444):
223 self.source = source227 self.source = source
224 self.target = target228 self.target = target
225 self.owner = owner229 self.owner = owner
226230
=== modified file 'hooks/charmhelpers/core/templating.py'
--- hooks/charmhelpers/core/templating.py 2014-08-18 12:39:26 +0000
+++ hooks/charmhelpers/core/templating.py 2014-12-17 21:09:26 +0000
@@ -4,7 +4,8 @@
4from charmhelpers.core import hookenv4from charmhelpers.core import hookenv
55
66
7def render(source, target, context, owner='root', group='root', perms=0444, templates_dir=None):7def render(source, target, context, owner='root', group='root',
8 perms=0o444, templates_dir=None):
8 """9 """
9 Render a template.10 Render a template.
1011
@@ -47,5 +48,5 @@
47 level=hookenv.ERROR)48 level=hookenv.ERROR)
48 raise e49 raise e
49 content = template.render(context)50 content = template.render(context)
50 host.mkdir(os.path.dirname(target))51 host.mkdir(os.path.dirname(target), owner, group)
51 host.write_file(target, content, owner, group, perms)52 host.write_file(target, content, owner, group, perms)
5253
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2014-11-21 19:25:26 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2014-12-17 21:09:26 +0000
@@ -5,10 +5,6 @@
5from charmhelpers.core.host import (5from charmhelpers.core.host import (
6 lsb_release6 lsb_release
7)7)
8from urlparse import (
9 urlparse,
10 urlunparse,
11)
12import subprocess8import subprocess
13from charmhelpers.core.hookenv import (9from charmhelpers.core.hookenv import (
14 config,10 config,
@@ -16,6 +12,12 @@
16)12)
17import os13import os
1814
15import six
16if six.PY3:
17 from urllib.parse import urlparse, urlunparse
18else:
19 from urlparse import urlparse, urlunparse
20
1921
20CLOUD_ARCHIVE = """# Ubuntu Cloud Archive22CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
21deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main23deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
@@ -149,7 +151,7 @@
149 cmd = ['apt-get', '--assume-yes']151 cmd = ['apt-get', '--assume-yes']
150 cmd.extend(options)152 cmd.extend(options)
151 cmd.append('install')153 cmd.append('install')
152 if isinstance(packages, basestring):154 if isinstance(packages, six.string_types):
153 cmd.append(packages)155 cmd.append(packages)
154 else:156 else:
155 cmd.extend(packages)157 cmd.extend(packages)
@@ -182,7 +184,7 @@
182def apt_purge(packages, fatal=False):184def apt_purge(packages, fatal=False):
183 """Purge one or more packages"""185 """Purge one or more packages"""
184 cmd = ['apt-get', '--assume-yes', 'purge']186 cmd = ['apt-get', '--assume-yes', 'purge']
185 if isinstance(packages, basestring):187 if isinstance(packages, six.string_types):
186 cmd.append(packages)188 cmd.append(packages)
187 else:189 else:
188 cmd.extend(packages)190 cmd.extend(packages)
@@ -193,7 +195,7 @@
193def apt_hold(packages, fatal=False):195def apt_hold(packages, fatal=False):
194 """Hold one or more packages"""196 """Hold one or more packages"""
195 cmd = ['apt-mark', 'hold']197 cmd = ['apt-mark', 'hold']
196 if isinstance(packages, basestring):198 if isinstance(packages, six.string_types):
197 cmd.append(packages)199 cmd.append(packages)
198 else:200 else:
199 cmd.extend(packages)201 cmd.extend(packages)
@@ -260,7 +262,7 @@
260262
261 if key:263 if key:
262 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:264 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
263 with NamedTemporaryFile() as key_file:265 with NamedTemporaryFile('w+') as key_file:
264 key_file.write(key)266 key_file.write(key)
265 key_file.flush()267 key_file.flush()
266 key_file.seek(0)268 key_file.seek(0)
@@ -297,14 +299,14 @@
297 sources = safe_load((config(sources_var) or '').strip()) or []299 sources = safe_load((config(sources_var) or '').strip()) or []
298 keys = safe_load((config(keys_var) or '').strip()) or None300 keys = safe_load((config(keys_var) or '').strip()) or None
299301
300 if isinstance(sources, basestring):302 if isinstance(sources, six.string_types):
301 sources = [sources]303 sources = [sources]
302304
303 if keys is None:305 if keys is None:
304 for source in sources:306 for source in sources:
305 add_source(source, None)307 add_source(source, None)
306 else:308 else:
307 if isinstance(keys, basestring):309 if isinstance(keys, six.string_types):
308 keys = [keys]310 keys = [keys]
309311
310 if len(sources) != len(keys):312 if len(sources) != len(keys):
@@ -401,7 +403,7 @@
401 while result is None or result == APT_NO_LOCK:403 while result is None or result == APT_NO_LOCK:
402 try:404 try:
403 result = subprocess.check_call(cmd, env=env)405 result = subprocess.check_call(cmd, env=env)
404 except subprocess.CalledProcessError, e:406 except subprocess.CalledProcessError as e:
405 retry_count = retry_count + 1407 retry_count = retry_count + 1
406 if retry_count > APT_NO_LOCK_RETRY_COUNT:408 if retry_count > APT_NO_LOCK_RETRY_COUNT:
407 raise409 raise
408410
=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
--- hooks/charmhelpers/fetch/archiveurl.py 2014-10-22 10:34:07 +0000
+++ hooks/charmhelpers/fetch/archiveurl.py 2014-12-17 21:09:26 +0000
@@ -1,8 +1,23 @@
1import os1import os
2import urllib2
3from urllib import urlretrieve
4import urlparse
5import hashlib2import hashlib
3import re
4
5import six
6if six.PY3:
7 from urllib.request import (
8 build_opener, install_opener, urlopen, urlretrieve,
9 HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
10 )
11 from urllib.parse import urlparse, urlunparse, parse_qs
12 from urllib.error import URLError
13else:
14 from urllib import urlretrieve
15 from urllib2 import (
16 build_opener, install_opener, urlopen,
17 HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
18 URLError
19 )
20 from urlparse import urlparse, urlunparse, parse_qs
621
7from charmhelpers.fetch import (22from charmhelpers.fetch import (
8 BaseFetchHandler,23 BaseFetchHandler,
@@ -15,6 +30,24 @@
15from charmhelpers.core.host import mkdir, check_hash30from charmhelpers.core.host import mkdir, check_hash
1631
1732
33def splituser(host):
34 '''urllib.splituser(), but six's support of this seems broken'''
35 _userprog = re.compile('^(.*)@(.*)$')
36 match = _userprog.match(host)
37 if match:
38 return match.group(1, 2)
39 return None, host
40
41
42def splitpasswd(user):
43 '''urllib.splitpasswd(), but six's support of this is missing'''
44 _passwdprog = re.compile('^([^:]*):(.*)$', re.S)
45 match = _passwdprog.match(user)
46 if match:
47 return match.group(1, 2)
48 return user, None
49
50
18class ArchiveUrlFetchHandler(BaseFetchHandler):51class ArchiveUrlFetchHandler(BaseFetchHandler):
19 """52 """
20 Handler to download archive files from arbitrary URLs.53 Handler to download archive files from arbitrary URLs.
@@ -42,20 +75,20 @@
42 """75 """
43 # propogate all exceptions76 # propogate all exceptions
44 # URLError, OSError, etc77 # URLError, OSError, etc
45 proto, netloc, path, params, query, fragment = urlparse.urlparse(source)78 proto, netloc, path, params, query, fragment = urlparse(source)
46 if proto in ('http', 'https'):79 if proto in ('http', 'https'):
47 auth, barehost = urllib2.splituser(netloc)80 auth, barehost = splituser(netloc)
48 if auth is not None:81 if auth is not None:
49 source = urlparse.urlunparse((proto, barehost, path, params, query, fragment))82 source = urlunparse((proto, barehost, path, params, query, fragment))
50 username, password = urllib2.splitpasswd(auth)83 username, password = splitpasswd(auth)
51 passman = urllib2.HTTPPasswordMgrWithDefaultRealm()84 passman = HTTPPasswordMgrWithDefaultRealm()
52 # Realm is set to None in add_password to force the username and password85 # Realm is set to None in add_password to force the username and password
53 # to be used whatever the realm86 # to be used whatever the realm
54 passman.add_password(None, source, username, password)87 passman.add_password(None, source, username, password)
55 authhandler = urllib2.HTTPBasicAuthHandler(passman)88 authhandler = HTTPBasicAuthHandler(passman)
56 opener = urllib2.build_opener(authhandler)89 opener = build_opener(authhandler)
57 urllib2.install_opener(opener)90 install_opener(opener)
58 response = urllib2.urlopen(source)91 response = urlopen(source)
59 try:92 try:
60 with open(dest, 'w') as dest_file:93 with open(dest, 'w') as dest_file:
61 dest_file.write(response.read())94 dest_file.write(response.read())
@@ -91,17 +124,21 @@
91 url_parts = self.parse_url(source)124 url_parts = self.parse_url(source)
92 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')125 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
93 if not os.path.exists(dest_dir):126 if not os.path.exists(dest_dir):
94 mkdir(dest_dir, perms=0755)127 mkdir(dest_dir, perms=0o755)
95 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))128 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
96 try:129 try:
97 self.download(source, dld_file)130 self.download(source, dld_file)
98 except urllib2.URLError as e:131 except URLError as e:
99 raise UnhandledSource(e.reason)132 raise UnhandledSource(e.reason)
100 except OSError as e:133 except OSError as e:
101 raise UnhandledSource(e.strerror)134 raise UnhandledSource(e.strerror)
102 options = urlparse.parse_qs(url_parts.fragment)135 options = parse_qs(url_parts.fragment)
103 for key, value in options.items():136 for key, value in options.items():
104 if key in hashlib.algorithms:137 if not six.PY3:
138 algorithms = hashlib.algorithms
139 else:
140 algorithms = hashlib.algorithms_available
141 if key in algorithms:
105 check_hash(dld_file, value, key)142 check_hash(dld_file, value, key)
106 if checksum:143 if checksum:
107 check_hash(dld_file, checksum, hash_type)144 check_hash(dld_file, checksum, hash_type)
108145
=== modified file 'hooks/charmhelpers/fetch/bzrurl.py'
--- hooks/charmhelpers/fetch/bzrurl.py 2014-06-23 09:47:35 +0000
+++ hooks/charmhelpers/fetch/bzrurl.py 2014-12-17 21:09:26 +0000
@@ -5,6 +5,10 @@
5)5)
6from charmhelpers.core.host import mkdir6from charmhelpers.core.host import mkdir
77
8import six
9if six.PY3:
10 raise ImportError('bzrlib does not support Python3')
11
8try:12try:
9 from bzrlib.branch import Branch13 from bzrlib.branch import Branch
10except ImportError:14except ImportError:
@@ -42,7 +46,7 @@
42 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",46 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
43 branch_name)47 branch_name)
44 if not os.path.exists(dest_dir):48 if not os.path.exists(dest_dir):
45 mkdir(dest_dir, perms=0755)49 mkdir(dest_dir, perms=0o755)
46 try:50 try:
47 self.branch(source, dest_dir)51 self.branch(source, dest_dir)
48 except OSError as e:52 except OSError as e:
4953
=== modified file 'hooks/charmhelpers/fetch/giturl.py'
--- hooks/charmhelpers/fetch/giturl.py 2014-11-21 19:25:26 +0000
+++ hooks/charmhelpers/fetch/giturl.py 2014-12-17 21:09:26 +0000
@@ -5,6 +5,10 @@
5)5)
6from charmhelpers.core.host import mkdir6from charmhelpers.core.host import mkdir
77
8import six
9if six.PY3:
10 raise ImportError('GitPython does not support Python 3')
11
8try:12try:
9 from git import Repo13 from git import Repo
10except ImportError:14except ImportError:
@@ -17,7 +21,7 @@
17 """Handler for git branches via generic and github URLs"""21 """Handler for git branches via generic and github URLs"""
18 def can_handle(self, source):22 def can_handle(self, source):
19 url_parts = self.parse_url(source)23 url_parts = self.parse_url(source)
20 #TODO (mattyw) no support for ssh git@ yet24 # TODO (mattyw) no support for ssh git@ yet
21 if url_parts.scheme not in ('http', 'https', 'git'):25 if url_parts.scheme not in ('http', 'https', 'git'):
22 return False26 return False
23 else:27 else:
@@ -30,13 +34,16 @@
30 repo = Repo.clone_from(source, dest)34 repo = Repo.clone_from(source, dest)
31 repo.git.checkout(branch)35 repo.git.checkout(branch)
3236
33 def install(self, source, branch="master"):37 def install(self, source, branch="master", dest=None):
34 url_parts = self.parse_url(source)38 url_parts = self.parse_url(source)
35 branch_name = url_parts.path.strip("/").split("/")[-1]39 branch_name = url_parts.path.strip("/").split("/")[-1]
36 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",40 if dest:
37 branch_name)41 dest_dir = os.path.join(dest, branch_name)
42 else:
43 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
44 branch_name)
38 if not os.path.exists(dest_dir):45 if not os.path.exists(dest_dir):
39 mkdir(dest_dir, perms=0755)46 mkdir(dest_dir, perms=0o755)
40 try:47 try:
41 self.clone(source, dest_dir, branch)48 self.clone(source, dest_dir, branch)
42 except OSError as e:49 except OSError as e:
4350
=== modified file 'hooks/percona_hooks.py'
--- hooks/percona_hooks.py 2014-11-25 14:59:34 +0000
+++ hooks/percona_hooks.py 2014-12-17 21:09:26 +0000
@@ -26,6 +26,7 @@
26 write_file,26 write_file,
27 lsb_release,27 lsb_release,
28)28)
29from charmhelpers.core.templating import render
29from charmhelpers.fetch import (30from charmhelpers.fetch import (
30 apt_update,31 apt_update,
31 apt_install,32 apt_install,
@@ -40,7 +41,6 @@
40 PACKAGES,41 PACKAGES,
41 MY_CNF,42 MY_CNF,
42 setup_percona_repo,43 setup_percona_repo,
43 render_template,
44 get_host_ip,44 get_host_ip,
45 get_cluster_hosts,45 get_cluster_hosts,
46 configure_sstuser,46 configure_sstuser,
@@ -123,9 +123,7 @@
123 context['ipv6'] = False123 context['ipv6'] = False
124124
125 context.update(parse_config())125 context.update(parse_config())
126 write_file(path=MY_CNF,126 render(os.path.basename(MY_CNF), MY_CNF, context, perms=0o444)
127 content=render_template(os.path.basename(MY_CNF), context),
128 perms=0o444)
129127
130128
131@hooks.hook('upgrade-charm')129@hooks.hook('upgrade-charm')
132130
=== modified file 'hooks/percona_utils.py'
--- hooks/percona_utils.py 2014-11-21 19:25:26 +0000
+++ hooks/percona_utils.py 2014-12-17 21:09:26 +0000
@@ -28,20 +28,6 @@
28from mysql import get_mysql_root_password, MySQLHelper28from mysql import get_mysql_root_password, MySQLHelper
2929
3030
31try:
32 import jinja2
33except ImportError:
34 apt_install(filter_installed_packages(['python-jinja2']),
35 fatal=True)
36 import jinja2
37
38try:
39 import dns.resolver
40except ImportError:
41 apt_install(filter_installed_packages(['python-dnspython']),
42 fatal=True)
43 import dns.resolver
44
45PACKAGES = [31PACKAGES = [
46 'percona-xtradb-cluster-server-5.5',32 'percona-xtradb-cluster-server-5.5',
47 'percona-xtradb-cluster-client-5.5',33 'percona-xtradb-cluster-client-5.5',
@@ -72,18 +58,15 @@
72 sources.write(REPO.format(release=lsb_release()['DISTRIB_CODENAME']))58 sources.write(REPO.format(release=lsb_release()['DISTRIB_CODENAME']))
73 subprocess.check_call(['apt-key', 'add', KEY])59 subprocess.check_call(['apt-key', 'add', KEY])
7460
75TEMPLATES_DIR = 'templates'
76FILES_DIR = 'files'
77
78
79def render_template(template_name, context, template_dir=TEMPLATES_DIR):
80 templates = jinja2.Environment(
81 loader=jinja2.FileSystemLoader(template_dir))
82 template = templates.get_template(template_name)
83 return template.render(context)
84
8561
86def get_host_ip(hostname=None):62def get_host_ip(hostname=None):
63 try:
64 import dns.resolver
65 except ImportError:
66 apt_install(filter_installed_packages(['python-dnspython']),
67 fatal=True)
68 import dns.resolver
69
87 if config('prefer-ipv6'):70 if config('prefer-ipv6'):
88 # Ensure we have a valid ipv6 address configured71 # Ensure we have a valid ipv6 address configured
89 get_ipv6_addr(exc_list=[config('vip')], fatal=True)[0]72 get_ipv6_addr(exc_list=[config('vip')], fatal=True)[0]

Subscribers

People subscribed via source and target branches

to status/vote changes: