Merge lp:~hopem/charms/trusty/mysql/contrib-database-mysql into lp:charms/trusty/mysql
- Trusty Tahr (14.04)
- contrib-database-mysql
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 137 |
Proposed branch: | lp:~hopem/charms/trusty/mysql/contrib-database-mysql |
Merge into: | lp:charms/trusty/mysql |
Diff against target: |
1602 lines (+1001/-191) 24 files modified
charm-helpers.yaml (+2/-0) hooks/charmhelpers/__init__.py (+16/-0) hooks/charmhelpers/contrib/__init__.py (+15/-0) hooks/charmhelpers/contrib/database/mysql.py (+372/-0) hooks/charmhelpers/contrib/network/__init__.py (+15/-0) hooks/charmhelpers/contrib/network/ip.py (+16/-0) hooks/charmhelpers/contrib/peerstorage/__init__.py (+148/-0) hooks/charmhelpers/core/__init__.py (+15/-0) hooks/charmhelpers/core/decorators.py (+57/-0) hooks/charmhelpers/core/fstab.py (+16/-0) hooks/charmhelpers/core/hookenv.py (+32/-4) hooks/charmhelpers/core/host.py (+59/-9) hooks/charmhelpers/core/services/__init__.py (+16/-0) hooks/charmhelpers/core/services/base.py (+16/-0) hooks/charmhelpers/core/services/helpers.py (+16/-0) hooks/charmhelpers/core/sysctl.py (+27/-5) hooks/charmhelpers/core/templating.py (+19/-3) hooks/charmhelpers/fetch/__init__.py (+24/-1) hooks/charmhelpers/fetch/archiveurl.py (+16/-0) hooks/charmhelpers/fetch/bzrurl.py (+25/-1) hooks/charmhelpers/fetch/giturl.py (+26/-3) hooks/common.py (+8/-59) hooks/ha_relations.py (+10/-0) hooks/shared_db_relations.py (+35/-106) |
To merge this branch: | bzr merge lp:~hopem/charms/trusty/mysql/contrib-database-mysql |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Cory Johns (community) | Approve | ||
Review Queue (community) | automated testing | Needs Fixing | |
Review via email: mp+248743@code.launchpad.net |
Commit message
Description of the change
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #1732 mysql for hopem mp248743
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #1663 mysql for hopem mp248743
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://
Build: http://
- 146. By Edward Hope-Morley
-
fix charm-helpers yaml
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #1834 mysql for hopem mp248743
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #1662 mysql for hopem mp248743
UNIT FAIL: unit-test missing
UNIT Results (max last 2 lines):
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.
Full unit test output: http://
Build: http://
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #1853 mysql for hopem mp248743
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://
Build: http://
Review Queue (review-queue) wrote : | # |
This items has failed automated testing! Results available here http://
Cory Johns (johnsca) wrote : | # |
The automated test failure is only for Azure and seems spurious. I manually tested this relation with cs:trusty/lamp and it seems fine. +1
Preview Diff
1 | === modified file 'charm-helpers.yaml' | |||
2 | --- charm-helpers.yaml 2014-12-01 17:17:12 +0000 | |||
3 | +++ charm-helpers.yaml 2015-02-10 11:19:03 +0000 | |||
4 | @@ -5,3 +5,5 @@ | |||
5 | 5 | - core | 5 | - core |
6 | 6 | - fetch | 6 | - fetch |
7 | 7 | - contrib.network.ip | 7 | - contrib.network.ip |
8 | 8 | - contrib.database | ||
9 | 9 | - contrib.peerstorage | ||
10 | 8 | 10 | ||
11 | === modified file 'hooks/charmhelpers/__init__.py' | |||
12 | --- hooks/charmhelpers/__init__.py 2014-12-01 17:17:12 +0000 | |||
13 | +++ hooks/charmhelpers/__init__.py 2015-02-10 11:19:03 +0000 | |||
14 | @@ -1,3 +1,19 @@ | |||
15 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
16 | 2 | # | ||
17 | 3 | # This file is part of charm-helpers. | ||
18 | 4 | # | ||
19 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
20 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
21 | 7 | # published by the Free Software Foundation. | ||
22 | 8 | # | ||
23 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
24 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
25 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
26 | 12 | # GNU Lesser General Public License for more details. | ||
27 | 13 | # | ||
28 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
29 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
30 | 16 | |||
31 | 1 | # Bootstrap charm-helpers, installing its dependencies if necessary using | 17 | # Bootstrap charm-helpers, installing its dependencies if necessary using |
32 | 2 | # only standard libraries. | 18 | # only standard libraries. |
33 | 3 | import subprocess | 19 | import subprocess |
34 | 4 | 20 | ||
35 | === modified file 'hooks/charmhelpers/contrib/__init__.py' | |||
36 | --- hooks/charmhelpers/contrib/__init__.py 2014-05-08 10:22:43 +0000 | |||
37 | +++ hooks/charmhelpers/contrib/__init__.py 2015-02-10 11:19:03 +0000 | |||
38 | @@ -0,0 +1,15 @@ | |||
39 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
40 | 2 | # | ||
41 | 3 | # This file is part of charm-helpers. | ||
42 | 4 | # | ||
43 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
44 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
45 | 7 | # published by the Free Software Foundation. | ||
46 | 8 | # | ||
47 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
48 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
49 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
50 | 12 | # GNU Lesser General Public License for more details. | ||
51 | 13 | # | ||
52 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
53 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
54 | 0 | 16 | ||
55 | === added directory 'hooks/charmhelpers/contrib/database' | |||
56 | === added file 'hooks/charmhelpers/contrib/database/__init__.py' | |||
57 | === added file 'hooks/charmhelpers/contrib/database/mysql.py' | |||
58 | --- hooks/charmhelpers/contrib/database/mysql.py 1970-01-01 00:00:00 +0000 | |||
59 | +++ hooks/charmhelpers/contrib/database/mysql.py 2015-02-10 11:19:03 +0000 | |||
60 | @@ -0,0 +1,372 @@ | |||
61 | 1 | """Helper for working with a MySQL database""" | ||
62 | 2 | import json | ||
63 | 3 | import socket | ||
64 | 4 | import re | ||
65 | 5 | import sys | ||
66 | 6 | import platform | ||
67 | 7 | import os | ||
68 | 8 | import glob | ||
69 | 9 | |||
70 | 10 | from string import upper | ||
71 | 11 | |||
72 | 12 | from charmhelpers.core.host import ( | ||
73 | 13 | mkdir, | ||
74 | 14 | pwgen, | ||
75 | 15 | write_file | ||
76 | 16 | ) | ||
77 | 17 | from charmhelpers.core.hookenv import ( | ||
78 | 18 | relation_get, | ||
79 | 19 | related_units, | ||
80 | 20 | unit_get, | ||
81 | 21 | log, | ||
82 | 22 | DEBUG, | ||
83 | 23 | INFO, | ||
84 | 24 | ) | ||
85 | 25 | from charmhelpers.core.hookenv import config as config_get | ||
86 | 26 | from charmhelpers.fetch import ( | ||
87 | 27 | apt_install, | ||
88 | 28 | apt_update, | ||
89 | 29 | filter_installed_packages, | ||
90 | 30 | ) | ||
91 | 31 | from charmhelpers.contrib.peerstorage import ( | ||
92 | 32 | peer_store, | ||
93 | 33 | peer_retrieve, | ||
94 | 34 | ) | ||
95 | 35 | |||
96 | 36 | try: | ||
97 | 37 | import MySQLdb | ||
98 | 38 | except ImportError: | ||
99 | 39 | apt_update(fatal=True) | ||
100 | 40 | apt_install(filter_installed_packages(['python-mysqldb']), fatal=True) | ||
101 | 41 | import MySQLdb | ||
102 | 42 | |||
103 | 43 | |||
104 | 44 | class MySQLHelper(object): | ||
105 | 45 | |||
106 | 46 | def __init__(self, rpasswdf_template, upasswdf_template, host='localhost'): | ||
107 | 47 | self.host = host | ||
108 | 48 | # Password file path templates | ||
109 | 49 | self.root_passwd_file_template = rpasswdf_template | ||
110 | 50 | self.user_passwd_file_template = upasswdf_template | ||
111 | 51 | |||
112 | 52 | def connect(self, user='root', password=None): | ||
113 | 53 | self.connection = MySQLdb.connect(user=user, host=self.host, | ||
114 | 54 | passwd=password) | ||
115 | 55 | |||
116 | 56 | def database_exists(self, db_name): | ||
117 | 57 | cursor = self.connection.cursor() | ||
118 | 58 | try: | ||
119 | 59 | cursor.execute("SHOW DATABASES") | ||
120 | 60 | databases = [i[0] for i in cursor.fetchall()] | ||
121 | 61 | finally: | ||
122 | 62 | cursor.close() | ||
123 | 63 | |||
124 | 64 | return db_name in databases | ||
125 | 65 | |||
126 | 66 | def create_database(self, db_name): | ||
127 | 67 | cursor = self.connection.cursor() | ||
128 | 68 | try: | ||
129 | 69 | cursor.execute("CREATE DATABASE {} CHARACTER SET UTF8" | ||
130 | 70 | .format(db_name)) | ||
131 | 71 | finally: | ||
132 | 72 | cursor.close() | ||
133 | 73 | |||
134 | 74 | def grant_exists(self, db_name, db_user, remote_ip): | ||
135 | 75 | cursor = self.connection.cursor() | ||
136 | 76 | priv_string = "GRANT ALL PRIVILEGES ON `{}`.* " \ | ||
137 | 77 | "TO '{}'@'{}'".format(db_name, db_user, remote_ip) | ||
138 | 78 | try: | ||
139 | 79 | cursor.execute("SHOW GRANTS for '{}'@'{}'".format(db_user, | ||
140 | 80 | remote_ip)) | ||
141 | 81 | grants = [i[0] for i in cursor.fetchall()] | ||
142 | 82 | except MySQLdb.OperationalError: | ||
143 | 83 | return False | ||
144 | 84 | finally: | ||
145 | 85 | cursor.close() | ||
146 | 86 | |||
147 | 87 | # TODO: review for different grants | ||
148 | 88 | return priv_string in grants | ||
149 | 89 | |||
150 | 90 | def create_grant(self, db_name, db_user, remote_ip, password): | ||
151 | 91 | cursor = self.connection.cursor() | ||
152 | 92 | try: | ||
153 | 93 | # TODO: review for different grants | ||
154 | 94 | cursor.execute("GRANT ALL PRIVILEGES ON {}.* TO '{}'@'{}' " | ||
155 | 95 | "IDENTIFIED BY '{}'".format(db_name, | ||
156 | 96 | db_user, | ||
157 | 97 | remote_ip, | ||
158 | 98 | password)) | ||
159 | 99 | finally: | ||
160 | 100 | cursor.close() | ||
161 | 101 | |||
162 | 102 | def create_admin_grant(self, db_user, remote_ip, password): | ||
163 | 103 | cursor = self.connection.cursor() | ||
164 | 104 | try: | ||
165 | 105 | cursor.execute("GRANT ALL PRIVILEGES ON *.* TO '{}'@'{}' " | ||
166 | 106 | "IDENTIFIED BY '{}'".format(db_user, | ||
167 | 107 | remote_ip, | ||
168 | 108 | password)) | ||
169 | 109 | finally: | ||
170 | 110 | cursor.close() | ||
171 | 111 | |||
172 | 112 | def cleanup_grant(self, db_user, remote_ip): | ||
173 | 113 | cursor = self.connection.cursor() | ||
174 | 114 | try: | ||
175 | 115 | cursor.execute("DROP FROM mysql.user WHERE user='{}' " | ||
176 | 116 | "AND HOST='{}'".format(db_user, | ||
177 | 117 | remote_ip)) | ||
178 | 118 | finally: | ||
179 | 119 | cursor.close() | ||
180 | 120 | |||
181 | 121 | def execute(self, sql): | ||
182 | 122 | """Execute arbitary SQL against the database.""" | ||
183 | 123 | cursor = self.connection.cursor() | ||
184 | 124 | try: | ||
185 | 125 | cursor.execute(sql) | ||
186 | 126 | finally: | ||
187 | 127 | cursor.close() | ||
188 | 128 | |||
189 | 129 | def migrate_passwords_to_peer_relation(self): | ||
190 | 130 | """Migrate any passwords storage on disk to cluster peer relation.""" | ||
191 | 131 | dirname = os.path.dirname(self.root_passwd_file_template) | ||
192 | 132 | path = os.path.join(dirname, '*.passwd') | ||
193 | 133 | for f in glob.glob(path): | ||
194 | 134 | _key = os.path.basename(f) | ||
195 | 135 | with open(f, 'r') as passwd: | ||
196 | 136 | _value = passwd.read().strip() | ||
197 | 137 | |||
198 | 138 | try: | ||
199 | 139 | peer_store(_key, _value) | ||
200 | 140 | os.unlink(f) | ||
201 | 141 | except ValueError: | ||
202 | 142 | # NOTE cluster relation not yet ready - skip for now | ||
203 | 143 | pass | ||
204 | 144 | |||
205 | 145 | def get_mysql_password_on_disk(self, username=None, password=None): | ||
206 | 146 | """Retrieve, generate or store a mysql password for the provided | ||
207 | 147 | username on disk.""" | ||
208 | 148 | if username: | ||
209 | 149 | template = self.user_passwd_file_template | ||
210 | 150 | passwd_file = template.format(username) | ||
211 | 151 | else: | ||
212 | 152 | passwd_file = self.root_passwd_file_template | ||
213 | 153 | |||
214 | 154 | _password = None | ||
215 | 155 | if os.path.exists(passwd_file): | ||
216 | 156 | with open(passwd_file, 'r') as passwd: | ||
217 | 157 | _password = passwd.read().strip() | ||
218 | 158 | else: | ||
219 | 159 | mkdir(os.path.dirname(passwd_file), owner='root', group='root', | ||
220 | 160 | perms=0o770) | ||
221 | 161 | # Force permissions - for some reason the chmod in makedirs fails | ||
222 | 162 | os.chmod(os.path.dirname(passwd_file), 0o770) | ||
223 | 163 | _password = password or pwgen(length=32) | ||
224 | 164 | write_file(passwd_file, _password, owner='root', group='root', | ||
225 | 165 | perms=0o660) | ||
226 | 166 | |||
227 | 167 | return _password | ||
228 | 168 | |||
229 | 169 | def get_mysql_password(self, username=None, password=None): | ||
230 | 170 | """Retrieve, generate or store a mysql password for the provided | ||
231 | 171 | username using peer relation cluster.""" | ||
232 | 172 | self.migrate_passwords_to_peer_relation() | ||
233 | 173 | if username: | ||
234 | 174 | _key = 'mysql-{}.passwd'.format(username) | ||
235 | 175 | else: | ||
236 | 176 | _key = 'mysql.passwd' | ||
237 | 177 | |||
238 | 178 | try: | ||
239 | 179 | _password = peer_retrieve(_key) | ||
240 | 180 | if _password is None: | ||
241 | 181 | _password = password or pwgen(length=32) | ||
242 | 182 | peer_store(_key, _password) | ||
243 | 183 | except ValueError: | ||
244 | 184 | # cluster relation is not yet started; use on-disk | ||
245 | 185 | _password = self.get_mysql_password_on_disk(username, password) | ||
246 | 186 | |||
247 | 187 | return _password | ||
248 | 188 | |||
249 | 189 | def get_mysql_root_password(self, password=None): | ||
250 | 190 | """Retrieve or generate mysql root password for service units.""" | ||
251 | 191 | return self.get_mysql_password(username=None, password=password) | ||
252 | 192 | |||
253 | 193 | def get_allowed_units(self, database, username, relation_id=None): | ||
254 | 194 | """Get list of units with access grants for database with username. | ||
255 | 195 | |||
256 | 196 | This is typically used to provide shared-db relations with a list of | ||
257 | 197 | which units have been granted access to the given database. | ||
258 | 198 | """ | ||
259 | 199 | self.connect(password=self.get_mysql_root_password()) | ||
260 | 200 | allowed_units = set() | ||
261 | 201 | for unit in related_units(relation_id): | ||
262 | 202 | settings = relation_get(rid=relation_id, unit=unit) | ||
263 | 203 | # First check for setting with prefix, then without | ||
264 | 204 | for attr in ["%s_hostname" % (database), 'hostname']: | ||
265 | 205 | hosts = settings.get(attr, None) | ||
266 | 206 | if hosts: | ||
267 | 207 | break | ||
268 | 208 | |||
269 | 209 | if hosts: | ||
270 | 210 | # hostname can be json-encoded list of hostnames | ||
271 | 211 | try: | ||
272 | 212 | hosts = json.loads(hosts) | ||
273 | 213 | except ValueError: | ||
274 | 214 | hosts = [hosts] | ||
275 | 215 | else: | ||
276 | 216 | hosts = [settings['private-address']] | ||
277 | 217 | |||
278 | 218 | if hosts: | ||
279 | 219 | for host in hosts: | ||
280 | 220 | if self.grant_exists(database, username, host): | ||
281 | 221 | log("Grant exists for host '%s' on db '%s'" % | ||
282 | 222 | (host, database), level=DEBUG) | ||
283 | 223 | if unit not in allowed_units: | ||
284 | 224 | allowed_units.add(unit) | ||
285 | 225 | else: | ||
286 | 226 | log("Grant does NOT exist for host '%s' on db '%s'" % | ||
287 | 227 | (host, database), level=DEBUG) | ||
288 | 228 | else: | ||
289 | 229 | log("No hosts found for grant check", level=INFO) | ||
290 | 230 | |||
291 | 231 | return allowed_units | ||
292 | 232 | |||
293 | 233 | def configure_db(self, hostname, database, username, admin=False): | ||
294 | 234 | """Configure access to database for username from hostname.""" | ||
295 | 235 | if config_get('prefer-ipv6'): | ||
296 | 236 | remote_ip = hostname | ||
297 | 237 | elif hostname != unit_get('private-address'): | ||
298 | 238 | try: | ||
299 | 239 | remote_ip = socket.gethostbyname(hostname) | ||
300 | 240 | except Exception: | ||
301 | 241 | # socket.gethostbyname doesn't support ipv6 | ||
302 | 242 | remote_ip = hostname | ||
303 | 243 | else: | ||
304 | 244 | remote_ip = '127.0.0.1' | ||
305 | 245 | |||
306 | 246 | self.connect(password=self.get_mysql_root_password()) | ||
307 | 247 | if not self.database_exists(database): | ||
308 | 248 | self.create_database(database) | ||
309 | 249 | |||
310 | 250 | password = self.get_mysql_password(username) | ||
311 | 251 | if not self.grant_exists(database, username, remote_ip): | ||
312 | 252 | if not admin: | ||
313 | 253 | self.create_grant(database, username, remote_ip, password) | ||
314 | 254 | else: | ||
315 | 255 | self.create_admin_grant(username, remote_ip, password) | ||
316 | 256 | |||
317 | 257 | return password | ||
318 | 258 | |||
319 | 259 | |||
320 | 260 | class PerconaClusterHelper(object): | ||
321 | 261 | |||
322 | 262 | # Going for the biggest page size to avoid wasted bytes. InnoDB page size is | ||
323 | 263 | # 16MB | ||
324 | 264 | DEFAULT_PAGE_SIZE = 16 * 1024 * 1024 | ||
325 | 265 | |||
326 | 266 | def human_to_bytes(self, human): | ||
327 | 267 | """Convert human readable configuration options to bytes.""" | ||
328 | 268 | num_re = re.compile('^[0-9]+$') | ||
329 | 269 | if num_re.match(human): | ||
330 | 270 | return human | ||
331 | 271 | |||
332 | 272 | factors = { | ||
333 | 273 | 'K': 1024, | ||
334 | 274 | 'M': 1048576, | ||
335 | 275 | 'G': 1073741824, | ||
336 | 276 | 'T': 1099511627776 | ||
337 | 277 | } | ||
338 | 278 | modifier = human[-1] | ||
339 | 279 | if modifier in factors: | ||
340 | 280 | return int(human[:-1]) * factors[modifier] | ||
341 | 281 | |||
342 | 282 | if modifier == '%': | ||
343 | 283 | total_ram = self.human_to_bytes(self.get_mem_total()) | ||
344 | 284 | if self.is_32bit_system() and total_ram > self.sys_mem_limit(): | ||
345 | 285 | total_ram = self.sys_mem_limit() | ||
346 | 286 | factor = int(human[:-1]) * 0.01 | ||
347 | 287 | pctram = total_ram * factor | ||
348 | 288 | return int(pctram - (pctram % self.DEFAULT_PAGE_SIZE)) | ||
349 | 289 | |||
350 | 290 | raise ValueError("Can only convert K,M,G, or T") | ||
351 | 291 | |||
352 | 292 | def is_32bit_system(self): | ||
353 | 293 | """Determine whether system is 32 or 64 bit.""" | ||
354 | 294 | try: | ||
355 | 295 | return sys.maxsize < 2 ** 32 | ||
356 | 296 | except OverflowError: | ||
357 | 297 | return False | ||
358 | 298 | |||
359 | 299 | def sys_mem_limit(self): | ||
360 | 300 | """Determine the default memory limit for the current service unit.""" | ||
361 | 301 | if platform.machine() in ['armv7l']: | ||
362 | 302 | _mem_limit = self.human_to_bytes('2700M') # experimentally determined | ||
363 | 303 | else: | ||
364 | 304 | # Limit for x86 based 32bit systems | ||
365 | 305 | _mem_limit = self.human_to_bytes('4G') | ||
366 | 306 | |||
367 | 307 | return _mem_limit | ||
368 | 308 | |||
369 | 309 | def get_mem_total(self): | ||
370 | 310 | """Calculate the total memory in the current service unit.""" | ||
371 | 311 | with open('/proc/meminfo') as meminfo_file: | ||
372 | 312 | for line in meminfo_file: | ||
373 | 313 | key, mem = line.split(':', 2) | ||
374 | 314 | if key == 'MemTotal': | ||
375 | 315 | mtot, modifier = mem.strip().split(' ') | ||
376 | 316 | return '%s%s' % (mtot, upper(modifier[0])) | ||
377 | 317 | |||
378 | 318 | def parse_config(self): | ||
379 | 319 | """Parse charm configuration and calculate values for config files.""" | ||
380 | 320 | config = config_get() | ||
381 | 321 | mysql_config = {} | ||
382 | 322 | if 'max-connections' in config: | ||
383 | 323 | mysql_config['max_connections'] = config['max-connections'] | ||
384 | 324 | |||
385 | 325 | # Total memory available for dataset | ||
386 | 326 | dataset_bytes = self.human_to_bytes(config['dataset-size']) | ||
387 | 327 | mysql_config['dataset_bytes'] = dataset_bytes | ||
388 | 328 | |||
389 | 329 | if 'query-cache-type' in config: | ||
390 | 330 | # Query Cache Configuration | ||
391 | 331 | mysql_config['query_cache_size'] = config['query-cache-size'] | ||
392 | 332 | if (config['query-cache-size'] == -1 and | ||
393 | 333 | config['query-cache-type'] in ['ON', 'DEMAND']): | ||
394 | 334 | # Calculate the query cache size automatically | ||
395 | 335 | qcache_bytes = (dataset_bytes * 0.20) | ||
396 | 336 | qcache_bytes = int(qcache_bytes - | ||
397 | 337 | (qcache_bytes % self.DEFAULT_PAGE_SIZE)) | ||
398 | 338 | mysql_config['query_cache_size'] = qcache_bytes | ||
399 | 339 | dataset_bytes -= qcache_bytes | ||
400 | 340 | |||
401 | 341 | # 5.5 allows the words, but not 5.1 | ||
402 | 342 | if config['query-cache-type'] == 'ON': | ||
403 | 343 | mysql_config['query_cache_type'] = 1 | ||
404 | 344 | elif config['query-cache-type'] == 'DEMAND': | ||
405 | 345 | mysql_config['query_cache_type'] = 2 | ||
406 | 346 | else: | ||
407 | 347 | mysql_config['query_cache_type'] = 0 | ||
408 | 348 | |||
409 | 349 | # Set a sane default key_buffer size | ||
410 | 350 | mysql_config['key_buffer'] = self.human_to_bytes('32M') | ||
411 | 351 | |||
412 | 352 | if 'preferred-storage-engine' in config: | ||
413 | 353 | # Storage engine configuration | ||
414 | 354 | preferred_engines = config['preferred-storage-engine'].split(',') | ||
415 | 355 | chunk_size = int(dataset_bytes / len(preferred_engines)) | ||
416 | 356 | mysql_config['innodb_flush_log_at_trx_commit'] = 1 | ||
417 | 357 | mysql_config['sync_binlog'] = 1 | ||
418 | 358 | if 'InnoDB' in preferred_engines: | ||
419 | 359 | mysql_config['innodb_buffer_pool_size'] = chunk_size | ||
420 | 360 | if config['tuning-level'] == 'fast': | ||
421 | 361 | mysql_config['innodb_flush_log_at_trx_commit'] = 2 | ||
422 | 362 | else: | ||
423 | 363 | mysql_config['innodb_buffer_pool_size'] = 0 | ||
424 | 364 | |||
425 | 365 | mysql_config['default_storage_engine'] = preferred_engines[0] | ||
426 | 366 | if 'MyISAM' in preferred_engines: | ||
427 | 367 | mysql_config['key_buffer'] = chunk_size | ||
428 | 368 | |||
429 | 369 | if config['tuning-level'] == 'fast': | ||
430 | 370 | mysql_config['sync_binlog'] = 0 | ||
431 | 371 | |||
432 | 372 | return mysql_config | ||
433 | 0 | 373 | ||
434 | === modified file 'hooks/charmhelpers/contrib/network/__init__.py' | |||
435 | --- hooks/charmhelpers/contrib/network/__init__.py 2014-09-21 21:48:22 +0000 | |||
436 | +++ hooks/charmhelpers/contrib/network/__init__.py 2015-02-10 11:19:03 +0000 | |||
437 | @@ -0,0 +1,15 @@ | |||
438 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
439 | 2 | # | ||
440 | 3 | # This file is part of charm-helpers. | ||
441 | 4 | # | ||
442 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
443 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
444 | 7 | # published by the Free Software Foundation. | ||
445 | 8 | # | ||
446 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
447 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
448 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
449 | 12 | # GNU Lesser General Public License for more details. | ||
450 | 13 | # | ||
451 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
452 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
453 | 0 | 16 | ||
454 | === modified file 'hooks/charmhelpers/contrib/network/ip.py' | |||
455 | --- hooks/charmhelpers/contrib/network/ip.py 2014-11-26 12:31:33 +0000 | |||
456 | +++ hooks/charmhelpers/contrib/network/ip.py 2015-02-10 11:19:03 +0000 | |||
457 | @@ -1,3 +1,19 @@ | |||
458 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
459 | 2 | # | ||
460 | 3 | # This file is part of charm-helpers. | ||
461 | 4 | # | ||
462 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
463 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
464 | 7 | # published by the Free Software Foundation. | ||
465 | 8 | # | ||
466 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
467 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
468 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
469 | 12 | # GNU Lesser General Public License for more details. | ||
470 | 13 | # | ||
471 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
472 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
473 | 16 | |||
474 | 1 | import glob | 17 | import glob |
475 | 2 | import re | 18 | import re |
476 | 3 | import subprocess | 19 | import subprocess |
477 | 4 | 20 | ||
478 | === added directory 'hooks/charmhelpers/contrib/peerstorage' | |||
479 | === added file 'hooks/charmhelpers/contrib/peerstorage/__init__.py' | |||
480 | --- hooks/charmhelpers/contrib/peerstorage/__init__.py 1970-01-01 00:00:00 +0000 | |||
481 | +++ hooks/charmhelpers/contrib/peerstorage/__init__.py 2015-02-10 11:19:03 +0000 | |||
482 | @@ -0,0 +1,148 @@ | |||
483 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
484 | 2 | # | ||
485 | 3 | # This file is part of charm-helpers. | ||
486 | 4 | # | ||
487 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
488 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
489 | 7 | # published by the Free Software Foundation. | ||
490 | 8 | # | ||
491 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
492 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
493 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
494 | 12 | # GNU Lesser General Public License for more details. | ||
495 | 13 | # | ||
496 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
497 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
498 | 16 | |||
499 | 17 | import six | ||
500 | 18 | from charmhelpers.core.hookenv import relation_id as current_relation_id | ||
501 | 19 | from charmhelpers.core.hookenv import ( | ||
502 | 20 | is_relation_made, | ||
503 | 21 | relation_ids, | ||
504 | 22 | relation_get, | ||
505 | 23 | local_unit, | ||
506 | 24 | relation_set, | ||
507 | 25 | ) | ||
508 | 26 | |||
509 | 27 | |||
510 | 28 | """ | ||
511 | 29 | This helper provides functions to support use of a peer relation | ||
512 | 30 | for basic key/value storage, with the added benefit that all storage | ||
513 | 31 | can be replicated across peer units. | ||
514 | 32 | |||
515 | 33 | Requirement to use: | ||
516 | 34 | |||
517 | 35 | To use this, the "peer_echo()" method has to be called form the peer | ||
518 | 36 | relation's relation-changed hook: | ||
519 | 37 | |||
520 | 38 | @hooks.hook("cluster-relation-changed") # Adapt the to your peer relation name | ||
521 | 39 | def cluster_relation_changed(): | ||
522 | 40 | peer_echo() | ||
523 | 41 | |||
524 | 42 | Once this is done, you can use peer storage from anywhere: | ||
525 | 43 | |||
526 | 44 | @hooks.hook("some-hook") | ||
527 | 45 | def some_hook(): | ||
528 | 46 | # You can store and retrieve key/values this way: | ||
529 | 47 | if is_relation_made("cluster"): # from charmhelpers.core.hookenv | ||
530 | 48 | # There are peers available so we can work with peer storage | ||
531 | 49 | peer_store("mykey", "myvalue") | ||
532 | 50 | value = peer_retrieve("mykey") | ||
533 | 51 | print value | ||
534 | 52 | else: | ||
535 | 53 | print "No peers joind the relation, cannot share key/values :(" | ||
536 | 54 | """ | ||
537 | 55 | |||
538 | 56 | |||
539 | 57 | def peer_retrieve(key, relation_name='cluster'): | ||
540 | 58 | """Retrieve a named key from peer relation `relation_name`.""" | ||
541 | 59 | cluster_rels = relation_ids(relation_name) | ||
542 | 60 | if len(cluster_rels) > 0: | ||
543 | 61 | cluster_rid = cluster_rels[0] | ||
544 | 62 | return relation_get(attribute=key, rid=cluster_rid, | ||
545 | 63 | unit=local_unit()) | ||
546 | 64 | else: | ||
547 | 65 | raise ValueError('Unable to detect' | ||
548 | 66 | 'peer relation {}'.format(relation_name)) | ||
549 | 67 | |||
550 | 68 | |||
551 | 69 | def peer_retrieve_by_prefix(prefix, relation_name='cluster', delimiter='_', | ||
552 | 70 | inc_list=None, exc_list=None): | ||
553 | 71 | """ Retrieve k/v pairs given a prefix and filter using {inc,exc}_list """ | ||
554 | 72 | inc_list = inc_list if inc_list else [] | ||
555 | 73 | exc_list = exc_list if exc_list else [] | ||
556 | 74 | peerdb_settings = peer_retrieve('-', relation_name=relation_name) | ||
557 | 75 | matched = {} | ||
558 | 76 | for k, v in peerdb_settings.items(): | ||
559 | 77 | full_prefix = prefix + delimiter | ||
560 | 78 | if k.startswith(full_prefix): | ||
561 | 79 | new_key = k.replace(full_prefix, '') | ||
562 | 80 | if new_key in exc_list: | ||
563 | 81 | continue | ||
564 | 82 | if new_key in inc_list or len(inc_list) == 0: | ||
565 | 83 | matched[new_key] = v | ||
566 | 84 | return matched | ||
567 | 85 | |||
568 | 86 | |||
569 | 87 | def peer_store(key, value, relation_name='cluster'): | ||
570 | 88 | """Store the key/value pair on the named peer relation `relation_name`.""" | ||
571 | 89 | cluster_rels = relation_ids(relation_name) | ||
572 | 90 | if len(cluster_rels) > 0: | ||
573 | 91 | cluster_rid = cluster_rels[0] | ||
574 | 92 | relation_set(relation_id=cluster_rid, | ||
575 | 93 | relation_settings={key: value}) | ||
576 | 94 | else: | ||
577 | 95 | raise ValueError('Unable to detect ' | ||
578 | 96 | 'peer relation {}'.format(relation_name)) | ||
579 | 97 | |||
580 | 98 | |||
581 | 99 | def peer_echo(includes=None): | ||
582 | 100 | """Echo filtered attributes back onto the same relation for storage. | ||
583 | 101 | |||
584 | 102 | This is a requirement to use the peerstorage module - it needs to be called | ||
585 | 103 | from the peer relation's changed hook. | ||
586 | 104 | """ | ||
587 | 105 | rdata = relation_get() | ||
588 | 106 | echo_data = {} | ||
589 | 107 | if includes is None: | ||
590 | 108 | echo_data = rdata.copy() | ||
591 | 109 | for ex in ['private-address', 'public-address']: | ||
592 | 110 | if ex in echo_data: | ||
593 | 111 | echo_data.pop(ex) | ||
594 | 112 | else: | ||
595 | 113 | for attribute, value in six.iteritems(rdata): | ||
596 | 114 | for include in includes: | ||
597 | 115 | if include in attribute: | ||
598 | 116 | echo_data[attribute] = value | ||
599 | 117 | if len(echo_data) > 0: | ||
600 | 118 | relation_set(relation_settings=echo_data) | ||
601 | 119 | |||
602 | 120 | |||
603 | 121 | def peer_store_and_set(relation_id=None, peer_relation_name='cluster', | ||
604 | 122 | peer_store_fatal=False, relation_settings=None, | ||
605 | 123 | delimiter='_', **kwargs): | ||
606 | 124 | """Store passed-in arguments both in argument relation and in peer storage. | ||
607 | 125 | |||
608 | 126 | It functions like doing relation_set() and peer_store() at the same time, | ||
609 | 127 | with the same data. | ||
610 | 128 | |||
611 | 129 | @param relation_id: the id of the relation to store the data on. Defaults | ||
612 | 130 | to the current relation. | ||
613 | 131 | @param peer_store_fatal: Set to True, the function will raise an exception | ||
614 | 132 | should the peer sotrage not be avialable.""" | ||
615 | 133 | |||
616 | 134 | relation_settings = relation_settings if relation_settings else {} | ||
617 | 135 | relation_set(relation_id=relation_id, | ||
618 | 136 | relation_settings=relation_settings, | ||
619 | 137 | **kwargs) | ||
620 | 138 | if is_relation_made(peer_relation_name): | ||
621 | 139 | for key, value in six.iteritems(dict(list(kwargs.items()) + | ||
622 | 140 | list(relation_settings.items()))): | ||
623 | 141 | key_prefix = relation_id or current_relation_id() | ||
624 | 142 | peer_store(key_prefix + delimiter + key, | ||
625 | 143 | value, | ||
626 | 144 | relation_name=peer_relation_name) | ||
627 | 145 | else: | ||
628 | 146 | if peer_store_fatal: | ||
629 | 147 | raise ValueError('Unable to detect ' | ||
630 | 148 | 'peer relation {}'.format(peer_relation_name)) | ||
631 | 0 | 149 | ||
632 | === modified file 'hooks/charmhelpers/core/__init__.py' | |||
633 | --- hooks/charmhelpers/core/__init__.py 2014-02-19 14:49:31 +0000 | |||
634 | +++ hooks/charmhelpers/core/__init__.py 2015-02-10 11:19:03 +0000 | |||
635 | @@ -0,0 +1,15 @@ | |||
636 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
637 | 2 | # | ||
638 | 3 | # This file is part of charm-helpers. | ||
639 | 4 | # | ||
640 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
641 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
642 | 7 | # published by the Free Software Foundation. | ||
643 | 8 | # | ||
644 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
645 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
646 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
647 | 12 | # GNU Lesser General Public License for more details. | ||
648 | 13 | # | ||
649 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
650 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
651 | 0 | 16 | ||
652 | === added file 'hooks/charmhelpers/core/decorators.py' | |||
653 | --- hooks/charmhelpers/core/decorators.py 1970-01-01 00:00:00 +0000 | |||
654 | +++ hooks/charmhelpers/core/decorators.py 2015-02-10 11:19:03 +0000 | |||
655 | @@ -0,0 +1,57 @@ | |||
656 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
657 | 2 | # | ||
658 | 3 | # This file is part of charm-helpers. | ||
659 | 4 | # | ||
660 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
661 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
662 | 7 | # published by the Free Software Foundation. | ||
663 | 8 | # | ||
664 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
665 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
666 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
667 | 12 | # GNU Lesser General Public License for more details. | ||
668 | 13 | # | ||
669 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
670 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
671 | 16 | |||
672 | 17 | # | ||
673 | 18 | # Copyright 2014 Canonical Ltd. | ||
674 | 19 | # | ||
675 | 20 | # Authors: | ||
676 | 21 | # Edward Hope-Morley <opentastic@gmail.com> | ||
677 | 22 | # | ||
678 | 23 | |||
679 | 24 | import time | ||
680 | 25 | |||
681 | 26 | from charmhelpers.core.hookenv import ( | ||
682 | 27 | log, | ||
683 | 28 | INFO, | ||
684 | 29 | ) | ||
685 | 30 | |||
686 | 31 | |||
687 | 32 | def retry_on_exception(num_retries, base_delay=0, exc_type=Exception): | ||
688 | 33 | """If the decorated function raises exception exc_type, allow num_retries | ||
689 | 34 | retry attempts before raise the exception. | ||
690 | 35 | """ | ||
691 | 36 | def _retry_on_exception_inner_1(f): | ||
692 | 37 | def _retry_on_exception_inner_2(*args, **kwargs): | ||
693 | 38 | retries = num_retries | ||
694 | 39 | multiplier = 1 | ||
695 | 40 | while True: | ||
696 | 41 | try: | ||
697 | 42 | return f(*args, **kwargs) | ||
698 | 43 | except exc_type: | ||
699 | 44 | if not retries: | ||
700 | 45 | raise | ||
701 | 46 | |||
702 | 47 | delay = base_delay * multiplier | ||
703 | 48 | multiplier += 1 | ||
704 | 49 | log("Retrying '%s' %d more times (delay=%s)" % | ||
705 | 50 | (f.__name__, retries, delay), level=INFO) | ||
706 | 51 | retries -= 1 | ||
707 | 52 | if delay: | ||
708 | 53 | time.sleep(delay) | ||
709 | 54 | |||
710 | 55 | return _retry_on_exception_inner_2 | ||
711 | 56 | |||
712 | 57 | return _retry_on_exception_inner_1 | ||
713 | 0 | 58 | ||
714 | === modified file 'hooks/charmhelpers/core/fstab.py' | |||
715 | --- hooks/charmhelpers/core/fstab.py 2014-11-26 12:31:33 +0000 | |||
716 | +++ hooks/charmhelpers/core/fstab.py 2015-02-10 11:19:03 +0000 | |||
717 | @@ -1,6 +1,22 @@ | |||
718 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
719 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
720 | 3 | 3 | ||
721 | 4 | # Copyright 2014-2015 Canonical Limited. | ||
722 | 5 | # | ||
723 | 6 | # This file is part of charm-helpers. | ||
724 | 7 | # | ||
725 | 8 | # charm-helpers is free software: you can redistribute it and/or modify | ||
726 | 9 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
727 | 10 | # published by the Free Software Foundation. | ||
728 | 11 | # | ||
729 | 12 | # charm-helpers is distributed in the hope that it will be useful, | ||
730 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
731 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
732 | 15 | # GNU Lesser General Public License for more details. | ||
733 | 16 | # | ||
734 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
735 | 18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
736 | 19 | |||
737 | 4 | __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' | 20 | __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' |
738 | 5 | 21 | ||
739 | 6 | import io | 22 | import io |
740 | 7 | 23 | ||
741 | === modified file 'hooks/charmhelpers/core/hookenv.py' | |||
742 | --- hooks/charmhelpers/core/hookenv.py 2014-11-26 12:31:33 +0000 | |||
743 | +++ hooks/charmhelpers/core/hookenv.py 2015-02-10 11:19:03 +0000 | |||
744 | @@ -1,3 +1,19 @@ | |||
745 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
746 | 2 | # | ||
747 | 3 | # This file is part of charm-helpers. | ||
748 | 4 | # | ||
749 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
750 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
751 | 7 | # published by the Free Software Foundation. | ||
752 | 8 | # | ||
753 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
754 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
755 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
756 | 12 | # GNU Lesser General Public License for more details. | ||
757 | 13 | # | ||
758 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
759 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
760 | 16 | |||
761 | 1 | "Interactions with the Juju environment" | 17 | "Interactions with the Juju environment" |
762 | 2 | # Copyright 2013 Canonical Ltd. | 18 | # Copyright 2013 Canonical Ltd. |
763 | 3 | # | 19 | # |
764 | @@ -68,6 +84,8 @@ | |||
765 | 68 | command = ['juju-log'] | 84 | command = ['juju-log'] |
766 | 69 | if level: | 85 | if level: |
767 | 70 | command += ['-l', level] | 86 | command += ['-l', level] |
768 | 87 | if not isinstance(message, six.string_types): | ||
769 | 88 | message = repr(message) | ||
770 | 71 | command += [message] | 89 | command += [message] |
771 | 72 | subprocess.call(command) | 90 | subprocess.call(command) |
772 | 73 | 91 | ||
773 | @@ -394,21 +412,31 @@ | |||
774 | 394 | 412 | ||
775 | 395 | 413 | ||
776 | 396 | @cached | 414 | @cached |
777 | 415 | def metadata(): | ||
778 | 416 | """Get the current charm metadata.yaml contents as a python object""" | ||
779 | 417 | with open(os.path.join(charm_dir(), 'metadata.yaml')) as md: | ||
780 | 418 | return yaml.safe_load(md) | ||
781 | 419 | |||
782 | 420 | |||
783 | 421 | @cached | ||
784 | 397 | def relation_types(): | 422 | def relation_types(): |
785 | 398 | """Get a list of relation types supported by this charm""" | 423 | """Get a list of relation types supported by this charm""" |
786 | 399 | charmdir = os.environ.get('CHARM_DIR', '') | ||
787 | 400 | mdf = open(os.path.join(charmdir, 'metadata.yaml')) | ||
788 | 401 | md = yaml.safe_load(mdf) | ||
789 | 402 | rel_types = [] | 424 | rel_types = [] |
790 | 425 | md = metadata() | ||
791 | 403 | for key in ('provides', 'requires', 'peers'): | 426 | for key in ('provides', 'requires', 'peers'): |
792 | 404 | section = md.get(key) | 427 | section = md.get(key) |
793 | 405 | if section: | 428 | if section: |
794 | 406 | rel_types.extend(section.keys()) | 429 | rel_types.extend(section.keys()) |
795 | 407 | mdf.close() | ||
796 | 408 | return rel_types | 430 | return rel_types |
797 | 409 | 431 | ||
798 | 410 | 432 | ||
799 | 411 | @cached | 433 | @cached |
800 | 434 | def charm_name(): | ||
801 | 435 | """Get the name of the current charm as is specified on metadata.yaml""" | ||
802 | 436 | return metadata().get('name') | ||
803 | 437 | |||
804 | 438 | |||
805 | 439 | @cached | ||
806 | 412 | def relations(): | 440 | def relations(): |
807 | 413 | """Get a nested dictionary of relation data for all related units""" | 441 | """Get a nested dictionary of relation data for all related units""" |
808 | 414 | rels = {} | 442 | rels = {} |
809 | 415 | 443 | ||
810 | === modified file 'hooks/charmhelpers/core/host.py' | |||
811 | --- hooks/charmhelpers/core/host.py 2014-11-26 12:31:33 +0000 | |||
812 | +++ hooks/charmhelpers/core/host.py 2015-02-10 11:19:03 +0000 | |||
813 | @@ -1,3 +1,19 @@ | |||
814 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
815 | 2 | # | ||
816 | 3 | # This file is part of charm-helpers. | ||
817 | 4 | # | ||
818 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
819 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
820 | 7 | # published by the Free Software Foundation. | ||
821 | 8 | # | ||
822 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
823 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
824 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
825 | 12 | # GNU Lesser General Public License for more details. | ||
826 | 13 | # | ||
827 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
828 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
829 | 16 | |||
830 | 1 | """Tools for working with the host system""" | 17 | """Tools for working with the host system""" |
831 | 2 | # Copyright 2012 Canonical Ltd. | 18 | # Copyright 2012 Canonical Ltd. |
832 | 3 | # | 19 | # |
833 | @@ -101,6 +117,26 @@ | |||
834 | 101 | return user_info | 117 | return user_info |
835 | 102 | 118 | ||
836 | 103 | 119 | ||
837 | 120 | def add_group(group_name, system_group=False): | ||
838 | 121 | """Add a group to the system""" | ||
839 | 122 | try: | ||
840 | 123 | group_info = grp.getgrnam(group_name) | ||
841 | 124 | log('group {0} already exists!'.format(group_name)) | ||
842 | 125 | except KeyError: | ||
843 | 126 | log('creating group {0}'.format(group_name)) | ||
844 | 127 | cmd = ['addgroup'] | ||
845 | 128 | if system_group: | ||
846 | 129 | cmd.append('--system') | ||
847 | 130 | else: | ||
848 | 131 | cmd.extend([ | ||
849 | 132 | '--group', | ||
850 | 133 | ]) | ||
851 | 134 | cmd.append(group_name) | ||
852 | 135 | subprocess.check_call(cmd) | ||
853 | 136 | group_info = grp.getgrnam(group_name) | ||
854 | 137 | return group_info | ||
855 | 138 | |||
856 | 139 | |||
857 | 104 | def add_user_to_group(username, group): | 140 | def add_user_to_group(username, group): |
858 | 105 | """Add a user to a group""" | 141 | """Add a user to a group""" |
859 | 106 | cmd = [ | 142 | cmd = [ |
860 | @@ -142,21 +178,24 @@ | |||
861 | 142 | uid = pwd.getpwnam(owner).pw_uid | 178 | uid = pwd.getpwnam(owner).pw_uid |
862 | 143 | gid = grp.getgrnam(group).gr_gid | 179 | gid = grp.getgrnam(group).gr_gid |
863 | 144 | realpath = os.path.abspath(path) | 180 | realpath = os.path.abspath(path) |
866 | 145 | if os.path.exists(realpath): | 181 | path_exists = os.path.exists(realpath) |
867 | 146 | if force and not os.path.isdir(realpath): | 182 | if path_exists and force: |
868 | 183 | if not os.path.isdir(realpath): | ||
869 | 147 | log("Removing non-directory file {} prior to mkdir()".format(path)) | 184 | log("Removing non-directory file {} prior to mkdir()".format(path)) |
870 | 148 | os.unlink(realpath) | 185 | os.unlink(realpath) |
872 | 149 | else: | 186 | os.makedirs(realpath, perms) |
873 | 187 | elif not path_exists: | ||
874 | 150 | os.makedirs(realpath, perms) | 188 | os.makedirs(realpath, perms) |
875 | 151 | os.chown(realpath, uid, gid) | 189 | os.chown(realpath, uid, gid) |
876 | 190 | os.chmod(realpath, perms) | ||
877 | 152 | 191 | ||
878 | 153 | 192 | ||
879 | 154 | def write_file(path, content, owner='root', group='root', perms=0o444): | 193 | def write_file(path, content, owner='root', group='root', perms=0o444): |
881 | 155 | """Create or overwrite a file with the contents of a string""" | 194 | """Create or overwrite a file with the contents of a byte string.""" |
882 | 156 | log("Writing file {} {}:{} {:o}".format(path, owner, group, perms)) | 195 | log("Writing file {} {}:{} {:o}".format(path, owner, group, perms)) |
883 | 157 | uid = pwd.getpwnam(owner).pw_uid | 196 | uid = pwd.getpwnam(owner).pw_uid |
884 | 158 | gid = grp.getgrnam(group).gr_gid | 197 | gid = grp.getgrnam(group).gr_gid |
886 | 159 | with open(path, 'w') as target: | 198 | with open(path, 'wb') as target: |
887 | 160 | os.fchown(target.fileno(), uid, gid) | 199 | os.fchown(target.fileno(), uid, gid) |
888 | 161 | os.fchmod(target.fileno(), perms) | 200 | os.fchmod(target.fileno(), perms) |
889 | 162 | target.write(content) | 201 | target.write(content) |
890 | @@ -322,7 +361,7 @@ | |||
891 | 322 | ip_output = (line for line in ip_output if line) | 361 | ip_output = (line for line in ip_output if line) |
892 | 323 | for line in ip_output: | 362 | for line in ip_output: |
893 | 324 | if line.split()[1].startswith(int_type): | 363 | if line.split()[1].startswith(int_type): |
895 | 325 | matched = re.search('.*: (bond[0-9]+\.[0-9]+)@.*', line) | 364 | matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line) |
896 | 326 | if matched: | 365 | if matched: |
897 | 327 | interface = matched.groups()[0] | 366 | interface = matched.groups()[0] |
898 | 328 | else: | 367 | else: |
899 | @@ -366,10 +405,13 @@ | |||
900 | 366 | * 0 => Installed revno is the same as supplied arg | 405 | * 0 => Installed revno is the same as supplied arg |
901 | 367 | * -1 => Installed revno is less than supplied arg | 406 | * -1 => Installed revno is less than supplied arg |
902 | 368 | 407 | ||
903 | 408 | This function imports apt_cache function from charmhelpers.fetch if | ||
904 | 409 | the pkgcache argument is None. Be sure to add charmhelpers.fetch if | ||
905 | 410 | you call this function, or pass an apt_pkg.Cache() instance. | ||
906 | 369 | ''' | 411 | ''' |
907 | 370 | import apt_pkg | 412 | import apt_pkg |
908 | 371 | from charmhelpers.fetch import apt_cache | ||
909 | 372 | if not pkgcache: | 413 | if not pkgcache: |
910 | 414 | from charmhelpers.fetch import apt_cache | ||
911 | 373 | pkgcache = apt_cache() | 415 | pkgcache = apt_cache() |
912 | 374 | pkg = pkgcache[package] | 416 | pkg = pkgcache[package] |
913 | 375 | return apt_pkg.version_compare(pkg.current_ver.ver_str, revno) | 417 | return apt_pkg.version_compare(pkg.current_ver.ver_str, revno) |
914 | @@ -384,13 +426,21 @@ | |||
915 | 384 | os.chdir(cur) | 426 | os.chdir(cur) |
916 | 385 | 427 | ||
917 | 386 | 428 | ||
919 | 387 | def chownr(path, owner, group): | 429 | def chownr(path, owner, group, follow_links=True): |
920 | 388 | uid = pwd.getpwnam(owner).pw_uid | 430 | uid = pwd.getpwnam(owner).pw_uid |
921 | 389 | gid = grp.getgrnam(group).gr_gid | 431 | gid = grp.getgrnam(group).gr_gid |
922 | 432 | if follow_links: | ||
923 | 433 | chown = os.chown | ||
924 | 434 | else: | ||
925 | 435 | chown = os.lchown | ||
926 | 390 | 436 | ||
927 | 391 | for root, dirs, files in os.walk(path): | 437 | for root, dirs, files in os.walk(path): |
928 | 392 | for name in dirs + files: | 438 | for name in dirs + files: |
929 | 393 | full = os.path.join(root, name) | 439 | full = os.path.join(root, name) |
930 | 394 | broken_symlink = os.path.lexists(full) and not os.path.exists(full) | 440 | broken_symlink = os.path.lexists(full) and not os.path.exists(full) |
931 | 395 | if not broken_symlink: | 441 | if not broken_symlink: |
933 | 396 | os.chown(full, uid, gid) | 442 | chown(full, uid, gid) |
934 | 443 | |||
935 | 444 | |||
936 | 445 | def lchownr(path, owner, group): | ||
937 | 446 | chownr(path, owner, group, follow_links=False) | ||
938 | 397 | 447 | ||
939 | === modified file 'hooks/charmhelpers/core/services/__init__.py' | |||
940 | --- hooks/charmhelpers/core/services/__init__.py 2014-10-22 14:38:57 +0000 | |||
941 | +++ hooks/charmhelpers/core/services/__init__.py 2015-02-10 11:19:03 +0000 | |||
942 | @@ -1,2 +1,18 @@ | |||
943 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
944 | 2 | # | ||
945 | 3 | # This file is part of charm-helpers. | ||
946 | 4 | # | ||
947 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
948 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
949 | 7 | # published by the Free Software Foundation. | ||
950 | 8 | # | ||
951 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
952 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
953 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
954 | 12 | # GNU Lesser General Public License for more details. | ||
955 | 13 | # | ||
956 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
957 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
958 | 16 | |||
959 | 1 | from .base import * # NOQA | 17 | from .base import * # NOQA |
960 | 2 | from .helpers import * # NOQA | 18 | from .helpers import * # NOQA |
961 | 3 | 19 | ||
962 | === modified file 'hooks/charmhelpers/core/services/base.py' | |||
963 | --- hooks/charmhelpers/core/services/base.py 2014-09-26 07:17:08 +0000 | |||
964 | +++ hooks/charmhelpers/core/services/base.py 2015-02-10 11:19:03 +0000 | |||
965 | @@ -1,3 +1,19 @@ | |||
966 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
967 | 2 | # | ||
968 | 3 | # This file is part of charm-helpers. | ||
969 | 4 | # | ||
970 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
971 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
972 | 7 | # published by the Free Software Foundation. | ||
973 | 8 | # | ||
974 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
975 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
976 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
977 | 12 | # GNU Lesser General Public License for more details. | ||
978 | 13 | # | ||
979 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
980 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
981 | 16 | |||
982 | 1 | import os | 17 | import os |
983 | 2 | import re | 18 | import re |
984 | 3 | import json | 19 | import json |
985 | 4 | 20 | ||
986 | === modified file 'hooks/charmhelpers/core/services/helpers.py' | |||
987 | --- hooks/charmhelpers/core/services/helpers.py 2014-11-26 12:31:33 +0000 | |||
988 | +++ hooks/charmhelpers/core/services/helpers.py 2015-02-10 11:19:03 +0000 | |||
989 | @@ -1,3 +1,19 @@ | |||
990 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
991 | 2 | # | ||
992 | 3 | # This file is part of charm-helpers. | ||
993 | 4 | # | ||
994 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
995 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
996 | 7 | # published by the Free Software Foundation. | ||
997 | 8 | # | ||
998 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
999 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1000 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1001 | 12 | # GNU Lesser General Public License for more details. | ||
1002 | 13 | # | ||
1003 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1004 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1005 | 16 | |||
1006 | 1 | import os | 17 | import os |
1007 | 2 | import yaml | 18 | import yaml |
1008 | 3 | from charmhelpers.core import hookenv | 19 | from charmhelpers.core import hookenv |
1009 | 4 | 20 | ||
1010 | === modified file 'hooks/charmhelpers/core/sysctl.py' | |||
1011 | --- hooks/charmhelpers/core/sysctl.py 2014-10-09 10:30:08 +0000 | |||
1012 | +++ hooks/charmhelpers/core/sysctl.py 2015-02-10 11:19:03 +0000 | |||
1013 | @@ -1,6 +1,22 @@ | |||
1014 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
1015 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
1016 | 3 | 3 | ||
1017 | 4 | # Copyright 2014-2015 Canonical Limited. | ||
1018 | 5 | # | ||
1019 | 6 | # This file is part of charm-helpers. | ||
1020 | 7 | # | ||
1021 | 8 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1022 | 9 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1023 | 10 | # published by the Free Software Foundation. | ||
1024 | 11 | # | ||
1025 | 12 | # charm-helpers is distributed in the hope that it will be useful, | ||
1026 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1027 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1028 | 15 | # GNU Lesser General Public License for more details. | ||
1029 | 16 | # | ||
1030 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
1031 | 18 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1032 | 19 | |||
1033 | 4 | __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' | 20 | __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>' |
1034 | 5 | 21 | ||
1035 | 6 | import yaml | 22 | import yaml |
1036 | @@ -10,25 +26,31 @@ | |||
1037 | 10 | from charmhelpers.core.hookenv import ( | 26 | from charmhelpers.core.hookenv import ( |
1038 | 11 | log, | 27 | log, |
1039 | 12 | DEBUG, | 28 | DEBUG, |
1040 | 29 | ERROR, | ||
1041 | 13 | ) | 30 | ) |
1042 | 14 | 31 | ||
1043 | 15 | 32 | ||
1044 | 16 | def create(sysctl_dict, sysctl_file): | 33 | def create(sysctl_dict, sysctl_file): |
1045 | 17 | """Creates a sysctl.conf file from a YAML associative array | 34 | """Creates a sysctl.conf file from a YAML associative array |
1046 | 18 | 35 | ||
1049 | 19 | :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 } | 36 | :param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }" |
1050 | 20 | :type sysctl_dict: dict | 37 | :type sysctl_dict: str |
1051 | 21 | :param sysctl_file: path to the sysctl file to be saved | 38 | :param sysctl_file: path to the sysctl file to be saved |
1052 | 22 | :type sysctl_file: str or unicode | 39 | :type sysctl_file: str or unicode |
1053 | 23 | :returns: None | 40 | :returns: None |
1054 | 24 | """ | 41 | """ |
1056 | 25 | sysctl_dict = yaml.load(sysctl_dict) | 42 | try: |
1057 | 43 | sysctl_dict_parsed = yaml.safe_load(sysctl_dict) | ||
1058 | 44 | except yaml.YAMLError: | ||
1059 | 45 | log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict), | ||
1060 | 46 | level=ERROR) | ||
1061 | 47 | return | ||
1062 | 26 | 48 | ||
1063 | 27 | with open(sysctl_file, "w") as fd: | 49 | with open(sysctl_file, "w") as fd: |
1065 | 28 | for key, value in sysctl_dict.items(): | 50 | for key, value in sysctl_dict_parsed.items(): |
1066 | 29 | fd.write("{}={}\n".format(key, value)) | 51 | fd.write("{}={}\n".format(key, value)) |
1067 | 30 | 52 | ||
1069 | 31 | log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict), | 53 | log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed), |
1070 | 32 | level=DEBUG) | 54 | level=DEBUG) |
1071 | 33 | 55 | ||
1072 | 34 | check_call(["sysctl", "-p", sysctl_file]) | 56 | check_call(["sysctl", "-p", sysctl_file]) |
1073 | 35 | 57 | ||
1074 | === modified file 'hooks/charmhelpers/core/templating.py' | |||
1075 | --- hooks/charmhelpers/core/templating.py 2014-11-26 12:31:33 +0000 | |||
1076 | +++ hooks/charmhelpers/core/templating.py 2015-02-10 11:19:03 +0000 | |||
1077 | @@ -1,3 +1,19 @@ | |||
1078 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
1079 | 2 | # | ||
1080 | 3 | # This file is part of charm-helpers. | ||
1081 | 4 | # | ||
1082 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1083 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1084 | 7 | # published by the Free Software Foundation. | ||
1085 | 8 | # | ||
1086 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
1087 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1088 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1089 | 12 | # GNU Lesser General Public License for more details. | ||
1090 | 13 | # | ||
1091 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1092 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1093 | 16 | |||
1094 | 1 | import os | 17 | import os |
1095 | 2 | 18 | ||
1096 | 3 | from charmhelpers.core import host | 19 | from charmhelpers.core import host |
1097 | @@ -5,7 +21,7 @@ | |||
1098 | 5 | 21 | ||
1099 | 6 | 22 | ||
1100 | 7 | def render(source, target, context, owner='root', group='root', | 23 | def render(source, target, context, owner='root', group='root', |
1102 | 8 | perms=0o444, templates_dir=None): | 24 | perms=0o444, templates_dir=None, encoding='UTF-8'): |
1103 | 9 | """ | 25 | """ |
1104 | 10 | Render a template. | 26 | Render a template. |
1105 | 11 | 27 | ||
1106 | @@ -48,5 +64,5 @@ | |||
1107 | 48 | level=hookenv.ERROR) | 64 | level=hookenv.ERROR) |
1108 | 49 | raise e | 65 | raise e |
1109 | 50 | content = template.render(context) | 66 | content = template.render(context) |
1112 | 51 | host.mkdir(os.path.dirname(target)) | 67 | host.mkdir(os.path.dirname(target), owner, group, perms=0o755) |
1113 | 52 | host.write_file(target, content, owner, group, perms) | 68 | host.write_file(target, content.encode(encoding), owner, group, perms) |
1114 | 53 | 69 | ||
1115 | === modified file 'hooks/charmhelpers/fetch/__init__.py' | |||
1116 | --- hooks/charmhelpers/fetch/__init__.py 2014-11-26 12:31:33 +0000 | |||
1117 | +++ hooks/charmhelpers/fetch/__init__.py 2015-02-10 11:19:03 +0000 | |||
1118 | @@ -1,3 +1,19 @@ | |||
1119 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
1120 | 2 | # | ||
1121 | 3 | # This file is part of charm-helpers. | ||
1122 | 4 | # | ||
1123 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1124 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1125 | 7 | # published by the Free Software Foundation. | ||
1126 | 8 | # | ||
1127 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
1128 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1129 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1130 | 12 | # GNU Lesser General Public License for more details. | ||
1131 | 13 | # | ||
1132 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1133 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1134 | 16 | |||
1135 | 1 | import importlib | 17 | import importlib |
1136 | 2 | from tempfile import NamedTemporaryFile | 18 | from tempfile import NamedTemporaryFile |
1137 | 3 | import time | 19 | import time |
1138 | @@ -64,9 +80,16 @@ | |||
1139 | 64 | 'trusty-juno/updates': 'trusty-updates/juno', | 80 | 'trusty-juno/updates': 'trusty-updates/juno', |
1140 | 65 | 'trusty-updates/juno': 'trusty-updates/juno', | 81 | 'trusty-updates/juno': 'trusty-updates/juno', |
1141 | 66 | 'juno/proposed': 'trusty-proposed/juno', | 82 | 'juno/proposed': 'trusty-proposed/juno', |
1142 | 67 | 'juno/proposed': 'trusty-proposed/juno', | ||
1143 | 68 | 'trusty-juno/proposed': 'trusty-proposed/juno', | 83 | 'trusty-juno/proposed': 'trusty-proposed/juno', |
1144 | 69 | 'trusty-proposed/juno': 'trusty-proposed/juno', | 84 | 'trusty-proposed/juno': 'trusty-proposed/juno', |
1145 | 85 | # Kilo | ||
1146 | 86 | 'kilo': 'trusty-updates/kilo', | ||
1147 | 87 | 'trusty-kilo': 'trusty-updates/kilo', | ||
1148 | 88 | 'trusty-kilo/updates': 'trusty-updates/kilo', | ||
1149 | 89 | 'trusty-updates/kilo': 'trusty-updates/kilo', | ||
1150 | 90 | 'kilo/proposed': 'trusty-proposed/kilo', | ||
1151 | 91 | 'trusty-kilo/proposed': 'trusty-proposed/kilo', | ||
1152 | 92 | 'trusty-proposed/kilo': 'trusty-proposed/kilo', | ||
1153 | 70 | } | 93 | } |
1154 | 71 | 94 | ||
1155 | 72 | # The order of this list is very important. Handlers should be listed in from | 95 | # The order of this list is very important. Handlers should be listed in from |
1156 | 73 | 96 | ||
1157 | === modified file 'hooks/charmhelpers/fetch/archiveurl.py' | |||
1158 | --- hooks/charmhelpers/fetch/archiveurl.py 2014-11-26 12:31:33 +0000 | |||
1159 | +++ hooks/charmhelpers/fetch/archiveurl.py 2015-02-10 11:19:03 +0000 | |||
1160 | @@ -1,3 +1,19 @@ | |||
1161 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
1162 | 2 | # | ||
1163 | 3 | # This file is part of charm-helpers. | ||
1164 | 4 | # | ||
1165 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1166 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1167 | 7 | # published by the Free Software Foundation. | ||
1168 | 8 | # | ||
1169 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
1170 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1171 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1172 | 12 | # GNU Lesser General Public License for more details. | ||
1173 | 13 | # | ||
1174 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1175 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1176 | 16 | |||
1177 | 1 | import os | 17 | import os |
1178 | 2 | import hashlib | 18 | import hashlib |
1179 | 3 | import re | 19 | import re |
1180 | 4 | 20 | ||
1181 | === modified file 'hooks/charmhelpers/fetch/bzrurl.py' | |||
1182 | --- hooks/charmhelpers/fetch/bzrurl.py 2014-11-26 12:31:33 +0000 | |||
1183 | +++ hooks/charmhelpers/fetch/bzrurl.py 2015-02-10 11:19:03 +0000 | |||
1184 | @@ -1,3 +1,19 @@ | |||
1185 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
1186 | 2 | # | ||
1187 | 3 | # This file is part of charm-helpers. | ||
1188 | 4 | # | ||
1189 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1190 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1191 | 7 | # published by the Free Software Foundation. | ||
1192 | 8 | # | ||
1193 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
1194 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1195 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1196 | 12 | # GNU Lesser General Public License for more details. | ||
1197 | 13 | # | ||
1198 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1199 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1200 | 16 | |||
1201 | 1 | import os | 17 | import os |
1202 | 2 | from charmhelpers.fetch import ( | 18 | from charmhelpers.fetch import ( |
1203 | 3 | BaseFetchHandler, | 19 | BaseFetchHandler, |
1204 | @@ -11,10 +27,12 @@ | |||
1205 | 11 | 27 | ||
1206 | 12 | try: | 28 | try: |
1207 | 13 | from bzrlib.branch import Branch | 29 | from bzrlib.branch import Branch |
1208 | 30 | from bzrlib import bzrdir, workingtree, errors | ||
1209 | 14 | except ImportError: | 31 | except ImportError: |
1210 | 15 | from charmhelpers.fetch import apt_install | 32 | from charmhelpers.fetch import apt_install |
1211 | 16 | apt_install("python-bzrlib") | 33 | apt_install("python-bzrlib") |
1212 | 17 | from bzrlib.branch import Branch | 34 | from bzrlib.branch import Branch |
1213 | 35 | from bzrlib import bzrdir, workingtree, errors | ||
1214 | 18 | 36 | ||
1215 | 19 | 37 | ||
1216 | 20 | class BzrUrlFetchHandler(BaseFetchHandler): | 38 | class BzrUrlFetchHandler(BaseFetchHandler): |
1217 | @@ -35,8 +53,14 @@ | |||
1218 | 35 | from bzrlib.plugin import load_plugins | 53 | from bzrlib.plugin import load_plugins |
1219 | 36 | load_plugins() | 54 | load_plugins() |
1220 | 37 | try: | 55 | try: |
1221 | 56 | local_branch = bzrdir.BzrDir.create_branch_convenience(dest) | ||
1222 | 57 | except errors.AlreadyControlDirError: | ||
1223 | 58 | local_branch = Branch.open(dest) | ||
1224 | 59 | try: | ||
1225 | 38 | remote_branch = Branch.open(source) | 60 | remote_branch = Branch.open(source) |
1227 | 39 | remote_branch.bzrdir.sprout(dest).open_branch() | 61 | remote_branch.push(local_branch) |
1228 | 62 | tree = workingtree.WorkingTree.open(dest) | ||
1229 | 63 | tree.update() | ||
1230 | 40 | except Exception as e: | 64 | except Exception as e: |
1231 | 41 | raise e | 65 | raise e |
1232 | 42 | 66 | ||
1233 | 43 | 67 | ||
1234 | === modified file 'hooks/charmhelpers/fetch/giturl.py' | |||
1235 | --- hooks/charmhelpers/fetch/giturl.py 2014-11-26 12:31:33 +0000 | |||
1236 | +++ hooks/charmhelpers/fetch/giturl.py 2015-02-10 11:19:03 +0000 | |||
1237 | @@ -1,3 +1,19 @@ | |||
1238 | 1 | # Copyright 2014-2015 Canonical Limited. | ||
1239 | 2 | # | ||
1240 | 3 | # This file is part of charm-helpers. | ||
1241 | 4 | # | ||
1242 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | ||
1243 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | ||
1244 | 7 | # published by the Free Software Foundation. | ||
1245 | 8 | # | ||
1246 | 9 | # charm-helpers is distributed in the hope that it will be useful, | ||
1247 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1248 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1249 | 12 | # GNU Lesser General Public License for more details. | ||
1250 | 13 | # | ||
1251 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1252 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1253 | 16 | |||
1254 | 1 | import os | 17 | import os |
1255 | 2 | from charmhelpers.fetch import ( | 18 | from charmhelpers.fetch import ( |
1256 | 3 | BaseFetchHandler, | 19 | BaseFetchHandler, |
1257 | @@ -16,6 +32,8 @@ | |||
1258 | 16 | apt_install("python-git") | 32 | apt_install("python-git") |
1259 | 17 | from git import Repo | 33 | from git import Repo |
1260 | 18 | 34 | ||
1261 | 35 | from git.exc import GitCommandError | ||
1262 | 36 | |||
1263 | 19 | 37 | ||
1264 | 20 | class GitUrlFetchHandler(BaseFetchHandler): | 38 | class GitUrlFetchHandler(BaseFetchHandler): |
1265 | 21 | """Handler for git branches via generic and github URLs""" | 39 | """Handler for git branches via generic and github URLs""" |
1266 | @@ -34,15 +52,20 @@ | |||
1267 | 34 | repo = Repo.clone_from(source, dest) | 52 | repo = Repo.clone_from(source, dest) |
1268 | 35 | repo.git.checkout(branch) | 53 | repo.git.checkout(branch) |
1269 | 36 | 54 | ||
1271 | 37 | def install(self, source, branch="master"): | 55 | def install(self, source, branch="master", dest=None): |
1272 | 38 | url_parts = self.parse_url(source) | 56 | url_parts = self.parse_url(source) |
1273 | 39 | branch_name = url_parts.path.strip("/").split("/")[-1] | 57 | branch_name = url_parts.path.strip("/").split("/")[-1] |
1276 | 40 | dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", | 58 | if dest: |
1277 | 41 | branch_name) | 59 | dest_dir = os.path.join(dest, branch_name) |
1278 | 60 | else: | ||
1279 | 61 | dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", | ||
1280 | 62 | branch_name) | ||
1281 | 42 | if not os.path.exists(dest_dir): | 63 | if not os.path.exists(dest_dir): |
1282 | 43 | mkdir(dest_dir, perms=0o755) | 64 | mkdir(dest_dir, perms=0o755) |
1283 | 44 | try: | 65 | try: |
1284 | 45 | self.clone(source, dest_dir, branch) | 66 | self.clone(source, dest_dir, branch) |
1285 | 67 | except GitCommandError as e: | ||
1286 | 68 | raise UnhandledSource(e.message) | ||
1287 | 46 | except OSError as e: | 69 | except OSError as e: |
1288 | 47 | raise UnhandledSource(e.strerror) | 70 | raise UnhandledSource(e.strerror) |
1289 | 48 | return dest_dir | 71 | return dest_dir |
1290 | 49 | 72 | ||
1291 | === modified file 'hooks/common.py' | |||
1292 | --- hooks/common.py 2015-01-07 14:18:57 +0000 | |||
1293 | +++ hooks/common.py 2015-02-10 11:19:03 +0000 | |||
1294 | @@ -6,6 +6,7 @@ | |||
1295 | 6 | import shutil | 6 | import shutil |
1296 | 7 | from charmhelpers.core import hookenv, host | 7 | from charmhelpers.core import hookenv, host |
1297 | 8 | from charmhelpers.core.templating import render | 8 | from charmhelpers.core.templating import render |
1298 | 9 | from charmhelpers.contrib.database.mysql import MySQLHelper | ||
1299 | 9 | 10 | ||
1300 | 10 | 11 | ||
1301 | 11 | def get_service_user_file(service): | 12 | def get_service_user_file(service): |
1302 | @@ -60,71 +61,19 @@ | |||
1303 | 60 | broken = os.path.exists(broken_path) | 61 | broken = os.path.exists(broken_path) |
1304 | 61 | 62 | ||
1305 | 62 | 63 | ||
1306 | 64 | def get_db_helper(): | ||
1307 | 65 | return MySQLHelper(rpasswdf_template='/var/lib/mysql/mysql.passwd', | ||
1308 | 66 | upasswdf_template='/var/lib/mysql/mysql-{}.passwd') | ||
1309 | 67 | |||
1310 | 68 | |||
1311 | 63 | def get_db_cursor(): | 69 | def get_db_cursor(): |
1312 | 64 | # Connect to mysql | 70 | # Connect to mysql |
1314 | 65 | passwd = open("/var/lib/mysql/mysql.passwd").read().strip() | 71 | db_helper = get_db_helper() |
1315 | 72 | passwd = db_helper.get_mysql_root_password() | ||
1316 | 66 | connection = MySQLdb.connect(user="root", host="localhost", passwd=passwd) | 73 | connection = MySQLdb.connect(user="root", host="localhost", passwd=passwd) |
1317 | 67 | return connection.cursor() | 74 | return connection.cursor() |
1318 | 68 | 75 | ||
1319 | 69 | 76 | ||
1320 | 70 | def database_exists(db_name): | ||
1321 | 71 | cursor = get_db_cursor() | ||
1322 | 72 | try: | ||
1323 | 73 | cursor.execute("SHOW DATABASES") | ||
1324 | 74 | databases = [i[0] for i in cursor.fetchall()] | ||
1325 | 75 | finally: | ||
1326 | 76 | cursor.close() | ||
1327 | 77 | return db_name in databases | ||
1328 | 78 | |||
1329 | 79 | |||
1330 | 80 | def create_database(db_name): | ||
1331 | 81 | cursor = get_db_cursor() | ||
1332 | 82 | try: | ||
1333 | 83 | cursor.execute("CREATE DATABASE {}".format(db_name)) | ||
1334 | 84 | finally: | ||
1335 | 85 | cursor.close() | ||
1336 | 86 | |||
1337 | 87 | |||
1338 | 88 | def grant_exists(db_name, db_user, remote_ip): | ||
1339 | 89 | cursor = get_db_cursor() | ||
1340 | 90 | priv_string = "GRANT ALL PRIVILEGES ON `{}`.* " \ | ||
1341 | 91 | "TO '{}'@'{}'".format(db_name, db_user, remote_ip) | ||
1342 | 92 | try: | ||
1343 | 93 | cursor.execute("SHOW GRANTS for '{}'@'{}'".format(db_user, | ||
1344 | 94 | remote_ip)) | ||
1345 | 95 | grants = [i[0] for i in cursor.fetchall()] | ||
1346 | 96 | except MySQLdb.OperationalError: | ||
1347 | 97 | print "No grants found" | ||
1348 | 98 | return False | ||
1349 | 99 | finally: | ||
1350 | 100 | cursor.close() | ||
1351 | 101 | return priv_string in grants | ||
1352 | 102 | |||
1353 | 103 | |||
1354 | 104 | def create_grant(db_name, db_user, | ||
1355 | 105 | remote_ip, password): | ||
1356 | 106 | cursor = get_db_cursor() | ||
1357 | 107 | try: | ||
1358 | 108 | cursor.execute("GRANT ALL PRIVILEGES ON {}.* TO '{}'@'{}' " | ||
1359 | 109 | "IDENTIFIED BY '{}'".format(db_name, | ||
1360 | 110 | db_user, | ||
1361 | 111 | remote_ip, | ||
1362 | 112 | password)) | ||
1363 | 113 | finally: | ||
1364 | 114 | cursor.close() | ||
1365 | 115 | |||
1366 | 116 | |||
1367 | 117 | def cleanup_grant(db_user, | ||
1368 | 118 | remote_ip): | ||
1369 | 119 | cursor = get_db_cursor() | ||
1370 | 120 | try: | ||
1371 | 121 | cursor.execute("DROP FROM mysql.user WHERE user='{}' " | ||
1372 | 122 | "AND HOST='{}'".format(db_user, | ||
1373 | 123 | remote_ip)) | ||
1374 | 124 | finally: | ||
1375 | 125 | cursor.close() | ||
1376 | 126 | |||
1377 | 127 | |||
1378 | 128 | def migrate_to_mount(new_path): | 77 | def migrate_to_mount(new_path): |
1379 | 129 | """Invoked when new mountpoint appears. This function safely migrates | 78 | """Invoked when new mountpoint appears. This function safely migrates |
1380 | 130 | MySQL data from local disk to persistent storage (only if needed) | 79 | MySQL data from local disk to persistent storage (only if needed) |
1381 | 131 | 80 | ||
1382 | === modified file 'hooks/ha_relations.py' | |||
1383 | --- hooks/ha_relations.py 2014-09-21 22:03:41 +0000 | |||
1384 | +++ hooks/ha_relations.py 2015-02-10 11:19:03 +0000 | |||
1385 | @@ -7,6 +7,10 @@ | |||
1386 | 7 | import lib.ceph_utils as ceph | 7 | import lib.ceph_utils as ceph |
1387 | 8 | import lib.cluster_utils as cluster | 8 | import lib.cluster_utils as cluster |
1388 | 9 | 9 | ||
1389 | 10 | from charmhelpers.contrib.peerstorage import ( | ||
1390 | 11 | peer_echo, | ||
1391 | 12 | ) | ||
1392 | 13 | |||
1393 | 10 | # CEPH | 14 | # CEPH |
1394 | 11 | DATA_SRC_DST = '/var/lib/mysql' | 15 | DATA_SRC_DST = '/var/lib/mysql' |
1395 | 12 | SERVICE_NAME = os.getenv('JUJU_UNIT_NAME').split('/')[0] | 16 | SERVICE_NAME = os.getenv('JUJU_UNIT_NAME').split('/')[0] |
1396 | @@ -14,6 +18,11 @@ | |||
1397 | 14 | LEADER_RES = 'res_mysql_vip' | 18 | LEADER_RES = 'res_mysql_vip' |
1398 | 15 | 19 | ||
1399 | 16 | 20 | ||
1400 | 21 | def cluster_changed(): | ||
1401 | 22 | # Echo any passwords placed on peer relation | ||
1402 | 23 | peer_echo(includes=['.passwd']) | ||
1403 | 24 | |||
1404 | 25 | |||
1405 | 17 | def ha_relation_joined(): | 26 | def ha_relation_joined(): |
1406 | 18 | vip = utils.config_get('vip') | 27 | vip = utils.config_get('vip') |
1407 | 19 | vip_iface = utils.config_get('vip_iface') | 28 | vip_iface = utils.config_get('vip_iface') |
1408 | @@ -148,6 +157,7 @@ | |||
1409 | 148 | "ha-relation-changed": ha_relation_changed, | 157 | "ha-relation-changed": ha_relation_changed, |
1410 | 149 | "ceph-relation-joined": ceph_joined, | 158 | "ceph-relation-joined": ceph_joined, |
1411 | 150 | "ceph-relation-changed": ceph_changed, | 159 | "ceph-relation-changed": ceph_changed, |
1412 | 160 | "cluster-relation-changed": cluster_changed, | ||
1413 | 151 | } | 161 | } |
1414 | 152 | 162 | ||
1415 | 153 | utils.do_hooks(hooks) | 163 | utils.do_hooks(hooks) |
1416 | 154 | 164 | ||
1417 | === modified file 'hooks/shared_db_relations.py' | |||
1418 | --- hooks/shared_db_relations.py 2014-11-26 16:35:19 +0000 | |||
1419 | +++ hooks/shared_db_relations.py 2015-02-10 11:19:03 +0000 | |||
1420 | @@ -7,18 +7,12 @@ | |||
1421 | 7 | # | 7 | # |
1422 | 8 | # Author: Adam Gandelman <adam.gandelman@canonical.com> | 8 | # Author: Adam Gandelman <adam.gandelman@canonical.com> |
1423 | 9 | 9 | ||
1424 | 10 | |||
1425 | 11 | from common import ( | ||
1426 | 12 | database_exists, | ||
1427 | 13 | create_database, | ||
1428 | 14 | grant_exists, | ||
1429 | 15 | create_grant) | ||
1430 | 16 | import subprocess | 10 | import subprocess |
1431 | 17 | import json | 11 | import json |
1432 | 18 | import socket | ||
1433 | 19 | import os | ||
1434 | 20 | import lib.utils as utils | 12 | import lib.utils as utils |
1435 | 21 | import lib.cluster_utils as cluster | 13 | import lib.cluster_utils as cluster |
1436 | 14 | |||
1437 | 15 | from common import get_db_helper | ||
1438 | 22 | from charmhelpers.core import hookenv | 16 | from charmhelpers.core import hookenv |
1439 | 23 | from charmhelpers.contrib.network.ip import ( | 17 | from charmhelpers.contrib.network.ip import ( |
1440 | 24 | get_ipv6_addr | 18 | get_ipv6_addr |
1441 | @@ -50,71 +44,6 @@ | |||
1442 | 50 | 44 | ||
1443 | 51 | 45 | ||
1444 | 52 | def shared_db_changed(): | 46 | def shared_db_changed(): |
1445 | 53 | |||
1446 | 54 | def get_allowed_units(database, username): | ||
1447 | 55 | allowed_units = set() | ||
1448 | 56 | for relid in hookenv.relation_ids('shared-db'): | ||
1449 | 57 | for unit in hookenv.related_units(relid): | ||
1450 | 58 | attr = "%s_%s" % (database, 'hostname') | ||
1451 | 59 | hosts = hookenv.relation_get(attribute=attr, unit=unit, | ||
1452 | 60 | rid=relid) | ||
1453 | 61 | if not hosts: | ||
1454 | 62 | hosts = [hookenv.relation_get(attribute='private-address', | ||
1455 | 63 | unit=unit, rid=relid)] | ||
1456 | 64 | else: | ||
1457 | 65 | # hostname can be json-encoded list of hostnames | ||
1458 | 66 | try: | ||
1459 | 67 | hosts = json.loads(hosts) | ||
1460 | 68 | except ValueError: | ||
1461 | 69 | pass | ||
1462 | 70 | |||
1463 | 71 | if not isinstance(hosts, list): | ||
1464 | 72 | hosts = [hosts] | ||
1465 | 73 | |||
1466 | 74 | if hosts: | ||
1467 | 75 | for host in hosts: | ||
1468 | 76 | utils.juju_log('INFO', "Checking host '%s' grant" % | ||
1469 | 77 | (host)) | ||
1470 | 78 | if grant_exists(database, username, host): | ||
1471 | 79 | if unit not in allowed_units: | ||
1472 | 80 | allowed_units.add(unit) | ||
1473 | 81 | else: | ||
1474 | 82 | utils.juju_log('INFO', "No hosts found for grant check") | ||
1475 | 83 | |||
1476 | 84 | return allowed_units | ||
1477 | 85 | |||
1478 | 86 | def configure_db(hostname, | ||
1479 | 87 | database, | ||
1480 | 88 | username): | ||
1481 | 89 | passwd_file = "/var/lib/mysql/mysql-{}.passwd".format(username) | ||
1482 | 90 | if hostname != local_hostname: | ||
1483 | 91 | try: | ||
1484 | 92 | remote_ip = socket.gethostbyname(hostname) | ||
1485 | 93 | except Exception: | ||
1486 | 94 | # socket.gethostbyname doesn't support ipv6 | ||
1487 | 95 | remote_ip = hostname | ||
1488 | 96 | else: | ||
1489 | 97 | remote_ip = '127.0.0.1' | ||
1490 | 98 | |||
1491 | 99 | if not os.path.exists(passwd_file): | ||
1492 | 100 | password = pwgen() | ||
1493 | 101 | with open(passwd_file, 'w') as pfile: | ||
1494 | 102 | pfile.write(password) | ||
1495 | 103 | os.chmod(pfile.name, 0600) | ||
1496 | 104 | else: | ||
1497 | 105 | with open(passwd_file) as pfile: | ||
1498 | 106 | password = pfile.read().strip() | ||
1499 | 107 | |||
1500 | 108 | if not database_exists(database): | ||
1501 | 109 | create_database(database) | ||
1502 | 110 | if not grant_exists(database, | ||
1503 | 111 | username, | ||
1504 | 112 | remote_ip): | ||
1505 | 113 | create_grant(database, | ||
1506 | 114 | username, | ||
1507 | 115 | remote_ip, password) | ||
1508 | 116 | return password | ||
1509 | 117 | |||
1510 | 118 | if not cluster.eligible_leader(LEADER_RES): | 47 | if not cluster.eligible_leader(LEADER_RES): |
1511 | 119 | utils.juju_log('INFO', | 48 | utils.juju_log('INFO', |
1512 | 120 | 'MySQL service is peered, bailing shared-db relation' | 49 | 'MySQL service is peered, bailing shared-db relation' |
1513 | @@ -132,6 +61,8 @@ | |||
1514 | 132 | 'username', | 61 | 'username', |
1515 | 133 | 'hostname']) | 62 | 'hostname']) |
1516 | 134 | 63 | ||
1517 | 64 | db_helper = get_db_helper() | ||
1518 | 65 | |||
1519 | 135 | if singleset.issubset(settings): | 66 | if singleset.issubset(settings): |
1520 | 136 | # Process a single database configuration | 67 | # Process a single database configuration |
1521 | 137 | hostname = settings['hostname'] | 68 | hostname = settings['hostname'] |
1522 | @@ -142,26 +73,23 @@ | |||
1523 | 142 | try: | 73 | try: |
1524 | 143 | hostname = json.loads(hostname) | 74 | hostname = json.loads(hostname) |
1525 | 144 | except ValueError: | 75 | except ValueError: |
1546 | 145 | pass | 76 | hostname = [hostname] |
1547 | 146 | 77 | ||
1548 | 147 | if isinstance(hostname, list): | 78 | for host in hostname: |
1549 | 148 | for host in hostname: | 79 | password = db_helper.configure_db(host, database, username) |
1550 | 149 | password = configure_db(host, database, username) | 80 | |
1551 | 150 | else: | 81 | allowed_units = db_helper.get_allowed_units(database, username) |
1552 | 151 | password = configure_db(hostname, database, username) | 82 | allowed_units = unit_sorted(allowed_units) |
1553 | 152 | 83 | allowed_units = ' '.join(allowed_units) | |
1554 | 153 | allowed_units = " ".join(unit_sorted(get_allowed_units(database, | 84 | |
1555 | 154 | username))) | 85 | if cluster.is_clustered(): |
1556 | 155 | 86 | db_host = utils.config_get("vip") | |
1557 | 156 | if not cluster.is_clustered(): | 87 | else: |
1558 | 157 | utils.relation_set(db_host=local_hostname, | 88 | db_host = local_hostname |
1559 | 158 | password=password, | 89 | |
1560 | 159 | allowed_units=allowed_units) | 90 | utils.relation_set(db_host=db_host, |
1561 | 160 | else: | 91 | password=password, |
1562 | 161 | utils.relation_set(db_host=utils.config_get("vip"), | 92 | allowed_units=allowed_units) |
1543 | 162 | password=password, | ||
1544 | 163 | allowed_units=allowed_units) | ||
1545 | 164 | |||
1563 | 165 | else: | 93 | else: |
1564 | 166 | # Process multiple database setup requests. | 94 | # Process multiple database setup requests. |
1565 | 167 | # from incoming relation data: | 95 | # from incoming relation data: |
1566 | @@ -195,22 +123,23 @@ | |||
1567 | 195 | database = databases[db]['database'] | 123 | database = databases[db]['database'] |
1568 | 196 | hostname = databases[db]['hostname'] | 124 | hostname = databases[db]['hostname'] |
1569 | 197 | username = databases[db]['username'] | 125 | username = databases[db]['username'] |
1570 | 126 | |||
1571 | 198 | try: | 127 | try: |
1572 | 128 | # Can be json-encoded list of hostnames | ||
1573 | 199 | hostname = json.loads(hostname) | 129 | hostname = json.loads(hostname) |
1574 | 200 | except ValueError: | 130 | except ValueError: |
1588 | 201 | hostname = hostname | 131 | # Otherwise expected to be single hostname |
1589 | 202 | 132 | hostname = [hostname] | |
1590 | 203 | if isinstance(hostname, list): | 133 | |
1591 | 204 | for host in hostname: | 134 | for host in hostname: |
1592 | 205 | password = configure_db(host, database, username) | 135 | password = db_helper.configure_db(host, database, username) |
1593 | 206 | else: | 136 | |
1594 | 207 | password = configure_db(hostname, database, username) | 137 | a_units = db_helper.get_allowed_units(database, username) |
1595 | 208 | 138 | a_units = ' '.join(unit_sorted(a_units)) | |
1596 | 209 | return_data['_'.join([db, 'password'])] = password | 139 | return_data['%s_allowed_units' % (db)] = a_units |
1597 | 210 | allowed_units = unit_sorted(get_allowed_units(database, | 140 | |
1598 | 211 | username)) | 141 | return_data['%s_password' % (db)] = password |
1599 | 212 | return_data['_'.join([db, 'allowed_units'])] = \ | 142 | |
1587 | 213 | " ".join(allowed_units) | ||
1600 | 214 | if len(return_data) > 0: | 143 | if len(return_data) > 0: |
1601 | 215 | utils.relation_set(**return_data) | 144 | utils.relation_set(**return_data) |
1602 | 216 | if not cluster.is_clustered(): | 145 | if not cluster.is_clustered(): |
charm_unit_test #1560 mysql for hopem mp248743
UNIT FAIL: unit-test missing
UNIT Results (max last 2 lines):
INFO:root:Search string not found in makefile target commands.
ERROR:root:No make target was executed.
Full unit test output: http:// paste.ubuntu. com/10072413/ 10.245. 162.77: 8080/job/ charm_unit_ test/1560/
Build: http://