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

Proposed by Edward Hope-Morley
Status: Merged
Approved by: Billy Olsen
Approved revision: 51
Merged at revision: 51
Proposed branch: lp:~hopem/charms/trusty/percona-cluster/next-fix-lp1425999
Merge into: lp:~openstack-charmers-archive/charms/trusty/percona-cluster/next
Diff against target: 316 lines (+138/-64)
3 files modified
hooks/charmhelpers/contrib/database/mysql.py (+42/-59)
hooks/charmhelpers/contrib/network/ip.py (+84/-1)
hooks/charmhelpers/core/services/helpers.py (+12/-4)
To merge this branch: bzr merge lp:~hopem/charms/trusty/percona-cluster/next-fix-lp1425999
Reviewer Review Type Date Requested Status
Billy Olsen Approve
Jorge Niedbalski (community) Approve
Review via email: mp+251237@code.launchpad.net

This proposal supersedes a proposal from 2015-02-26.

To post a comment you must log in.
Revision history for this message
Billy Olsen (billy-olsen) wrote : Posted in a previous version of this proposal

LGTM, Approved. Everything looks good to in my testing.

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

Reverted and resynced to incorporate get_host_ip() from charmhelpers

Revision history for this message
Jorge Niedbalski (niedbalski) wrote :

Thanks for the re-sync Ed.

LGTM +1

review: Approve
Revision history for this message
Billy Olsen (billy-olsen) wrote :

