Merge lp:~gnuoy/charms/trusty/percona-cluster/1454317 into lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next

Proposed by Liam Young on 2015-05-13
Status: Work in progress
Proposed branch: lp:~gnuoy/charms/trusty/percona-cluster/1454317
Merge into: lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next
Diff against target: 501 lines (+210/-40)
11 files modified
charm-helpers-hooks.yaml (+1/-1)
charmhelpers/contrib/database/mysql.py (+21/-6)
charmhelpers/contrib/network/ip.py (+5/-3)
charmhelpers/core/hookenv.py (+32/-0)
charmhelpers/core/hugepage.py (+8/-1)
charmhelpers/core/strutils.py (+30/-0)
hooks/percona_hooks.py (+21/-3)
hooks/percona_utils.py (+12/-9)
tests/charmhelpers/contrib/amulet/utils.py (+47/-16)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+1/-1)
tests/charmhelpers/core/hookenv.py (+32/-0)
To merge this branch: bzr merge lp:~gnuoy/charms/trusty/percona-cluster/1454317
Reviewer Review Type Date Requested Status
Edward Hope-Morley 2015-05-13 Resubmit on 2015-09-16
Mario Splivalo (community) Approve on 2015-05-15
OpenStack Charmers 2015-09-16 Pending
Review via email: mp+258981@code.launchpad.net
To post a comment you must log in.

charm_unit_test #4246 percona-cluster-next for gnuoy mp258981
    UNIT FAIL: unit-test failed

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

Full unit test output: http://paste.ubuntu.com/11115163/
Build: http://10.245.162.77:8080/job/charm_unit_test/4246/

charm_lint_check #4521 percona-cluster-next for gnuoy mp258981
    LINT FAIL: lint-test failed

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

Full lint test output: http://paste.ubuntu.com/11115164/
Build: http://10.245.162.77:8080/job/charm_lint_check/4521/

charm_amulet_test #4100 percona-cluster-next for gnuoy mp258981
    AMULET FAIL: amulet-test missing

AMULET Results (max last 2 lines):
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.

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

Mario Splivalo (mariosplivalo) wrote :

This looks ok to me. Verified that passwords are stored correctly in relations, adding more units doesn't break things.

The issues are amulet tests, which were included with this commit: http://bazaar.launchpad.net/~openstack-charmers/charms/trusty/percona-cluster/next/revision/54

Apparently it's an amulet issue - test raises amulet.SKIP if no AMULET_OS_VIP is configured, but amulet considers that as an error and fails the test. Same issue as with rev54 commit, probably the test should be written in a manner so that it doesn't raise. amulet.SKIP.

Otherwise, peachy!

review: Approve
Edward Hope-Morley (hopem) wrote :

Liam, i don't see these changes in lp:charm-helpers (e.g. wipe_disk_passwords() is not in contrib.database.mysql).

review: Needs Fixing
Edward Hope-Morley (hopem) wrote :

Ok found it (linked charm-helpers MP to LP)

Edward Hope-Morley (hopem) wrote :

This could do with a cleanup+resync.

review: Resubmit
63. By Liam Young on 2015-09-30

Merged next in

64. By Liam Young on 2015-09-30

Rebase changes

65. By Liam Young on 2015-09-30

Fix typos and lint

Unmerged revisions

65. By Liam Young on 2015-09-30

Fix typos and lint

64. By Liam Young on 2015-09-30

Rebase changes

63. By Liam Young on 2015-09-30

Merged next in

62. By Liam Young on 2015-05-13

Only add passwords to the peer relation if the unit is clustered, the leader and remove stale passwords after a slave sync. Also replace eligible_leader with is_elected_leader as eligible_leader is deprecated

61. By Liam Young on 2015-05-13

Sync charmhelpers with db fixes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm-helpers-hooks.yaml'
2--- charm-helpers-hooks.yaml 2015-08-26 13:22:41 +0000
3+++ charm-helpers-hooks.yaml 2015-09-30 08:52:41 +0000
4@@ -1,4 +1,4 @@
5-branch: lp:charm-helpers
6+branch: lp:~gnuoy/charm-helpers/no-create-if-none
7 destination: hooks/charmhelpers
8 include:
9 - core
10
11=== modified file 'charmhelpers/contrib/database/mysql.py'
12--- charmhelpers/contrib/database/mysql.py 2015-08-03 14:52:57 +0000
13+++ charmhelpers/contrib/database/mysql.py 2015-09-30 08:52:41 +0000
14@@ -134,6 +134,14 @@
15 finally:
16 cursor.close()
17
18+ def wipe_disk_passwords(self):
19+ """Remove all passwords stored on disk"""
20+ dirname = os.path.dirname(self.root_passwd_file_template)
21+ path = os.path.join(dirname, '*.passwd')
22+ for f in glob.glob(path):
23+ log("Removing password file {}".format(f), level=DEBUG)
24+ os.unlink(f)
25+
26 def migrate_passwords_to_peer_relation(self, excludes=None):
27 """Migrate any passwords storage on disk to cluster peer relation."""
28 dirname = os.path.dirname(self.root_passwd_file_template)
29@@ -156,7 +164,7 @@
30 # NOTE cluster relation not yet ready - skip for now
31 pass
32
33- def get_mysql_password_on_disk(self, username=None, password=None):
34+ def get_mysql_password_on_disk(self, username=None, password=None, create_if_none=True):
35 """Retrieve, generate or store a mysql password for the provided
36 username on disk."""
37 if username:
38@@ -170,7 +178,7 @@
39 log("Using existing password file '%s'" % passwd_file, level=DEBUG)
40 with open(passwd_file, 'r') as passwd:
41 _password = passwd.read().strip()
42- else:
43+ elif create_if_none:
44 log("Generating new password file '%s'" % passwd_file, level=DEBUG)
45 if not os.path.isdir(os.path.dirname(passwd_file)):
46 # NOTE: need to ensure this is not mysql root dir (which needs
47@@ -207,7 +215,8 @@
48 for key in keys:
49 yield key
50
51- def get_mysql_password(self, username=None, password=None):
52+ def get_mysql_password(self, username=None, password=None,
53+ create_if_none=True):
54 """Retrieve, generate or store a mysql password for the provided
55 username using peer relation cluster."""
56 excludes = []
57@@ -227,12 +236,18 @@
58 # cluster relation is not yet started; use on-disk
59 _password = None
60
61- # If none available, generate new one
62+ # If none available, check if one is on disk, if not
63+ # get_mysql_password_on_disk will generate a new one if create_if_none
64+ # is true
65 if not _password:
66- _password = self.get_mysql_password_on_disk(username, password)
67+ _password = self.get_mysql_password_on_disk(
68+ username,
69+ password,
70+ create_if_none=create_if_none,
71+ )
72
73 # Put on wire if required
74- if self.migrate_passwd_to_peer_relation:
75+ if _password and self.migrate_passwd_to_peer_relation:
76 self.migrate_passwords_to_peer_relation(excludes=excludes)
77
78 return _password
79
80=== modified file 'charmhelpers/contrib/network/ip.py'
81--- charmhelpers/contrib/network/ip.py 2015-09-02 09:32:16 +0000
82+++ charmhelpers/contrib/network/ip.py 2015-09-30 08:52:41 +0000
83@@ -23,7 +23,7 @@
84 from functools import partial
85
86 from charmhelpers.core.hookenv import unit_get
87-from charmhelpers.fetch import apt_install
88+from charmhelpers.fetch import apt_install, apt_update
89 from charmhelpers.core.hookenv import (
90 log,
91 WARNING,
92@@ -32,13 +32,15 @@
93 try:
94 import netifaces
95 except ImportError:
96- apt_install('python-netifaces')
97+ apt_update(fatal=True)
98+ apt_install('python-netifaces', fatal=True)
99 import netifaces
100
101 try:
102 import netaddr
103 except ImportError:
104- apt_install('python-netaddr')
105+ apt_update(fatal=True)
106+ apt_install('python-netaddr', fatal=True)
107 import netaddr
108
109
110
111=== modified file 'charmhelpers/core/hookenv.py'
112--- charmhelpers/core/hookenv.py 2015-09-02 09:32:16 +0000
113+++ charmhelpers/core/hookenv.py 2015-09-30 08:52:41 +0000
114@@ -623,6 +623,38 @@
115 return unit_get('private-address')
116
117
118+@cached
119+def storage_get(attribute="", storage_id=""):
120+ """Get storage attributes"""
121+ _args = ['storage-get', '--format=json']
122+ if storage_id:
123+ _args.extend(('-s', storage_id))
124+ if attribute:
125+ _args.append(attribute)
126+ try:
127+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
128+ except ValueError:
129+ return None
130+
131+
132+@cached
133+def storage_list(storage_name=""):
134+ """List the storage IDs for the unit"""
135+ _args = ['storage-list', '--format=json']
136+ if storage_name:
137+ _args.append(storage_name)
138+ try:
139+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
140+ except ValueError:
141+ return None
142+ except OSError as e:
143+ import errno
144+ if e.errno == errno.ENOENT:
145+ # storage-list does not exist
146+ return []
147+ raise
148+
149+
150 class UnregisteredHookError(Exception):
151 """Raised when an undefined hook is called"""
152 pass
153
154=== modified file 'charmhelpers/core/hugepage.py'
155--- charmhelpers/core/hugepage.py 2015-08-26 13:11:30 +0000
156+++ charmhelpers/core/hugepage.py 2015-09-30 08:52:41 +0000
157@@ -25,11 +25,13 @@
158 fstab_mount,
159 mkdir,
160 )
161+from charmhelpers.core.strutils import bytes_from_string
162+from subprocess import check_output
163
164
165 def hugepage_support(user, group='hugetlb', nr_hugepages=256,
166 max_map_count=65536, mnt_point='/run/hugepages/kvm',
167- pagesize='2MB', mount=True):
168+ pagesize='2MB', mount=True, set_shmmax=False):
169 """Enable hugepages on system.
170
171 Args:
172@@ -49,6 +51,11 @@
173 'vm.max_map_count': max_map_count,
174 'vm.hugetlb_shm_group': gid,
175 }
176+ if set_shmmax:
177+ shmmax_current = int(check_output(['sysctl', '-n', 'kernel.shmmax']))
178+ shmmax_minsize = bytes_from_string(pagesize) * nr_hugepages
179+ if shmmax_minsize > shmmax_current:
180+ sysctl_settings['kernel.shmmax'] = shmmax_minsize
181 sysctl.create(yaml.dump(sysctl_settings), '/etc/sysctl.d/10-hugepage.conf')
182 mkdir(mnt_point, owner='root', group='root', perms=0o755, force=False)
183 lfstab = fstab.Fstab()
184
185=== modified file 'charmhelpers/core/strutils.py'
186--- charmhelpers/core/strutils.py 2015-05-06 13:20:02 +0000
187+++ charmhelpers/core/strutils.py 2015-09-30 08:52:41 +0000
188@@ -18,6 +18,7 @@
189 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
190
191 import six
192+import re
193
194
195 def bool_from_string(value):
196@@ -40,3 +41,32 @@
197
198 msg = "Unable to interpret string value '%s' as boolean" % (value)
199 raise ValueError(msg)
200+
201+
202+def bytes_from_string(value):
203+ """Interpret human readable string value as bytes.
204+
205+ Returns int
206+ """
207+ BYTE_POWER = {
208+ 'K': 1,
209+ 'KB': 1,
210+ 'M': 2,
211+ 'MB': 2,
212+ 'G': 3,
213+ 'GB': 3,
214+ 'T': 4,
215+ 'TB': 4,
216+ 'P': 5,
217+ 'PB': 5,
218+ }
219+ if isinstance(value, six.string_types):
220+ value = six.text_type(value)
221+ else:
222+ msg = "Unable to interpret non-string value '%s' as boolean" % (value)
223+ raise ValueError(msg)
224+ matches = re.match("([0-9]+)([a-zA-Z]+)", value)
225+ if not matches:
226+ msg = "Unable to interpret string value '%s' as bytes" % (value)
227+ raise ValueError(msg)
228+ return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
229
230=== modified file 'hooks/percona_hooks.py'
231--- hooks/percona_hooks.py 2015-09-23 18:29:21 +0000
232+++ hooks/percona_hooks.py 2015-09-30 08:52:41 +0000
233@@ -108,20 +108,28 @@
234 render_config()
235 apt_update(fatal=True)
236 apt_install(determine_packages(), fatal=True)
237- configure_sstuser(config('sst-password'))
238+ configure_sstuser(
239+ config('sst-password'),
240+ migrate_passwd_to_peer_relation=False
241+ )
242
243
244 def render_config(clustered=False, hosts=[]):
245 if not os.path.exists(os.path.dirname(MY_CNF)):
246 os.makedirs(os.path.dirname(MY_CNF))
247
248+ db_helper = get_db_helper(migrate_passwd_to_peer_relation=False)
249+ cfg_passwd = config('sst-password')
250+ sst_password = db_helper.get_mysql_password(username='sstuser',
251+ password=cfg_passwd)
252+
253 context = {
254 'cluster_name': 'juju_cluster',
255 'private_address': get_host_ip(),
256 'clustered': clustered,
257 'cluster_hosts': ",".join(hosts),
258 'sst_method': 'xtrabackup',
259- 'sst_password': config('sst-password'),
260+ 'sst_password': sst_password,
261 'innodb_file_per_table': config('innodb-file-per-table'),
262 'table_open_cache': config('table-open-cache'),
263 'lp1366997_workaround': config('lp1366997-workaround')
264@@ -251,6 +259,9 @@
265 log("Cluster is bootstrapped - configuring mysql on this node",
266 DEBUG)
267 render_config_restart_on_changed(clustered, hosts)
268+ # Bootstrapping from existing cluster invalidates passwords
269+ db_helper = get_db_helper()
270+ db_helper.wipe_disk_passwords()
271 else:
272 log("Not configuring", DEBUG)
273
274@@ -557,7 +568,14 @@
275 resources = {'res_mysql_vip': res_mysql_vip,
276 'res_mysql_monitor': 'ocf:percona:mysql_monitor'}
277
278- sstpsswd = config('sst-password')
279+ if is_elected_leader(DC_RESOURCE_NAME):
280+ migrate_passwd = True
281+ else:
282+ migrate_passwd = False
283+ db_helper = get_db_helper(migrate_passwd_to_peer_relation=migrate_passwd)
284+ cfg_passwd = config('sst-password')
285+ sstpsswd = db_helper.get_mysql_password(username='sstuser',
286+ password=cfg_passwd)
287 resource_params = {'res_mysql_vip': vip_params,
288 'res_mysql_monitor':
289 RES_MONITOR_PARAMS % {'sstpass': sstpsswd}}
290
291=== modified file 'hooks/percona_utils.py'
292--- hooks/percona_utils.py 2015-07-30 09:59:33 +0000
293+++ hooks/percona_utils.py 2015-09-30 08:52:41 +0000
294@@ -179,15 +179,18 @@
295 "BY '{}'")
296
297
298-def get_db_helper():
299- return MySQLHelper(rpasswdf_template='/var/lib/charm/%s/mysql.passwd' %
300- (service_name()),
301- upasswdf_template='/var/lib/charm/%s/mysql-{}.passwd' %
302- (service_name()))
303-
304-
305-def configure_sstuser(sst_password):
306- m_helper = get_db_helper()
307+def get_db_helper(migrate_passwd_to_peer_relation=True):
308+ return MySQLHelper(
309+ rpasswdf_template='/var/lib/charm/%s/mysql.passwd' %
310+ (service_name()),
311+ upasswdf_template='/var/lib/charm/%s/mysql-{}.passwd' %
312+ (service_name()),
313+ migrate_passwd_to_peer_relation=migrate_passwd_to_peer_relation)
314+
315+
316+def configure_sstuser(sst_password, migrate_passwd_to_peer_relation=True):
317+ m_helper = get_db_helper(
318+ migrate_passwd_to_peer_relation=migrate_passwd_to_peer_relation)
319 m_helper.connect(password=m_helper.get_mysql_root_password())
320 m_helper.execute(SQL_SST_USER_SETUP.format(sst_password))
321 m_helper.execute(SQL_SST_USER_SETUP_IPV6.format(sst_password))
322
323=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
324--- tests/charmhelpers/contrib/amulet/utils.py 2015-09-15 09:06:11 +0000
325+++ tests/charmhelpers/contrib/amulet/utils.py 2015-09-30 08:52:41 +0000
326@@ -326,7 +326,7 @@
327
328 def service_restarted_since(self, sentry_unit, mtime, service,
329 pgrep_full=None, sleep_time=20,
330- retry_count=2, retry_sleep_time=30):
331+ retry_count=30, retry_sleep_time=10):
332 """Check if service was been started after a given time.
333
334 Args:
335@@ -334,8 +334,9 @@
336 mtime (float): The epoch time to check against
337 service (string): service name to look for in process table
338 pgrep_full: [Deprecated] Use full command line search mode with pgrep
339- sleep_time (int): Seconds to sleep before looking for process
340- retry_count (int): If service is not found, how many times to retry
341+ sleep_time (int): Initial sleep time (s) before looking for file
342+ retry_sleep_time (int): Time (s) to sleep between retries
343+ retry_count (int): If file is not found, how many times to retry
344
345 Returns:
346 bool: True if service found and its start time it newer than mtime,
347@@ -359,11 +360,12 @@
348 pgrep_full)
349 self.log.debug('Attempt {} to get {} proc start time on {} '
350 'OK'.format(tries, service, unit_name))
351- except IOError:
352+ except IOError as e:
353 # NOTE(beisner) - race avoidance, proc may not exist yet.
354 # https://bugs.launchpad.net/charm-helpers/+bug/1474030
355 self.log.debug('Attempt {} to get {} proc start time on {} '
356- 'failed'.format(tries, service, unit_name))
357+ 'failed\n{}'.format(tries, service,
358+ unit_name, e))
359 time.sleep(retry_sleep_time)
360 tries += 1
361
362@@ -383,35 +385,62 @@
363 return False
364
365 def config_updated_since(self, sentry_unit, filename, mtime,
366- sleep_time=20):
367+ sleep_time=20, retry_count=30,
368+ retry_sleep_time=10):
369 """Check if file was modified after a given time.
370
371 Args:
372 sentry_unit (sentry): The sentry unit to check the file mtime on
373 filename (string): The file to check mtime of
374 mtime (float): The epoch time to check against
375- sleep_time (int): Seconds to sleep before looking for process
376+ sleep_time (int): Initial sleep time (s) before looking for file
377+ retry_sleep_time (int): Time (s) to sleep between retries
378+ retry_count (int): If file is not found, how many times to retry
379
380 Returns:
381 bool: True if file was modified more recently than mtime, False if
382- file was modified before mtime,
383+ file was modified before mtime, or if file not found.
384 """
385- self.log.debug('Checking %s updated since %s' % (filename, mtime))
386+ unit_name = sentry_unit.info['unit_name']
387+ self.log.debug('Checking that %s updated since %s on '
388+ '%s' % (filename, mtime, unit_name))
389 time.sleep(sleep_time)
390- file_mtime = self._get_file_mtime(sentry_unit, filename)
391+ file_mtime = None
392+ tries = 0
393+ while tries <= retry_count and not file_mtime:
394+ try:
395+ file_mtime = self._get_file_mtime(sentry_unit, filename)
396+ self.log.debug('Attempt {} to get {} file mtime on {} '
397+ 'OK'.format(tries, filename, unit_name))
398+ except IOError as e:
399+ # NOTE(beisner) - race avoidance, file may not exist yet.
400+ # https://bugs.launchpad.net/charm-helpers/+bug/1474030
401+ self.log.debug('Attempt {} to get {} file mtime on {} '
402+ 'failed\n{}'.format(tries, filename,
403+ unit_name, e))
404+ time.sleep(retry_sleep_time)
405+ tries += 1
406+
407+ if not file_mtime:
408+ self.log.warn('Could not determine file mtime, assuming '
409+ 'file does not exist')
410+ return False
411+
412 if file_mtime >= mtime:
413 self.log.debug('File mtime is newer than provided mtime '
414- '(%s >= %s)' % (file_mtime, mtime))
415+ '(%s >= %s) on %s (OK)' % (file_mtime,
416+ mtime, unit_name))
417 return True
418 else:
419- self.log.warn('File mtime %s is older than provided mtime %s'
420- % (file_mtime, mtime))
421+ self.log.warn('File mtime is older than provided mtime'
422+ '(%s < on %s) on %s' % (file_mtime,
423+ mtime, unit_name))
424 return False
425
426 def validate_service_config_changed(self, sentry_unit, mtime, service,
427 filename, pgrep_full=None,
428- sleep_time=20, retry_count=2,
429- retry_sleep_time=30):
430+ sleep_time=20, retry_count=30,
431+ retry_sleep_time=10):
432 """Check service and file were updated after mtime
433
434 Args:
435@@ -456,7 +485,9 @@
436 sentry_unit,
437 filename,
438 mtime,
439- sleep_time=0)
440+ sleep_time=sleep_time,
441+ retry_count=retry_count,
442+ retry_sleep_time=retry_sleep_time)
443
444 return service_restart and config_update
445
446
447=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
448--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-15 09:06:11 +0000
449+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-30 08:52:41 +0000
450@@ -752,7 +752,7 @@
451 self.log.debug('SSL is enabled @{}:{} '
452 '({})'.format(host, port, unit_name))
453 return True
454- elif not port and not conf_ssl:
455+ elif not conf_ssl:
456 self.log.debug('SSL not enabled @{}:{} '
457 '({})'.format(host, port, unit_name))
458 return False
459
460=== modified file 'tests/charmhelpers/core/hookenv.py'
461--- tests/charmhelpers/core/hookenv.py 2015-09-02 09:32:16 +0000
462+++ tests/charmhelpers/core/hookenv.py 2015-09-30 08:52:41 +0000
463@@ -623,6 +623,38 @@
464 return unit_get('private-address')
465
466
467+@cached
468+def storage_get(attribute="", storage_id=""):
469+ """Get storage attributes"""
470+ _args = ['storage-get', '--format=json']
471+ if storage_id:
472+ _args.extend(('-s', storage_id))
473+ if attribute:
474+ _args.append(attribute)
475+ try:
476+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
477+ except ValueError:
478+ return None
479+
480+
481+@cached
482+def storage_list(storage_name=""):
483+ """List the storage IDs for the unit"""
484+ _args = ['storage-list', '--format=json']
485+ if storage_name:
486+ _args.append(storage_name)
487+ try:
488+ return json.loads(subprocess.check_output(_args).decode('UTF-8'))
489+ except ValueError:
490+ return None
491+ except OSError as e:
492+ import errno
493+ if e.errno == errno.ENOENT:
494+ # storage-list does not exist
495+ return []
496+ raise
497+
498+
499 class UnregisteredHookError(Exception):
500 """Raised when an undefined hook is called"""
501 pass

Subscribers

People subscribed via source and target branches