Merge lp:~hopem/charms/trusty/percona-cluster/min-cluster-size into lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 66
Proposed branch: lp:~hopem/charms/trusty/percona-cluster/min-cluster-size
Merge into: lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next
Diff against target: 790 lines (+444/-77)
10 files modified
Makefile (+2/-2)
config.yaml (+6/-0)
hooks/percona_hooks.py (+113/-33)
hooks/percona_utils.py (+121/-0)
tests/10-deploy_test.py (+1/-1)
tests/40-test-bootstrap-single.py (+17/-0)
tests/41-test-bootstrap-multi-notmin.py (+41/-0)
tests/42-test-bootstrap-multi-min.py (+43/-0)
tests/basic_deployment.py (+83/-41)
unit_tests/test_percona_utils.py (+17/-0)
To merge this branch: bzr merge lp:~hopem/charms/trusty/percona-cluster/min-cluster-size
Reviewer Review Type Date Requested Status
James Page Approve
David Ames Pending
Review via email: mp+265502@code.launchpad.net

This proposal supersedes a proposal from 2015-07-17.

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

charm_lint_check #6364 percona-cluster-next for hopem mp265108
    LINT OK: passed

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

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

charm_unit_test #5996 percona-cluster-next for hopem mp265108
    UNIT OK: passed

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

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

charm_amulet_test #5175 percona-cluster-next for hopem mp265108
    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/11893665/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5175/

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

charm_unit_test #6137 percona-cluster-next for hopem mp265108
    UNIT OK: passed

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

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

charm_lint_check #6505 percona-cluster-next for hopem mp265108
    LINT OK: passed

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

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

charm_amulet_test #5227 percona-cluster-next for hopem mp265108
    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/11908703/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5227/

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

charm_unit_test #6141 percona-cluster-next for hopem mp265108
    UNIT OK: passed

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

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

charm_lint_check #6509 percona-cluster-next for hopem mp265108
    LINT OK: passed

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

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

charm_amulet_test #5231 percona-cluster-next for hopem mp265108
    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/11909255/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5231/

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

charm_unit_test #6142 percona-cluster-next for hopem mp265108
    UNIT OK: passed

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

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

charm_lint_check #6510 percona-cluster-next for hopem mp265108
    LINT OK: passed

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

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

charm_amulet_test #5232 percona-cluster-next for hopem mp265108
    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/11909396/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5232/

Revision history for this message
David Ames (thedac) wrote : Posted in a previous version of this proposal

Ed,

This looks good and appears to solve bug 1475585.

I am new here but it seems we want to keep the *_hooks.py as clean as possible and have these helper functions in the *_utils.py. For example is_bootstrapped and get_wsrep_value and possibly all of the new functions could be moved over to the utils
functions into perconal_utils.py

This should really get its own amulet test. Setup with min-cluster-size, add min-cluster-size -1, verify percona has not started
, add another unit and verify it all comes up.

Lastly, is "boostrap-pxc mysql" idempotent? If any config change changes the config file this gets run. On a stable cluster would "boostrap-pxc mysql" being run break anything?

review: Needs Fixing
Revision history for this message
Edward Hope-Morley (hopem) wrote : Posted in a previous version of this proposal

Thanks for the review David.

I totally agree that the helper functions should go into percona_utils.py and I will move them across.

I'll see what I can do amulet test-wise.

With regards to bootstrap-pxc, this should be safe since it will only be called once at bootstrap time (ideally once all nodes are configured but that is not a hard requirement). If it were called prior to more units being added to the cluster, on subsequent runs of config_changed() we should only ever be calling 'restart' although bootstrap-pxc is idempotent and, in fact, can be done before you have all units in the cluster. The charm will also only restart percona if the config file changes.

Revision history for this message
Edward Hope-Morley (hopem) wrote : Posted in a previous version of this proposal

Oh and yes, bootstrap-pxc is idempotent.

Revision history for this message
David Ames (thedac) wrote : Posted in a previous version of this proposal

> Oh and yes, bootstrap-pxc is idempotent.

Excellent.

Just to be clear bootstrap-pxc *will* be run more than once. After the cluster has been bootstrapped any config-changed run that changes the config file will "re-bootstrap" on the leader. This may not be your intent.

In config-changed bootstrapped is defaulted to False and therefore the leader will run render_config_restart_on_changed with bootstrap=True:

            elif clustered and is_leader():
                log("Leader unit - bootstrap required=%s" % (not bootstrapped),
                    DEBUG)
                render_config_restart_on_changed(clustered, hosts,
                                                 bootstrap=not bootstrapped)

And in render_config_restart_on_changed if the config file has been changed it will "re-bootstrap" rather than restart:

    if file_hash(MY_CNF) != pre_hash:
        if bootstrap:
            service('bootstrap-pxc', 'mysql')
            notify_bootstrapped()
            update_shared_db_rels()
        else:
            service_restart('mysql')

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6248 percona-cluster-next for hopem mp265502
    UNIT OK: passed

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

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #6616 percona-cluster-next for hopem mp265502
    LINT OK: passed

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

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5248 percona-cluster-next for hopem mp265502
    AMULET FAIL: amulet-test failed

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

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

Revision history for this message
Edward Hope-Morley (hopem) wrote :

I have done a full amulet test run with this charm (tests enabled in Makefile) and I get 100% success locally. I notice that the way amulet is responding to amulet.SKIP in osci is different to how it is handled locally i.e. I get:

juju-test.conductor.10-deploy_test.py DEBUG : Running 10-deploy_test.py (tests/10-deploy_test.py)
juju-test.conductor.10-deploy_test.py DEBUG : Please set the vip in local.yaml or env var AMULET_OS_VIP to run this test suite

juju-test.conductor.10-deploy_test.py DEBUG : Got exit code: 100
juju-test.conductor.10-deploy_test.py RESULT : ↷
juju-test.conductor DEBUG : Tearing down lxc juju environment
juju-test.conductor DEBUG : Calling "juju destroy-environment -y lxc"

yet OSCI says:

juju-test.conductor.10-deploy_test.py DEBUG : Running 10-deploy_test.py (tests/10-deploy_test.py)
juju-test.conductor.10-deploy_test.py DEBUG : Please set the vip in local.yaml or env var AMULET_OS_VIP to run this test suite

juju-test.conductor.10-deploy_test.py DEBUG : Got exit code: 100
juju-test.conductor.10-deploy_test.py RESULT : SKIP
juju-test.conductor INFO : Breaking here as requested by --set-e

66. By Edward Hope-Morley

[hopem,r=]

Add min-cluster-size config option. This allows the charm to wait
for a minimum number of peers to join before bootstrapping
percona and allowing relations to access the database.

Closes-Bug: 1475585

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6249 percona-cluster-next for hopem mp265502
    UNIT OK: passed

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

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #6617 percona-cluster-next for hopem mp265502
    LINT OK: passed

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

Revision history for this message
Edward Hope-Morley (hopem) wrote :

Hmm on closer inspection it appears that SKIP is being treated as a failure for me too (if i remove the vip config). Unlike OSCI I am not using --set-e but I am also not using --fail-on-skip so they should not be getting treated as failures. I'm gonna re-disable amulet here for now until this gets sorted.

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5249 percona-cluster-next for hopem mp265502
    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/11919482/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5249/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #6619 percona-cluster-next for hopem mp265502
    LINT OK: passed

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

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6251 percona-cluster-next for hopem mp265502
    UNIT OK: passed

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

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5251 percona-cluster-next for hopem mp265502
    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/11919598/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5251/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

FYI: in UOSCI, we use --set-e so that the runner will keep the juju environment after a failed test exits. We do that so that we can collect juju unit logs. Otherwise, you'd just get "I failed."

However, based on the juju test -h, I also don't believe the intended behavior is to have a SKIP invoke --set-e.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

AMULET_OS_VIP (and other network environment variables) are now calculated and exported to dynamically represent the IP space of each job's arbitrary jenkins slave.

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5253 percona-cluster-next for hopem mp265502
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/5253/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Woomp there it is!

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Amulet results from #5253 for those without private jenkins access: http://paste.ubuntu.com/11920594/

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Also just fyi, an example of what now gets passed to all uosci amulet jobs:
http://paste.ubuntu.com/11920597/

