Merge lp:~james-page/charms/trusty/percona-cluster/network-splits into lp:~openstack-charmers-archive/charms/trusty/percona-cluster/trunk

Proposed by James Page
Status: Merged
Merged at revision: 34
Proposed branch: lp:~james-page/charms/trusty/percona-cluster/network-splits
Merge into: lp:~openstack-charmers-archive/charms/trusty/percona-cluster/trunk
Diff against target: 556 lines (+287/-74)
11 files modified
.bzrignore (+2/-0)
Makefile (+10/-7)
charm-helpers.yaml (+1/-0)
config.yaml (+45/-36)
hooks/charmhelpers/contrib/hahelpers/cluster.py (+3/-2)
hooks/charmhelpers/contrib/network/ip.py (+156/-0)
hooks/charmhelpers/core/hookenv.py (+5/-4)
hooks/charmhelpers/core/host.py (+11/-5)
hooks/charmhelpers/fetch/__init__.py (+23/-15)
hooks/percona_hooks.py (+30/-4)
hooks/percona_utils.py (+1/-1)
To merge this branch: bzr merge lp:~james-page/charms/trusty/percona-cluster/network-splits
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
James Page Needs Resubmitting
Review via email: mp+228151@code.launchpad.net

Description of the change

Add support for separate 'access-network' configuration.

To post a comment you must log in.
Revision history for this message
Liam Young (gnuoy) wrote :

See inline comment