LGTM +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/charmhelpers/contrib/database/mysql.py'
2--- hooks/charmhelpers/contrib/database/mysql.py 2015-02-27 09:43:12 +0000
3+++ hooks/charmhelpers/contrib/database/mysql.py 2015-02-27 10:21:53 +0000
4@@ -15,14 +15,15 @@
5 write_file
6 )
7 from charmhelpers.core.hookenv import (
8+ config as config_get,
9 relation_get,
10 related_units,
11 unit_get,
12 log,
13 DEBUG,
14 INFO,
15+ WARNING,
16 )
17-from charmhelpers.core.hookenv import config as config_get
18 from charmhelpers.fetch import (
19 apt_install,
20 apt_update,
21@@ -32,6 +33,7 @@
22 peer_store,
23 peer_retrieve,
24 )
25+from charmhelpers.contrib.network.ip import get_host_ip
26
27 try:
28 import MySQLdb
29@@ -220,6 +222,18 @@
30 """Retrieve or generate mysql root password for service units."""
31 return self.get_mysql_password(username=None, password=password)
32
33+ def normalize_address(self, hostname):
34+ """Ensure that address returned is an IP address (i.e. not fqdn)"""
35+ if config_get('prefer-ipv6'):
36+ # TODO: add support for ipv6 dns
37+ return hostname
38+
39+ if hostname != unit_get('private-address'):
40+ return get_host_ip(hostname, fallback=hostname)
41+
42+ # Otherwise assume localhost
43+ return '127.0.0.1'
44+
45 def get_allowed_units(self, database, username, relation_id=None):
46 """Get list of units with access grants for database with username.
47
48@@ -247,6 +261,7 @@
49
50 if hosts:
51 for host in hosts:
52+ host = self.normalize_address(host)
53 if self.grant_exists(database, username, host):
54 log("Grant exists for host '%s' on db '%s'" %
55 (host, database), level=DEBUG)
56@@ -262,21 +277,11 @@
57
58 def configure_db(self, hostname, database, username, admin=False):
59 """Configure access to database for username from hostname."""
60- if config_get('prefer-ipv6'):
61- remote_ip = hostname
62- elif hostname != unit_get('private-address'):
63- try:
64- remote_ip = socket.gethostbyname(hostname)
65- except Exception:
66- # socket.gethostbyname doesn't support ipv6
67- remote_ip = hostname
68- else:
69- remote_ip = '127.0.0.1'
70-
71 self.connect(password=self.get_mysql_root_password())
72 if not self.database_exists(database):
73 self.create_database(database)
74
75+ remote_ip = self.normalize_address(hostname)
76 password = self.get_mysql_password(username)
77 if not self.grant_exists(database, username, remote_ip):
78 if not admin:
79@@ -289,9 +294,11 @@
80
81 class PerconaClusterHelper(object):
82
83- # Going for the biggest page size to avoid wasted bytes. InnoDB page size is
84- # 16MB
85+ # Going for the biggest page size to avoid wasted bytes.
86+ # InnoDB page size is 16MB
87+
88 DEFAULT_PAGE_SIZE = 16 * 1024 * 1024
89+ DEFAULT_INNODB_BUFFER_FACTOR = 0.50
90
91 def human_to_bytes(self, human):
92 """Convert human readable configuration options to bytes."""
93@@ -352,51 +359,27 @@
94 if 'max-connections' in config:
95 mysql_config['max_connections'] = config['max-connections']
96
97- # Total memory available for dataset
98- dataset_bytes = self.human_to_bytes(config['dataset-size'])
99- mysql_config['dataset_bytes'] = dataset_bytes
100-
101- if 'query-cache-type' in config:
102- # Query Cache Configuration
103- mysql_config['query_cache_size'] = config['query-cache-size']
104- if (config['query-cache-size'] == -1 and
105- config['query-cache-type'] in ['ON', 'DEMAND']):
106- # Calculate the query cache size automatically
107- qcache_bytes = (dataset_bytes * 0.20)
108- qcache_bytes = int(qcache_bytes -
109- (qcache_bytes % self.DEFAULT_PAGE_SIZE))
110- mysql_config['query_cache_size'] = qcache_bytes
111- dataset_bytes -= qcache_bytes
112-
113- # 5.5 allows the words, but not 5.1
114- if config['query-cache-type'] == 'ON':
115- mysql_config['query_cache_type'] = 1
116- elif config['query-cache-type'] == 'DEMAND':
117- mysql_config['query_cache_type'] = 2
118- else:
119- mysql_config['query_cache_type'] = 0
120-
121 # Set a sane default key_buffer size
122 mysql_config['key_buffer'] = self.human_to_bytes('32M')
123-
124- if 'preferred-storage-engine' in config:
125- # Storage engine configuration
126- preferred_engines = config['preferred-storage-engine'].split(',')
127- chunk_size = int(dataset_bytes / len(preferred_engines))
128- mysql_config['innodb_flush_log_at_trx_commit'] = 1
129- mysql_config['sync_binlog'] = 1
130- if 'InnoDB' in preferred_engines:
131- mysql_config['innodb_buffer_pool_size'] = chunk_size
132- if config['tuning-level'] == 'fast':
133- mysql_config['innodb_flush_log_at_trx_commit'] = 2
134- else:
135- mysql_config['innodb_buffer_pool_size'] = 0
136-
137- mysql_config['default_storage_engine'] = preferred_engines[0]
138- if 'MyISAM' in preferred_engines:
139- mysql_config['key_buffer'] = chunk_size
140-
141- if config['tuning-level'] == 'fast':
142- mysql_config['sync_binlog'] = 0
143-
144+ total_memory = self.human_to_bytes(self.get_mem_total())
145+
146+ log("Option 'dataset-size' has been deprecated, instead by default %d%% of system \
147+ available RAM will be used for innodb_buffer_pool_size allocation" %
148+ (self.DEFAULT_INNODB_BUFFER_FACTOR * 100), level="WARN")
149+
150+ innodb_buffer_pool_size = config.get('innodb-buffer-pool-size', None)
151+
152+ if innodb_buffer_pool_size:
153+ innodb_buffer_pool_size = self.human_to_bytes(
154+ innodb_buffer_pool_size)
155+
156+ if innodb_buffer_pool_size > total_memory:
157+ log("innodb_buffer_pool_size; {} is greater than system available memory:{}".format(
158+ innodb_buffer_pool_size,
159+ total_memory), level='WARN')
160+ else:
161+ innodb_buffer_pool_size = int(
162+ total_memory * self.DEFAULT_INNODB_BUFFER_FACTOR)
163+
164+ mysql_config['innodb_buffer_pool_size'] = innodb_buffer_pool_size
165 return mysql_config
166
167=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
168--- hooks/charmhelpers/contrib/network/ip.py 2015-02-04 18:56:00 +0000
169+++ hooks/charmhelpers/contrib/network/ip.py 2015-02-27 10:21:53 +0000
170@@ -17,13 +17,16 @@
171 import glob
172 import re
173 import subprocess
174+import six
175+import socket
176
177 from functools import partial
178
179 from charmhelpers.core.hookenv import unit_get
180 from charmhelpers.fetch import apt_install
181 from charmhelpers.core.hookenv import (
182- log
183+ log,
184+ WARNING,
185 )
186
187 try:
188@@ -365,3 +368,83 @@
189 return True
190
191 return False
192+
193+
194+def is_ip(address):
195+ """
196+ Returns True if address is a valid IP address.
197+ """
198+ try:
199+ # Test to see if already an IPv4 address
200+ socket.inet_aton(address)
201+ return True
202+ except socket.error:
203+ return False
204+
205+
206+def ns_query(address):
207+ try:
208+ import dns.resolver
209+ except ImportError:
210+ apt_install('python-dnspython')
211+ import dns.resolver
212+
213+ if isinstance(address, dns.name.Name):
214+ rtype = 'PTR'
215+ elif isinstance(address, six.string_types):
216+ rtype = 'A'
217+ else:
218+ return None
219+
220+ answers = dns.resolver.query(address, rtype)
221+ if answers:
222+ return str(answers[0])
223+ return None
224+
225+
226+def get_host_ip(hostname, fallback=None):
227+ """
228+ Resolves the IP for a given hostname, or returns
229+ the input if it is already an IP.
230+ """
231+ if is_ip(hostname):
232+ return hostname
233+
234+ ip_addr = ns_query(hostname)
235+ if not ip_addr:
236+ try:
237+ ip_addr = socket.gethostbyname(hostname)
238+ except:
239+ log("Failed to resolve hostname '%s'" % (hostname),
240+ level=WARNING)
241+ return fallback
242+ return ip_addr
243+
244+
245+def get_hostname(address, fqdn=True):
246+ """
247+ Resolves hostname for given IP, or returns the input
248+ if it is already a hostname.
249+ """
250+ if is_ip(address):
251+ try:
252+ import dns.reversename
253+ except ImportError:
254+ apt_install("python-dnspython")
255+ import dns.reversename
256+
257+ rev = dns.reversename.from_address(address)
258+ result = ns_query(rev)
259+ if not result:
260+ return None
261+ else:
262+ result = address
263+
264+ if fqdn:
265+ # strip trailing .
266+ if result.endswith('.'):
267+ return result[:-1]
268+ else:
269+ return result
270+ else:
271+ return result.split('.')[0]
272
273=== modified file 'hooks/charmhelpers/core/services/helpers.py'
274--- hooks/charmhelpers/core/services/helpers.py 2015-02-04 18:56:00 +0000
275+++ hooks/charmhelpers/core/services/helpers.py 2015-02-27 10:21:53 +0000
276@@ -45,12 +45,14 @@
277 """
278 name = None
279 interface = None
280- required_keys = []
281
282 def __init__(self, name=None, additional_required_keys=None):
283+ if not hasattr(self, 'required_keys'):
284+ self.required_keys = []
285+
286 if name is not None:
287 self.name = name
288- if additional_required_keys is not None:
289+ if additional_required_keys:
290 self.required_keys.extend(additional_required_keys)
291 self.get_data()
292
293@@ -134,7 +136,10 @@
294 """
295 name = 'db'
296 interface = 'mysql'
297- required_keys = ['host', 'user', 'password', 'database']
298+
299+ def __init__(self, *args, **kwargs):
300+ self.required_keys = ['host', 'user', 'password', 'database']
301+ super(HttpRelation).__init__(self, *args, **kwargs)
302
303
304 class HttpRelation(RelationContext):
305@@ -146,7 +151,10 @@
306 """
307 name = 'website'
308 interface = 'http'
309- required_keys = ['host', 'port']
310+
311+ def __init__(self, *args, **kwargs):
312+ self.required_keys = ['host', 'port']
313+ super(HttpRelation).__init__(self, *args, **kwargs)
314
315 def provide_data(self):
316 return {

Subscribers

People subscribed via source and target branches