Revision history for this message
James Page (james-page) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2015-04-20 10:53:43 +0000
+++ Makefile 2015-07-22 13:55:56 +0000
@@ -13,8 +13,8 @@
13 @echo Starting amulet tests...13 @echo Starting amulet tests...
14 #NOTE(beisner): can remove -v after bug 1320357 is fixed14 #NOTE(beisner): can remove -v after bug 1320357 is fixed
15 # https://bugs.launchpad.net/amulet/+bug/132035715 # https://bugs.launchpad.net/amulet/+bug/1320357
16 # @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 270016 @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700
17 echo "Tests disables; http://pad.lv/1446169"17 #echo "Tests disables; http://pad.lv/1446169"
1818
19bin/charm_helpers_sync.py:19bin/charm_helpers_sync.py:
20 @mkdir -p bin20 @mkdir -p bin
2121
=== modified file 'config.yaml'
--- config.yaml 2015-06-04 15:11:31 +0000
+++ config.yaml 2015-07-22 13:55:56 +0000
@@ -111,3 +111,9 @@
111 but also can be set to any specific value for the system.111 but also can be set to any specific value for the system.
112 Suffix this value with 'K','M','G', or 'T' to get the relevant kilo/mega/etc. bytes.112 Suffix this value with 'K','M','G', or 'T' to get the relevant kilo/mega/etc. bytes.
113 If suffixed with %, one will get that percentage of system total memory devoted.113 If suffixed with %, one will get that percentage of system total memory devoted.
114 min-cluster-size:
115 type: int
116 default:
117 description: |
118 Minimum number of units expected to exist before charm will attempt to
119 bootstrap percona cluster. If no value is provided this setting is ignored.
114120
=== modified file 'hooks/percona_hooks.py'
--- hooks/percona_hooks.py 2015-06-09 10:42:32 +0000
+++ hooks/percona_hooks.py 2015-07-22 13:55:56 +0000
@@ -1,17 +1,19 @@
1#!/usr/bin/python1#!/usr/bin/python
2# TODO: Support changes to root and sstuser passwords2# TODO: Support changes to root and sstuser passwords
3
4import sys3import sys
5import json4import json
6import os5import os
7import socket6import socket
7import time
88
9from charmhelpers.core.hookenv import (9from charmhelpers.core.hookenv import (
10 Hooks, UnregisteredHookError,10 Hooks, UnregisteredHookError,
11 is_relation_made,11 is_relation_made,
12 log,12 log,
13 local_unit,
13 relation_get,14 relation_get,
14 relation_set,15 relation_set,
16 relation_id,
15 relation_ids,17 relation_ids,
16 related_units,18 related_units,
17 unit_get,19 unit_get,
@@ -20,10 +22,13 @@
20 relation_type,22 relation_type,
21 DEBUG,23 DEBUG,
22 INFO,24 INFO,
25 WARNING,
23 is_leader,26 is_leader,
24)27)
25from charmhelpers.core.host import (28from charmhelpers.core.host import (
29 service,
26 service_restart,30 service_restart,
31 service_start,
27 file_hash,32 file_hash,
28 lsb_release,33 lsb_release,
29)34)
@@ -52,6 +57,9 @@
52 get_db_helper,57 get_db_helper,
53 mark_seeded, seeded,58 mark_seeded, seeded,
54 install_mysql_ocf,59 install_mysql_ocf,
60 is_sufficient_peers,
61 notify_bootstrapped,
62 is_bootstrapped,
55)63)
56from charmhelpers.contrib.database.mysql import (64from charmhelpers.contrib.database.mysql import (
57 PerconaClusterHelper,65 PerconaClusterHelper,
@@ -131,6 +139,57 @@
131 render(os.path.basename(MY_CNF), MY_CNF, context, perms=0o444)139 render(os.path.basename(MY_CNF), MY_CNF, context, perms=0o444)
132140
133141
142def render_config_restart_on_changed(clustered, hosts, bootstrap=False):
143 """Render mysql config and restart mysql service if file changes as a
144 result.
145
146 If bootstrap is True we do a bootstrap-pxc in order to bootstrap the
147 percona cluster. This should only be performed once at cluster creation
148 time.
149
150 If percona is already bootstrapped we can get away with just ensuring that
151 it is started so long as the new node to be added is guaranteed to have
152 been restarted so as to apply the new config.
153 """
154 pre_hash = file_hash(MY_CNF)
155 render_config(clustered, hosts)
156 if file_hash(MY_CNF) != pre_hash:
157 if bootstrap:
158 service('bootstrap-pxc', 'mysql')
159 notify_bootstrapped()
160 update_shared_db_rels()
161 else:
162 delay = 1
163 attempts = 0
164 max_retries = 5
165 # NOTE(dosaboy): avoid unnecessary restarts. Once mysql is started
166 # it needn't be restarted when new units join the cluster since the
167 # new units will join and apply their own config.
168 if not seeded():
169 action = service_restart
170 else:
171 action = service_start
172
173 while not action('mysql'):
174 if attempts == max_retries:
175 raise Exception("Failed to start mysql (max retries "
176 "reached)")
177
178 log("Failed to start mysql - retrying in %ss" % (delay),
179 WARNING)
180 time.sleep(delay)
181 delay += 2
182 attempts += 1
183 else:
184 mark_seeded()
185
186
187def update_shared_db_rels():
188 for r_id in relation_ids('shared-db'):
189 for unit in related_units(r_id):
190 shared_db_changed(r_id, unit)
191
192
134@hooks.hook('upgrade-charm')193@hooks.hook('upgrade-charm')
135@hooks.hook('config-changed')194@hooks.hook('config-changed')
136def config_changed():195def config_changed():
@@ -139,33 +198,48 @@
139198
140 hosts = get_cluster_hosts()199 hosts = get_cluster_hosts()
141 clustered = len(hosts) > 1200 clustered = len(hosts) > 1
142 pre_hash = file_hash(MY_CNF)201 bootstrapped = is_bootstrapped()
143 render_config(clustered, hosts)202
144 if file_hash(MY_CNF) != pre_hash:203 # NOTE: only configure the cluster if we have sufficient peers. This only
204 # applies if min-cluster-size is provided and is used to avoid extraneous
205 # configuration changes and premature bootstrapping as the cluster is
206 # deployed.
207 if is_sufficient_peers():
145 try:208 try:
146 # NOTE(jamespage): try with leadership election209 # NOTE(jamespage): try with leadership election
147 if clustered and not is_leader() and not seeded():210 if not clustered:
148 # Bootstrap node into seeded cluster211 render_config_restart_on_changed(clustered, hosts)
149 service_restart('mysql')212 elif clustered and is_leader():
150 mark_seeded()213 log("Leader unit - bootstrap required=%s" % (not bootstrapped),
151 elif not clustered:214 DEBUG)
152 # Restart with new configuration215 render_config_restart_on_changed(clustered, hosts,
153 service_restart('mysql')216 bootstrap=not bootstrapped)
217 elif bootstrapped:
218 log("Cluster is bootstrapped - configuring mysql on this node",
219 DEBUG)
220 render_config_restart_on_changed(clustered, hosts)
221 else:
222 log("Not configuring", DEBUG)
223
154 except NotImplementedError:224 except NotImplementedError:
155 # NOTE(jamespage): fallback to legacy behaviour.225 # NOTE(jamespage): fallback to legacy behaviour.
156 oldest = oldest_peer(peer_units())226 oldest = oldest_peer(peer_units())
157 if clustered and not oldest and not seeded():227 if not clustered:
158 # Bootstrap node into seeded cluster228 render_config_restart_on_changed(clustered, hosts)
159 service_restart('mysql')229 elif clustered and oldest:
160 mark_seeded()230 log("Leader unit - bootstrap required=%s" % (not bootstrapped),
161 elif not clustered:231 DEBUG)
162 # Restart with new configuration232 render_config_restart_on_changed(clustered, hosts,
163 service_restart('mysql')233 bootstrap=not bootstrapped)
234 elif bootstrapped:
235 log("Cluster is bootstrapped - configuring mysql on this node",
236 DEBUG)
237 render_config_restart_on_changed(clustered, hosts)
238 else:
239 log("Not configuring", DEBUG)
164240
165 # Notify any changes to the access network241 # Notify any changes to the access network
166 for r_id in relation_ids('shared-db'):242 update_shared_db_rels()
167 for unit in related_units(r_id):
168 shared_db_changed(r_id, unit)
169243
170 # (re)install pcmkr agent244 # (re)install pcmkr agent
171 install_mysql_ocf()245 install_mysql_ocf()
@@ -176,15 +250,20 @@
176250
177251
178@hooks.hook('cluster-relation-joined')252@hooks.hook('cluster-relation-joined')
179def cluster_joined(relation_id=None):253def cluster_joined():
180 if config('prefer-ipv6'):254 if config('prefer-ipv6'):
181 addr = get_ipv6_addr(exc_list=[config('vip')])[0]255 addr = get_ipv6_addr(exc_list=[config('vip')])[0]
182 relation_settings = {'private-address': addr,256 relation_settings = {'private-address': addr,
183 'hostname': socket.gethostname()}257 'hostname': socket.gethostname()}
184 log("Setting cluster relation: '%s'" % (relation_settings),258 log("Setting cluster relation: '%s'" % (relation_settings),
185 level=INFO)259 level=INFO)
186 relation_set(relation_id=relation_id,260 relation_set(relation_settings=relation_settings)
187 relation_settings=relation_settings)261
262 # Ensure all new peers are aware
263 cluster_state_uuid = relation_get('bootstrap-uuid', unit=local_unit())
264 if cluster_state_uuid:
265 notify_bootstrapped(cluster_rid=relation_id(),
266 cluster_uuid=cluster_state_uuid)
188267
189268
190@hooks.hook('cluster-relation-departed')269@hooks.hook('cluster-relation-departed')
@@ -282,10 +361,15 @@
282# TODO: This could be a hook common between mysql and percona-cluster361# TODO: This could be a hook common between mysql and percona-cluster
283@hooks.hook('shared-db-relation-changed')362@hooks.hook('shared-db-relation-changed')
284def shared_db_changed(relation_id=None, unit=None):363def shared_db_changed(relation_id=None, unit=None):
364 if not is_bootstrapped():
365 log("Percona cluster not yet bootstrapped - deferring shared-db rel "
366 "until bootstrapped", DEBUG)
367 return
368
285 if not is_elected_leader(DC_RESOURCE_NAME):369 if not is_elected_leader(DC_RESOURCE_NAME):
286 # NOTE(jamespage): relation level data candidate370 # NOTE(jamespage): relation level data candidate
287 log('Service is peered, clearing shared-db relation'371 log('Service is peered, clearing shared-db relation '
288 ' as this service unit is not the leader')372 'as this service unit is not the leader')
289 relation_clear(relation_id)373 relation_clear(relation_id)
290 # Each unit needs to set the db information otherwise if the unit374 # Each unit needs to set the db information otherwise if the unit
291 # with the info dies the settings die with it Bug# 1355848375 # with the info dies the settings die with it Bug# 1355848
@@ -419,7 +503,7 @@
419503
420 resources = {'res_mysql_vip': res_mysql_vip,504 resources = {'res_mysql_vip': res_mysql_vip,
421 'res_mysql_monitor': 'ocf:percona:mysql_monitor'}505 'res_mysql_monitor': 'ocf:percona:mysql_monitor'}
422 db_helper = get_db_helper()506
423 sstpsswd = config('sst-password')507 sstpsswd = config('sst-password')
424 resource_params = {'res_mysql_vip': vip_params,508 resource_params = {'res_mysql_vip': vip_params,
425 'res_mysql_monitor':509 'res_mysql_monitor':
@@ -451,9 +535,7 @@
451 if (clustered and is_elected_leader(DC_RESOURCE_NAME)):535 if (clustered and is_elected_leader(DC_RESOURCE_NAME)):
452 log('Cluster configured, notifying other services')536 log('Cluster configured, notifying other services')
453 # Tell all related services to start using the VIP537 # Tell all related services to start using the VIP
454 for r_id in relation_ids('shared-db'):538 update_shared_db_rels()
455 for unit in related_units(r_id):
456 shared_db_changed(r_id, unit)
457 for r_id in relation_ids('db'):539 for r_id in relation_ids('db'):
458 for unit in related_units(r_id):540 for unit in related_units(r_id):
459 db_changed(r_id, unit, admin=False)541 db_changed(r_id, unit, admin=False)
@@ -465,9 +547,7 @@
465@hooks.hook('leader-settings-changed')547@hooks.hook('leader-settings-changed')
466def leader_settings_changed():548def leader_settings_changed():
467 # Notify any changes to data in leader storage549 # Notify any changes to data in leader storage
468 for r_id in relation_ids('shared-db'):550 update_shared_db_rels()
469 for unit in related_units(r_id):
470 shared_db_changed(r_id, unit)
471551
472552
473@hooks.hook('nrpe-external-master-relation-joined',553@hooks.hook('nrpe-external-master-relation-joined',
474554
=== modified file 'hooks/percona_utils.py'
--- hooks/percona_utils.py 2015-05-13 10:21:30 +0000
+++ hooks/percona_utils.py 2015-07-22 13:55:56 +0000
@@ -5,6 +5,8 @@
5import tempfile5import tempfile
6import os6import os
7import shutil7import shutil
8import uuid
9
8from charmhelpers.core.host import (10from charmhelpers.core.host import (
9 lsb_release11 lsb_release
10)12)
@@ -20,6 +22,14 @@
20 config,22 config,
21 log,23 log,
22 DEBUG,24 DEBUG,
25 INFO,
26 WARNING,
27 ERROR,
28 is_leader,
29)
30from charmhelpers.contrib.hahelpers.cluster import (
31 oldest_peer,
32 peer_units,
23)33)
24from charmhelpers.fetch import (34from charmhelpers.fetch import (
25 apt_install,35 apt_install,
@@ -32,6 +42,11 @@
32 MySQLHelper,42 MySQLHelper,
33)43)
3444
45# NOTE: python-mysqldb is installed by charmhelpers.contrib.database.mysql so
46# hence why we import here
47from MySQLdb import (
48 OperationalError
49)
3550
36PACKAGES = [51PACKAGES = [
37 'percona-xtradb-cluster-server-5.5',52 'percona-xtradb-cluster-server-5.5',
@@ -90,6 +105,29 @@
90 return answers[0].address105 return answers[0].address
91106
92107
108def is_sufficient_peers():
109 """If min-cluster-size has been provided, check that we have sufficient
110 number of peers to proceed with bootstrapping percona cluster.
111 """
112 min_size = config('min-cluster-size')
113 if min_size:
114 size = 0
115 for rid in relation_ids('cluster'):
116 size = len(related_units(rid))
117
118 # Include this unit
119 size += 1
120 if min_size > size:
121 log("Insufficient number of units to configure percona cluster "
122 "(expected=%s, got=%s)" % (min_size, size), level=INFO)
123 return False
124 else:
125 log("Sufficient units available to configure percona cluster "
126 "(>=%s)" % (min_size), level=DEBUG)
127
128 return True
129
130
93def get_cluster_hosts():131def get_cluster_hosts():
94 hosts_map = {}132 hosts_map = {}
95 hostname = get_host_ip()133 hostname = get_host_ip()
@@ -246,3 +284,86 @@
246 shutil.copy(src_file, dest_file)284 shutil.copy(src_file, dest_file)
247 else:285 else:
248 log("'%s' already exists, skipping" % dest_file, level='INFO')286 log("'%s' already exists, skipping" % dest_file, level='INFO')
287
288
289def get_wsrep_value(key):
290 m_helper = get_db_helper()
291 try:
292 m_helper.connect(password=m_helper.get_mysql_root_password())
293 except OperationalError:
294 log("Could not connect to db", DEBUG)
295 return None
296
297 cursor = m_helper.connection.cursor()
298 ret = None
299 try:
300 cursor.execute("show status like '%s'" % (key))
301 ret = cursor.fetchall()
302 except:
303 log("Failed to get '%s'", ERROR)
304 return None
305 finally:
306 cursor.close()
307
308 if ret:
309 return ret[0][1]
310
311 return None
312
313
314def is_bootstrapped():
315 if not is_sufficient_peers():
316 return False
317
318 uuids = []
319 rids = relation_ids('cluster') or []
320 for rid in rids:
321 units = related_units(rid)
322 units.append(local_unit())
323 for unit in units:
324 id = relation_get('bootstrap-uuid', unit=unit, rid=rid)
325 if id:
326 uuids.append(id)
327
328 if uuids:
329 if len(set(uuids)) > 1:
330 log("Found inconsistent bootstrap uuids - %s" % (uuids), WARNING)
331
332 return True
333
334 try:
335 if not is_leader():
336 return False
337 except:
338 oldest = oldest_peer(peer_units())
339 if not oldest:
340 return False
341
342 # If this is the leader but we have not yet broadcast the cluster uuid then
343 # do so now.
344 wsrep_ready = get_wsrep_value('wsrep_ready') or ""
345 if wsrep_ready.lower() in ['on', 'ready']:
346 cluster_state_uuid = get_wsrep_value('wsrep_cluster_state_uuid')
347 if cluster_state_uuid:
348 notify_bootstrapped(cluster_uuid=cluster_state_uuid)
349 return True
350
351 return False
352
353
354def notify_bootstrapped(cluster_rid=None, cluster_uuid=None):
355 if cluster_rid:
356 rids = [cluster_rid]
357 else:
358 rids = relation_ids('cluster')
359
360 log("Notifying peers that percona is bootstrapped", DEBUG)
361 if not cluster_uuid:
362 cluster_uuid = get_wsrep_value('wsrep_cluster_state_uuid')
363 if not cluster_uuid:
364 cluster_uuid = str(uuid.uuid4())
365 log("Could not determine cluster uuid so using '%s' instead" %
366 (cluster_uuid), INFO)
367
368 for rid in rids:
369 relation_set(relation_id=rid, **{'bootstrap-uuid': cluster_uuid})
249370
=== modified file 'tests/10-deploy_test.py'
--- tests/10-deploy_test.py 2015-03-06 15:35:01 +0000
+++ tests/10-deploy_test.py 2015-07-22 13:55:56 +0000
@@ -19,7 +19,7 @@
19 new_master = self.find_master()19 new_master = self.find_master()
20 assert new_master is not None, "master unit not found"20 assert new_master is not None, "master unit not found"
21 assert (new_master.info['public-address'] !=21 assert (new_master.info['public-address'] !=
22 old_master.info['public-address'])22 old_master.info['public-address'])
2323
24 assert self.is_port_open(address=self.vip), 'cannot connect to vip'24 assert self.is_port_open(address=self.vip), 'cannot connect to vip'
2525
2626
=== added file 'tests/40-test-bootstrap-single.py'
--- tests/40-test-bootstrap-single.py 1970-01-01 00:00:00 +0000
+++ tests/40-test-bootstrap-single.py 2015-07-22 13:55:56 +0000
@@ -0,0 +1,17 @@
1#!/usr/bin/env python
2# test percona-cluster (1 node)
3import basic_deployment
4
5
6class SingleNode(basic_deployment.BasicDeployment):
7 def __init__(self):
8 super(SingleNode, self).__init__(units=1)
9
10 def run(self):
11 super(SingleNode, self).run()
12 assert self.is_pxc_bootstrapped(), "Cluster not bootstrapped"
13
14
15if __name__ == "__main__":
16 t = SingleNode()
17 t.run()
018
=== added file 'tests/41-test-bootstrap-multi-notmin.py'
--- tests/41-test-bootstrap-multi-notmin.py 1970-01-01 00:00:00 +0000
+++ tests/41-test-bootstrap-multi-notmin.py 2015-07-22 13:55:56 +0000
@@ -0,0 +1,41 @@
1#!/usr/bin/env python
2# test percona-cluster (1 node)
3import basic_deployment
4
5
6class MultiNode(basic_deployment.BasicDeployment):
7 def __init__(self):
8 super(MultiNode, self).__init__(units=2)
9
10 def _get_configs(self):
11 """Configure all of the services."""
12 cfg_percona = {'sst-password': 'ubuntu',
13 'root-password': 't00r',
14 'dataset-size': '512M',
15 'vip': self.vip,
16 'min-cluster-size': 3}
17
18 cfg_ha = {'debug': True,
19 'corosync_mcastaddr': '226.94.1.4',
20 'corosync_key': ('xZP7GDWV0e8Qs0GxWThXirNNYlScgi3sRTdZk/IXKD'
21 'qkNFcwdCWfRQnqrHU/6mb6sz6OIoZzX2MtfMQIDcXu'
22 'PqQyvKuv7YbRyGHmQwAWDUA4ed759VWAO39kHkfWp9'
23 'y5RRk/wcHakTcWYMwm70upDGJEP00YT3xem3NQy27A'
24 'C1w=')}
25
26 configs = {'percona-cluster': cfg_percona}
27 if self.units > 1:
28 configs['hacluster'] = cfg_ha
29
30 return configs
31
32 def run(self):
33 super(MultiNode, self).run()
34 got = self.get_cluster_size()
35 msg = "Percona cluster unexpected size (wanted=%s, got=%s)" % (1, got)
36 assert got == '1', msg
37
38
39if __name__ == "__main__":
40 t = MultiNode()
41 t.run()
042
=== added file 'tests/42-test-bootstrap-multi-min.py'
--- tests/42-test-bootstrap-multi-min.py 1970-01-01 00:00:00 +0000
+++ tests/42-test-bootstrap-multi-min.py 2015-07-22 13:55:56 +0000
@@ -0,0 +1,43 @@
1#!/usr/bin/env python
2# test percona-cluster (1 node)
3import basic_deployment
4
5
6class MultiNode(basic_deployment.BasicDeployment):
7 def __init__(self):
8 super(MultiNode, self).__init__(units=3)
9
10 def _get_configs(self):
11 """Configure all of the services."""
12 cfg_percona = {'sst-password': 'ubuntu',
13 'root-password': 't00r',
14 'dataset-size': '512M',
15 'vip': self.vip,
16 'min-cluster-size': 3}
17
18 cfg_ha = {'debug': True,
19 'corosync_mcastaddr': '226.94.1.4',
20 'corosync_key': ('xZP7GDWV0e8Qs0GxWThXirNNYlScgi3sRTdZk/IXKD'
21 'qkNFcwdCWfRQnqrHU/6mb6sz6OIoZzX2MtfMQIDcXu'
22 'PqQyvKuv7YbRyGHmQwAWDUA4ed759VWAO39kHkfWp9'
23 'y5RRk/wcHakTcWYMwm70upDGJEP00YT3xem3NQy27A'
24 'C1w=')}
25
26 configs = {'percona-cluster': cfg_percona}
27 if self.units > 1:
28 configs['hacluster'] = cfg_ha
29
30 return configs
31
32 def run(self):
33 super(MultiNode, self).run()
34 msg = "Percona cluster failed to bootstrap"
35 assert self.is_pxc_bootstrapped(), msg
36 got = self.get_cluster_size()
37 msg = "Percona cluster unexpected size (wanted=%s, got=%s)" % (3, got)
38 assert got == '3', msg
39
40
41if __name__ == "__main__":
42 t = MultiNode()
43 t.run()
044
=== modified file 'tests/basic_deployment.py'
--- tests/basic_deployment.py 2015-04-17 10:05:16 +0000
+++ tests/basic_deployment.py 2015-07-22 13:55:56 +0000
@@ -1,8 +1,8 @@
1import amulet1import amulet
2import re
2import os3import os
3import time4import time
4import telnetlib5import telnetlib
5import unittest
6import yaml6import yaml
7from charmhelpers.contrib.openstack.amulet.deployment import (7from charmhelpers.contrib.openstack.amulet.deployment import (
8 OpenStackAmuletDeployment8 OpenStackAmuletDeployment
@@ -17,19 +17,21 @@
17 self.units = units17 self.units = units
18 self.master_unit = None18 self.master_unit = None
19 self.vip = None19 self.vip = None
20 if vip:20 if units > 1:
21 self.vip = vip21 if vip:
22 elif 'AMULET_OS_VIP' in os.environ:22 self.vip = vip
23 self.vip = os.environ.get('AMULET_OS_VIP')23 elif 'AMULET_OS_VIP' in os.environ:
24 elif os.path.isfile('local.yaml'):24 self.vip = os.environ.get('AMULET_OS_VIP')
25 with open('local.yaml', 'rb') as f:25 elif os.path.isfile('local.yaml'):
26 self.cfg = yaml.safe_load(f.read())26 with open('local.yaml', 'rb') as f:
27 self.cfg = yaml.safe_load(f.read())
2728
28 self.vip = self.cfg.get('vip')29 self.vip = self.cfg.get('vip')
29 else:30 else:
30 amulet.raise_status(amulet.SKIP,31 amulet.raise_status(amulet.SKIP,
31 ("please set the vip in local.yaml or env var "32 ("Please set the vip in local.yaml or "
32 "AMULET_OS_VIP to run this test suite"))33 "env var AMULET_OS_VIP to run this test "
34 "suite"))
3335
34 def _add_services(self):36 def _add_services(self):
35 """Add services37 """Add services
@@ -40,16 +42,20 @@
40 """42 """
41 this_service = {'name': 'percona-cluster',43 this_service = {'name': 'percona-cluster',
42 'units': self.units}44 'units': self.units}
43 other_services = [{'name': 'hacluster'}]45 other_services = []
46 if self.units > 1:
47 other_services.append({'name': 'hacluster'})
48
44 super(BasicDeployment, self)._add_services(this_service,49 super(BasicDeployment, self)._add_services(this_service,
45 other_services)50 other_services)
4651
47 def _add_relations(self):52 def _add_relations(self):
48 """Add all of the relations for the services."""53 """Add all of the relations for the services."""
49 relations = {'percona-cluster:ha': 'hacluster:ha'}54 if self.units > 1:
50 super(BasicDeployment, self)._add_relations(relations)55 relations = {'percona-cluster:ha': 'hacluster:ha'}
56 super(BasicDeployment, self)._add_relations(relations)
5157
52 def _configure_services(self):58 def _get_configs(self):
53 """Configure all of the services."""59 """Configure all of the services."""
54 cfg_percona = {'sst-password': 'ubuntu',60 cfg_percona = {'sst-password': 'ubuntu',
55 'root-password': 't00r',61 'root-password': 't00r',
@@ -64,45 +70,55 @@
64 'y5RRk/wcHakTcWYMwm70upDGJEP00YT3xem3NQy27A'70 'y5RRk/wcHakTcWYMwm70upDGJEP00YT3xem3NQy27A'
65 'C1w=')}71 'C1w=')}
6672
67 configs = {'percona-cluster': cfg_percona,73 configs = {'percona-cluster': cfg_percona}
68 'hacluster': cfg_ha}74 if self.units > 1:
69 super(BasicDeployment, self)._configure_services(configs)75 configs['hacluster'] = cfg_ha
76
77 return configs
78
79 def _configure_services(self):
80 super(BasicDeployment, self)._configure_services(self._get_configs())
7081
71 def run(self):82 def run(self):
72 # The number of seconds to wait for the environment to setup.
73 seconds = 1200
74
75 self._add_services()83 self._add_services()
76 self._add_relations()84 self._add_relations()
77 self._configure_services()85 self._configure_services()
78 self._deploy()86 self._deploy()
7987
80 i = 088 if self.units > 1:
81 while i < 30 and not self.master_unit:89 i = 0
82 self.master_unit = self.find_master()90 while i < 30 and not self.master_unit:
83 i += 191 self.master_unit = self.find_master()
84 time.sleep(10)92 i += 1
8593 time.sleep(10)
86 assert self.master_unit is not None, 'percona-cluster vip not found'94
8795 msg = 'percona-cluster vip not found'
88 output, code = self.master_unit.run('sudo crm_verify --live-check')96 assert self.master_unit is not None, msg
89 assert code == 0, "'crm_verify --live-check' failed"97
9098 _, code = self.master_unit.run('sudo crm_verify --live-check')
91 resources = ['res_mysql_vip']99 assert code == 0, "'crm_verify --live-check' failed"
92 resources += ['res_mysql_monitor:%d' % i for i in range(self.units)]100
93101 resources = ['res_mysql_vip']
94 assert sorted(self.get_pcmkr_resources()) == sorted(resources)102 resources += ['res_mysql_monitor:%d' %
103 i for i in range(self.units)]
104
105 assert sorted(self.get_pcmkr_resources()) == sorted(resources)
106 else:
107 self.master_unit = self.find_master(ha=False)
95108
96 for i in range(self.units):109 for i in range(self.units):
97 uid = 'percona-cluster/%d' % i110 uid = 'percona-cluster/%d' % i
98 unit = self.d.sentry.unit[uid]111 unit = self.d.sentry.unit[uid]
99 assert self.is_mysqld_running(unit), 'mysql not running: %s' % uid112 assert self.is_mysqld_running(unit), 'mysql not running: %s' % uid
100113
101 def find_master(self):114 def find_master(self, ha=True):
102 for unit_id, unit in self.d.sentry.unit.items():115 for unit_id, unit in self.d.sentry.unit.items():
103 if not unit_id.startswith('percona-cluster/'):116 if not unit_id.startswith('percona-cluster/'):
104 continue117 continue
105118
119 if not ha:
120 return unit
121
106 # is the vip running here?122 # is the vip running here?
107 output, code = unit.run('sudo ip a | grep "inet %s/"' % self.vip)123 output, code = unit.run('sudo ip a | grep "inet %s/"' % self.vip)
108 print('---')124 print('---')
@@ -130,13 +146,37 @@
130 else:146 else:
131 u = self.master_unit147 u = self.master_unit
132148
133 output, code = u.run('pidof mysqld')149 _, code = u.run('pidof mysqld')
134
135 if code != 0:150 if code != 0:
151 print("ERROR: command returned non-zero '%s'" % (code))
136 return False152 return False
137153
138 return self.is_port_open(u, '3306')154 return self.is_port_open(u, '3306')
139155
156 def get_wsrep_value(self, attr, unit=None):
157 if unit:
158 u = unit
159 else:
160 u = self.master_unit
161
162 cmd = ("mysql -uroot -pt00r -e\"show status like '%s';\"| "
163 "grep %s" % (attr, attr))
164 output, code = u.run(cmd)
165 if code != 0:
166 print("ERROR: command returned non-zero '%s'" % (code))
167 return ""
168
169 value = re.search(r"^.+?\s+(.+)", output).group(1)
170 print("%s = %s" % (attr, value))
171 return value
172
173 def is_pxc_bootstrapped(self, unit=None):
174 value = self.get_wsrep_value('wsrep_ready', unit)
175 return value.lower() in ['on', 'ready']
176
177 def get_cluster_size(self, unit=None):
178 return self.get_wsrep_value('wsrep_cluster_size', unit)
179
140 def is_port_open(self, unit=None, port='3306', address=None):180 def is_port_open(self, unit=None, port='3306', address=None):
141 if unit:181 if unit:
142 addr = unit.info['public-address']182 addr = unit.info['public-address']
@@ -144,8 +184,10 @@
144 addr = address184 addr = address
145 else:185 else:
146 raise Exception('Please provide a unit or address')186 raise Exception('Please provide a unit or address')
187
147 try:188 try:
148 telnetlib.Telnet(addr, port)189 telnetlib.Telnet(addr, port)
149 return True190 return True
150 except TimeoutError: # noqa this exception only available in py3191 except TimeoutError: # noqa this exception only available in py3
192 print("ERROR: could not connect to %s:%s" % (addr, port))
151 return False193 return False
152194
=== modified file 'unit_tests/test_percona_utils.py'
--- unit_tests/test_percona_utils.py 2014-10-13 12:38:14 +0000
+++ unit_tests/test_percona_utils.py 2015-07-22 13:55:56 +0000
@@ -128,3 +128,20 @@
128 '0.0.0.0': 'hostB'})128 '0.0.0.0': 'hostB'})
129 mock_rel_get.assert_called_with(rid=2, unit=4)129 mock_rel_get.assert_called_with(rid=2, unit=4)
130 self.assertEqual(hosts, ['hostA', 'hostB'])130 self.assertEqual(hosts, ['hostA', 'hostB'])
131
132 @mock.patch.object(percona_utils, 'related_units')
133 @mock.patch.object(percona_utils, 'relation_ids')
134 @mock.patch.object(percona_utils, 'config')
135 def test_is_sufficient_peers(self, mock_config, mock_relation_ids,
136 mock_related_units):
137 _config = {'min-cluster-size': None}
138 mock_config.side_effect = lambda key: _config.get(key)
139 self.assertTrue(percona_utils.is_sufficient_peers())
140
141 mock_relation_ids.return_value = ['cluster:0']
142 mock_related_units.return_value = ['test/0']
143 _config = {'min-cluster-size': 3}
144 self.assertFalse(percona_utils.is_sufficient_peers())
145
146 mock_related_units.return_value = ['test/0', 'test/1']
147 self.assertTrue(percona_utils.is_sufficient_peers())

Subscribers

People subscribed via source and target branches