review: Needs Fixing
Revision history for this message
James Page (james-page) :
review: Needs Resubmitting
Revision history for this message
Liam Young (gnuoy) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2014-07-24 15:25:17 +0000
@@ -0,0 +1,2 @@
1bin
2.coverage
03
=== modified file 'Makefile'
--- Makefile 2013-09-20 13:04:42 +0000
+++ Makefile 2014-07-24 15:25:17 +0000
@@ -3,12 +3,15 @@
33
4lint:4lint:
5 @flake8 --exclude hooks/charmhelpers hooks5 @flake8 --exclude hooks/charmhelpers hooks
6 #@flake8 --exclude hooks/charmhelpers unit_tests
7 @charm proof6 @charm proof
87
9test:8bin/charm_helpers_sync.py:
10 @echo Starting tests...9 @mkdir -p bin
11 @$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests10 @bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py \
1211 > bin/charm_helpers_sync.py
13sync:12
14 @charm-helper-sync -c charm-helpers-sync.yaml13sync: bin/charm_helpers_sync.py
14 @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers.yaml
15
16publish: lint
17 bzr push lp:charms/trusty/percona-cluster
1518
=== renamed file 'charm-helpers-sync.yaml' => 'charm-helpers.yaml'
--- charm-helpers-sync.yaml 2014-06-23 09:47:35 +0000
+++ charm-helpers.yaml 2014-07-24 15:25:17 +0000
@@ -6,3 +6,4 @@
6 - contrib.hahelpers.cluster6 - contrib.hahelpers.cluster
7 - contrib.peerstorage7 - contrib.peerstorage
8 - payload.execd8 - payload.execd
9 - contrib.network.ip
910
=== modified file 'config.yaml'
--- config.yaml 2014-01-13 16:07:19 +0000
+++ config.yaml 2014-07-24 15:25:17 +0000
@@ -1,37 +1,46 @@
1options:1options:
2 source:2 source:
3 type: string3 type: string
4 description: Package install location for Percona XtraDB Cluster (defaults to distro for >= 14.04)4 description: Package install location for Percona XtraDB Cluster (defaults to distro for >= 14.04)
5 dataset-size:5 dataset-size:
6 default: '80%'6 default: '80%'
7 type: string7 type: string
8 description: How much data do you want to keep in memory in the DB. This will be used to tune settings in the database server appropriately. Suffix this value with 'K','M','G', or 'T' to get the relevant kilo/mega/etc. bytes. If suffixed with %, one will get that percentage of RAM devoted to dataset.8 description: How much data do you want to keep in memory in the DB. This will be used to tune settings in the database server appropriately. Suffix this value with 'K','M','G', or 'T' to get the relevant kilo/mega/etc. bytes. If suffixed with %, one will get that percentage of RAM devoted to dataset.
9 max-connections:9 max-connections:
10 default: -110 default: -1
11 type: int11 type: int
12 description: Maximum connections to allow. -1 means use the server's compiled in default.12 description: Maximum connections to allow. -1 means use the server's compiled in default.
13 root-password:13 root-password:
14 type: string14 type: string
15 description: Root password for MySQL access; must be configured pre-deployment for Active-Active clusters.15 description: Root password for MySQL access; must be configured pre-deployment for Active-Active clusters.
16 sst-password:16 sst-password:
17 type: string17 type: string
18 description: Re-sync account password for new cluster nodes; must be configured pre-deployment for Active-Active clusters.18 description: Re-sync account password for new cluster nodes; must be configured pre-deployment for Active-Active clusters.
19 vip:19 vip:
20 type: string20 type: string
21 description: Virtual IP to use to front Percona XtraDB Cluster in active/active HA configuration21 description: Virtual IP to use to front Percona XtraDB Cluster in active/active HA configuration
22 vip_iface:22 vip_iface:
23 type: string23 type: string
24 default: eth024 default: eth0
25 description: Network interface on which to place the Virtual IP25 description: Network interface on which to place the Virtual IP
26 vip_cidr:26 vip_cidr:
27 type: int27 type: int
28 default: 2428 default: 24
29 description: Netmask that will be used for the Virtual IP29 description: Netmask that will be used for the Virtual IP
30 ha-bindiface:30 ha-bindiface:
31 type: string31 type: string
32 default: eth032 default: eth0
33 description: Default network interface on which HA cluster will bind to communication with the other members of the HA Cluster.33 description: Default network interface on which HA cluster will bind to communication with the other members of the HA Cluster.
34 ha-mcastport:34 ha-mcastport:
35 type: int35 type: int
36 default: 549036 default: 5490
37 description: Default multicast port number that will be used to communicate between HA Cluster nodes.37 description: Default multicast port number that will be used to communicate between HA Cluster nodes.
38 # Network configuration options
39 # by default all access is over 'private-address'
40 access-network:
41 type: string
42 description: |
43 The IP address and netmask of the 'access' network (e.g., 192.168.0.0/24)
44 .
45 This network will be used for access to database services.
46
3847
=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-03-07 10:20:46 +0000
+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-07-24 15:25:17 +0000
@@ -146,12 +146,12 @@
146 Obtains all relevant configuration from charm configuration required146 Obtains all relevant configuration from charm configuration required
147 for initiating a relation to hacluster:147 for initiating a relation to hacluster:
148148
149 ha-bindiface, ha-mcastport, vip, vip_iface, vip_cidr149 ha-bindiface, ha-mcastport, vip
150150
151 returns: dict: A dict containing settings keyed by setting name.151 returns: dict: A dict containing settings keyed by setting name.
152 raises: HAIncompleteConfig if settings are missing.152 raises: HAIncompleteConfig if settings are missing.
153 '''153 '''
154 settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'vip_iface', 'vip_cidr']154 settings = ['ha-bindiface', 'ha-mcastport', 'vip']
155 conf = {}155 conf = {}
156 for setting in settings:156 for setting in settings:
157 conf[setting] = config_get(setting)157 conf[setting] = config_get(setting)
@@ -170,6 +170,7 @@
170170
171 :configs : OSTemplateRenderer: A config tempating object to inspect for171 :configs : OSTemplateRenderer: A config tempating object to inspect for
172 a complete https context.172 a complete https context.
173
173 :vip_setting: str: Setting in charm config that specifies174 :vip_setting: str: Setting in charm config that specifies
174 VIP address.175 VIP address.
175 '''176 '''
176177
=== added directory 'hooks/charmhelpers/contrib/network'
=== added file 'hooks/charmhelpers/contrib/network/__init__.py'
=== added file 'hooks/charmhelpers/contrib/network/ip.py'
--- hooks/charmhelpers/contrib/network/ip.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/network/ip.py 2014-07-24 15:25:17 +0000
@@ -0,0 +1,156 @@
1import sys
2
3from functools import partial
4
5from charmhelpers.fetch import apt_install
6from charmhelpers.core.hookenv import (
7 ERROR, log,
8)
9
10try:
11 import netifaces
12except ImportError:
13 apt_install('python-netifaces')
14 import netifaces
15
16try:
17 import netaddr
18except ImportError:
19 apt_install('python-netaddr')
20 import netaddr
21
22
23def _validate_cidr(network):
24 try:
25 netaddr.IPNetwork(network)
26 except (netaddr.core.AddrFormatError, ValueError):
27 raise ValueError("Network (%s) is not in CIDR presentation format" %
28 network)
29
30
31def get_address_in_network(network, fallback=None, fatal=False):
32 """
33 Get an IPv4 or IPv6 address within the network from the host.
34
35 :param network (str): CIDR presentation format. For example,
36 '192.168.1.0/24'.
37 :param fallback (str): If no address is found, return fallback.
38 :param fatal (boolean): If no address is found, fallback is not
39 set and fatal is True then exit(1).
40
41 """
42
43 def not_found_error_out():
44 log("No IP address found in network: %s" % network,
45 level=ERROR)
46 sys.exit(1)
47
48 if network is None:
49 if fallback is not None:
50 return fallback
51 else:
52 if fatal:
53 not_found_error_out()
54
55 _validate_cidr(network)
56 network = netaddr.IPNetwork(network)
57 for iface in netifaces.interfaces():
58 addresses = netifaces.ifaddresses(iface)
59 if network.version == 4 and netifaces.AF_INET in addresses:
60 addr = addresses[netifaces.AF_INET][0]['addr']
61 netmask = addresses[netifaces.AF_INET][0]['netmask']
62 cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
63 if cidr in network:
64 return str(cidr.ip)
65 if network.version == 6 and netifaces.AF_INET6 in addresses:
66 for addr in addresses[netifaces.AF_INET6]:
67 if not addr['addr'].startswith('fe80'):
68 cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
69 addr['netmask']))
70 if cidr in network:
71 return str(cidr.ip)
72
73 if fallback is not None:
74 return fallback
75
76 if fatal:
77 not_found_error_out()
78
79 return None
80
81
82def is_ipv6(address):
83 '''Determine whether provided address is IPv6 or not'''
84 try:
85 address = netaddr.IPAddress(address)
86 except netaddr.AddrFormatError:
87 # probably a hostname - so not an address at all!
88 return False
89 else:
90 return address.version == 6
91
92
93def is_address_in_network(network, address):
94 """
95 Determine whether the provided address is within a network range.
96
97 :param network (str): CIDR presentation format. For example,
98 '192.168.1.0/24'.
99 :param address: An individual IPv4 or IPv6 address without a net
100 mask or subnet prefix. For example, '192.168.1.1'.
101 :returns boolean: Flag indicating whether address is in network.
102 """
103 try:
104 network = netaddr.IPNetwork(network)
105 except (netaddr.core.AddrFormatError, ValueError):
106 raise ValueError("Network (%s) is not in CIDR presentation format" %
107 network)
108 try:
109 address = netaddr.IPAddress(address)
110 except (netaddr.core.AddrFormatError, ValueError):
111 raise ValueError("Address (%s) is not in correct presentation format" %
112 address)
113 if address in network:
114 return True
115 else:
116 return False
117
118
119def _get_for_address(address, key):
120 """Retrieve an attribute of or the physical interface that
121 the IP address provided could be bound to.
122
123 :param address (str): An individual IPv4 or IPv6 address without a net
124 mask or subnet prefix. For example, '192.168.1.1'.
125 :param key: 'iface' for the physical interface name or an attribute
126 of the configured interface, for example 'netmask'.
127 :returns str: Requested attribute or None if address is not bindable.
128 """
129 address = netaddr.IPAddress(address)
130 for iface in netifaces.interfaces():
131 addresses = netifaces.ifaddresses(iface)
132 if address.version == 4 and netifaces.AF_INET in addresses:
133 addr = addresses[netifaces.AF_INET][0]['addr']
134 netmask = addresses[netifaces.AF_INET][0]['netmask']
135 cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
136 if address in cidr:
137 if key == 'iface':
138 return iface
139 else:
140 return addresses[netifaces.AF_INET][0][key]
141 if address.version == 6 and netifaces.AF_INET6 in addresses:
142 for addr in addresses[netifaces.AF_INET6]:
143 if not addr['addr'].startswith('fe80'):
144 cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
145 addr['netmask']))
146 if address in cidr:
147 if key == 'iface':
148 return iface
149 else:
150 return addr[key]
151 return None
152
153
154get_iface_for_address = partial(_get_for_address, key='iface')
155
156get_netmask_for_address = partial(_get_for_address, key='netmask')
0157
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2014-06-23 09:47:35 +0000
+++ hooks/charmhelpers/core/hookenv.py 2014-07-24 15:25:17 +0000
@@ -25,7 +25,7 @@
25def cached(func):25def cached(func):
26 """Cache return values for multiple executions of func + args26 """Cache return values for multiple executions of func + args
2727
28 For example:28 For example::
2929
30 @cached30 @cached
31 def unit_get(attribute):31 def unit_get(attribute):
@@ -445,18 +445,19 @@
445class Hooks(object):445class Hooks(object):
446 """A convenient handler for hook functions.446 """A convenient handler for hook functions.
447447
448 Example:448 Example::
449
449 hooks = Hooks()450 hooks = Hooks()
450451
451 # register a hook, taking its name from the function name452 # register a hook, taking its name from the function name
452 @hooks.hook()453 @hooks.hook()
453 def install():454 def install():
454 ...455 pass # your code here
455456
456 # register a hook, providing a custom hook name457 # register a hook, providing a custom hook name
457 @hooks.hook("config-changed")458 @hooks.hook("config-changed")
458 def config_changed():459 def config_changed():
459 ...460 pass # your code here
460461
461 if __name__ == "__main__":462 if __name__ == "__main__":
462 # execute a hook based on the name the program is called by463 # execute a hook based on the name the program is called by
463464
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2014-06-23 09:47:35 +0000
+++ hooks/charmhelpers/core/host.py 2014-07-24 15:25:17 +0000
@@ -211,13 +211,13 @@
211def restart_on_change(restart_map, stopstart=False):211def restart_on_change(restart_map, stopstart=False):
212 """Restart services based on configuration files changing212 """Restart services based on configuration files changing
213213
214 This function is used a decorator, for example214 This function is used a decorator, for example::
215215
216 @restart_on_change({216 @restart_on_change({
217 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]217 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
218 })218 })
219 def ceph_client_changed():219 def ceph_client_changed():
220 ...220 pass # your code here
221221
222 In this example, the cinder-api and cinder-volume services222 In this example, the cinder-api and cinder-volume services
223 would be restarted if /etc/ceph/ceph.conf is changed by the223 would be restarted if /etc/ceph/ceph.conf is changed by the
@@ -313,13 +313,19 @@
313313
314def cmp_pkgrevno(package, revno, pkgcache=None):314def cmp_pkgrevno(package, revno, pkgcache=None):
315 '''Compare supplied revno with the revno of the installed package315 '''Compare supplied revno with the revno of the installed package
316 1 => Installed revno is greater than supplied arg316
317 0 => Installed revno is the same as supplied arg317 * 1 => Installed revno is greater than supplied arg
318 -1 => Installed revno is less than supplied arg318 * 0 => Installed revno is the same as supplied arg
319 * -1 => Installed revno is less than supplied arg
320
319 '''321 '''
320 import apt_pkg322 import apt_pkg
321 if not pkgcache:323 if not pkgcache:
322 apt_pkg.init()324 apt_pkg.init()
325 # Force Apt to build its cache in memory. That way we avoid race
326 # conditions with other applications building the cache in the same
327 # place.
328 apt_pkg.config.set("Dir::Cache::pkgcache", "")
323 pkgcache = apt_pkg.Cache()329 pkgcache = apt_pkg.Cache()
324 pkg = pkgcache[package]330 pkg = pkgcache[package]
325 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)331 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
326332
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2014-06-23 09:47:35 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2014-07-24 15:25:17 +0000
@@ -235,31 +235,39 @@
235 sources_var='install_sources',235 sources_var='install_sources',
236 keys_var='install_keys'):236 keys_var='install_keys'):
237 """237 """
238 Configure multiple sources from charm configuration238 Configure multiple sources from charm configuration.
239
240 The lists are encoded as yaml fragments in the configuration.
241 The frament needs to be included as a string.
239242
240 Example config:243 Example config:
241 install_sources:244 install_sources: |
242 - "ppa:foo"245 - "ppa:foo"
243 - "http://example.com/repo precise main"246 - "http://example.com/repo precise main"
244 install_keys:247 install_keys: |
245 - null248 - null
246 - "a1b2c3d4"249 - "a1b2c3d4"
247250
248 Note that 'null' (a.k.a. None) should not be quoted.251 Note that 'null' (a.k.a. None) should not be quoted.
249 """252 """
250 sources = safe_load(config(sources_var))253 sources = safe_load((config(sources_var) or '').strip()) or []
251 keys = config(keys_var)254 keys = safe_load((config(keys_var) or '').strip()) or None
252 if keys is not None:255
253 keys = safe_load(keys)256 if isinstance(sources, basestring):
254 if isinstance(sources, basestring) and (257 sources = [sources]
255 keys is None or isinstance(keys, basestring)):258
256 add_source(sources, keys)259 if keys is None:
260 for source in sources:
261 add_source(source, None)
257 else:262 else:
258 if not len(sources) == len(keys):263 if isinstance(keys, basestring):
259 msg = 'Install sources and keys lists are different lengths'264 keys = [keys]
260 raise SourceConfigError(msg)265
261 for src_num in range(len(sources)):266 if len(sources) != len(keys):
262 add_source(sources[src_num], keys[src_num])267 raise SourceConfigError(
268 'Install sources and keys lists are different lengths')
269 for source, key in zip(sources, keys):
270 add_source(source, key)
263 if update:271 if update:
264 apt_update(fatal=True)272 apt_update(fatal=True)
265273
266274
=== modified file 'hooks/percona_hooks.py'
--- hooks/percona_hooks.py 2014-06-23 09:46:08 +0000
+++ hooks/percona_hooks.py 2014-07-24 15:25:17 +0000
@@ -54,6 +54,10 @@
54)54)
55from mysql import configure_db55from mysql import configure_db
56from charmhelpers.payload.execd import execd_preinstall56from charmhelpers.payload.execd import execd_preinstall
57from charmhelpers.contrib.network.ip import (
58 is_address_in_network,
59 get_address_in_network,
60)
5761
58hooks = Hooks()62hooks = Hooks()
5963
@@ -106,6 +110,10 @@
106 elif not clustered:110 elif not clustered:
107 # Restart with new configuration111 # Restart with new configuration
108 service_restart('mysql')112 service_restart('mysql')
113 # Notify any changes to the access network
114 for r_id in relation_ids('shared-db'):
115 for unit in related_units(r_id):
116 shared_db_changed(r_id, unit)
109117
110118
111@hooks.hook('cluster-relation-changed')119@hooks.hook('cluster-relation-changed')
@@ -142,11 +150,15 @@
142 database_name,150 database_name,
143 username,151 username,
144 admin=admin)152 admin=admin)
153
145 relation_set(relation_id=relation_id,154 relation_set(relation_id=relation_id,
146 database=database_name,155 relation_settings={
147 user=username,156 'user': username,
148 password=password,157 'password': password,
149 host=db_host)158 'host': db_host,
159 'database': database_name,
160 }
161 )
150162
151163
152# TODO: This could be a hook common between mysql and percona-cluster164# TODO: This could be a hook common between mysql and percona-cluster
@@ -164,6 +176,9 @@
164 db_host = config('vip')176 db_host = config('vip')
165 else:177 else:
166 db_host = unit_get('private-address')178 db_host = unit_get('private-address')
179
180 access_network = config('access-network')
181
167 singleset = set([182 singleset = set([
168 'database',183 'database',
169 'username',184 'username',
@@ -175,6 +190,10 @@
175 password = configure_db(settings['hostname'],190 password = configure_db(settings['hostname'],
176 settings['database'],191 settings['database'],
177 settings['username'])192 settings['username'])
193 if (access_network is not None and
194 is_address_in_network(access_network,
195 get_host_ip(settings['hostname']))):
196 db_host = get_address_in_network(access_network)
178 relation_set(relation_id=relation_id,197 relation_set(relation_id=relation_id,
179 db_host=db_host,198 db_host=db_host,
180 password=password)199 password=password)
@@ -211,11 +230,18 @@
211 configure_db(databases[db]['hostname'],230 configure_db(databases[db]['hostname'],
212 databases[db]['database'],231 databases[db]['database'],
213 databases[db]['username'])232 databases[db]['username'])
233 if (access_network is not None and
234 is_address_in_network(
235 access_network,
236 get_host_ip(databases[db]['hostname']))):
237 db_host = get_address_in_network(access_network)
214 if len(return_data) > 0:238 if len(return_data) > 0:
215 relation_set(relation_id=relation_id,239 relation_set(relation_id=relation_id,
216 **return_data)240 **return_data)
217 relation_set(relation_id=relation_id,241 relation_set(relation_id=relation_id,
218 db_host=db_host)242 db_host=db_host)
243 relation_set(relation_id=relation_id,
244 relation_settings={'access-network': access_network})
219245
220246
221@hooks.hook('ha-relation-joined')247@hooks.hook('ha-relation-joined')
222248
=== modified file 'hooks/percona_utils.py'
--- hooks/percona_utils.py 2014-03-07 11:04:44 +0000
+++ hooks/percona_utils.py 2014-07-24 15:25:17 +0000
@@ -93,7 +93,7 @@
93 for relid in relation_ids('cluster'):93 for relid in relation_ids('cluster'):
94 for unit in related_units(relid):94 for unit in related_units(relid):
95 hosts.append(get_host_ip(relation_get('private-address',95 hosts.append(get_host_ip(relation_get('private-address',
96 unit, relid)))96 unit, relid)))
97 return hosts97 return hosts
9898
99SQL_SST_USER_SETUP = "GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.*" \99SQL_SST_USER_SETUP = "GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.*" \

Subscribers

People subscribed via source and target branches

to status/vote changes: