Merge lp:~james-page/charms/precise/hacluster/redux into lp:charms/hacluster
- Precise Pangolin (12.04)
- redux
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 16 |
Proposed branch: | lp:~james-page/charms/precise/hacluster/redux |
Merge into: | lp:charms/hacluster |
Diff against target: |
1217 lines (+647/-387) 10 files modified
.project (+17/-0) .pydevproject (+8/-0) config.yaml (+9/-4) copyright (+5/-0) hooks/hacluster.py (+8/-235) hooks/hooks.py (+136/-146) hooks/lib/cluster_utils.py (+130/-0) hooks/lib/utils.py (+332/-0) hooks/maas.py (+1/-1) hooks/pcmk.py (+1/-1) |
To merge this branch: | bzr merge lp:~james-page/charms/precise/hacluster/redux |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andres Rodriguez (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
Redux ready for final submission (hopefully)
1) Refactored to use HA openstack-
2) 'ready' barrier on peer relation added
Each service-unit set a 'ready' flag on the peer relationship when its received enough information to configure corosync and pacemaker; service units are only counted as valid once they have set this.
This ensures that nodes that don't have enough config info don't get counted when assessing clusterable size
3) oldest peer configures services
This is to avoid a split brain when initially forming the cluster; the oldest peer will most likely be busy dealing with other service relations, whilst the other service units start getting ready to be clustered during early service archive
By ensuring that the oldest peer configures the actual services, we smooth the transition from peering->clustered and avoid one of the newer service units trying to take control before the cluster is fully formed.
4) configurable initial cluster size
Default to 2 units; but should be set to the target size for the initial cluster.
5) default to corosync/pacemake v1 integration
This is as recommended upstream and splits the control of the corosync and pacemaker daemons.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andres Rodriguez (andreserl) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andres Rodriguez (andreserl) : | # |
Preview Diff
1 | === added file '.project' |
2 | --- .project 1970-01-01 00:00:00 +0000 |
3 | +++ .project 2013-03-25 14:21:21 +0000 |
4 | @@ -0,0 +1,17 @@ |
5 | +<?xml version="1.0" encoding="UTF-8"?> |
6 | +<projectDescription> |
7 | + <name>hacluster</name> |
8 | + <comment></comment> |
9 | + <projects> |
10 | + </projects> |
11 | + <buildSpec> |
12 | + <buildCommand> |
13 | + <name>org.python.pydev.PyDevBuilder</name> |
14 | + <arguments> |
15 | + </arguments> |
16 | + </buildCommand> |
17 | + </buildSpec> |
18 | + <natures> |
19 | + <nature>org.python.pydev.pythonNature</nature> |
20 | + </natures> |
21 | +</projectDescription> |
22 | |
23 | === added file '.pydevproject' |
24 | --- .pydevproject 1970-01-01 00:00:00 +0000 |
25 | +++ .pydevproject 2013-03-25 14:21:21 +0000 |
26 | @@ -0,0 +1,8 @@ |
27 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
28 | +<?eclipse-pydev version="1.0"?><pydev_project> |
29 | +<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property> |
30 | +<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property> |
31 | +<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> |
32 | +<path>/hacluster/hooks</path> |
33 | +</pydev_pathproperty> |
34 | +</pydev_project> |
35 | |
36 | === modified file 'config.yaml' |
37 | --- config.yaml 2013-01-24 23:39:45 +0000 |
38 | +++ config.yaml 2013-03-25 14:21:21 +0000 |
39 | @@ -7,22 +7,23 @@ |
40 | If multiple clusters are on the same bindnetaddr network, this value |
41 | can be changed. |
42 | corosync_pcmk_ver: |
43 | - default: 0 |
44 | + default: 1 |
45 | type: int |
46 | description: | |
47 | Service version for the Pacemaker service version. This will tell |
48 | Corosync how to start pacemaker |
49 | corosync_key: |
50 | type: string |
51 | - default: corosync-key |
52 | + default: "64RxJNcCkwo8EJYBsaacitUvbQp5AW4YolJi5/2urYZYp2jfLxY+3IUCOaAUJHPle4Yqfy+WBXO0I/6ASSAjj9jaiHVNaxmVhhjcmyBqy2vtPf+m+0VxVjUXlkTyYsODwobeDdO3SIkbIABGfjLTu29yqPTsfbvSYr6skRb9ne0=" |
53 | description: | |
54 | This value will become the Corosync authentication key. To generate |
55 | a suitable value use: |
56 | . |
57 | - corosync-keygen |
58 | + sudo corosync-keygen |
59 | + sudo cat /etc/corosync/authkey | base64 -w 0 |
60 | . |
61 | This configuration element is mandatory and the service will fail on |
62 | - install if it is not provided. |
63 | + install if it is not provided. The value must be base64 encoded. |
64 | stonith_enabled: |
65 | type: string |
66 | default: 'False' |
67 | @@ -36,3 +37,7 @@ |
68 | maas_credentials: |
69 | type: string |
70 | description: MAAS credentials (required for STONITH). |
71 | + cluster_count: |
72 | + type: int |
73 | + default: 2 |
74 | + description: Number of peer units required to bootstrap cluster services. |
75 | |
76 | === modified file 'copyright' |
77 | --- copyright 2012-11-20 20:06:11 +0000 |
78 | +++ copyright 2013-03-25 14:21:21 +0000 |
79 | @@ -15,3 +15,8 @@ |
80 | . |
81 | You should have received a copy of the GNU General Public License |
82 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
83 | + |
84 | + Files: ocf/ceph/* |
85 | + Copyright: 2012 Florian Haas, hastexo |
86 | + License: LGPL-2.1 |
87 | + On Debian based systems, see /usr/share/common-licenses/LGPL-2.1. |
88 | \ No newline at end of file |
89 | |
90 | === removed symlink 'hooks/ha-relation-departed' |
91 | === target was u'hooks.py' |
92 | === renamed file 'hooks/utils.py' => 'hooks/hacluster.py' |
93 | --- hooks/utils.py 2013-02-20 10:10:42 +0000 |
94 | +++ hooks/hacluster.py 2013-03-25 14:21:21 +0000 |
95 | @@ -10,243 +10,16 @@ |
96 | import os |
97 | import subprocess |
98 | import socket |
99 | -import sys |
100 | import fcntl |
101 | import struct |
102 | - |
103 | - |
104 | -def do_hooks(hooks): |
105 | - hook = os.path.basename(sys.argv[0]) |
106 | - |
107 | - try: |
108 | - hooks[hook]() |
109 | - except KeyError: |
110 | - juju_log('INFO', |
111 | - "This charm doesn't know how to handle '{}'.".format(hook)) |
112 | - |
113 | - |
114 | -def install(*pkgs): |
115 | - cmd = [ |
116 | - 'apt-get', |
117 | - '-y', |
118 | - 'install' |
119 | - ] |
120 | - for pkg in pkgs: |
121 | - cmd.append(pkg) |
122 | - subprocess.check_call(cmd) |
123 | - |
124 | -TEMPLATES_DIR = 'templates' |
125 | - |
126 | -try: |
127 | - import jinja2 |
128 | -except ImportError: |
129 | - install('python-jinja2') |
130 | - import jinja2 |
131 | - |
132 | -try: |
133 | - from netaddr import IPNetwork |
134 | -except ImportError: |
135 | - install('python-netaddr') |
136 | - from netaddr import IPNetwork |
137 | - |
138 | -try: |
139 | - import dns.resolver |
140 | -except ImportError: |
141 | - install('python-dnspython') |
142 | - import dns.resolver |
143 | - |
144 | - |
145 | -def render_template(template_name, context, template_dir=TEMPLATES_DIR): |
146 | - templates = jinja2.Environment( |
147 | - loader=jinja2.FileSystemLoader(template_dir) |
148 | - ) |
149 | - template = templates.get_template(template_name) |
150 | - return template.render(context) |
151 | - |
152 | - |
153 | -CLOUD_ARCHIVE = \ |
154 | -""" # Ubuntu Cloud Archive |
155 | -deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main |
156 | -""" |
157 | - |
158 | -CLOUD_ARCHIVE_POCKETS = { |
159 | - 'precise-folsom': 'precise-updates/folsom', |
160 | - 'precise-folsom/updates': 'precise-updates/folsom', |
161 | - 'precise-folsom/proposed': 'precise-proposed/folsom', |
162 | - 'precise-grizzly': 'precise-updates/grizzly', |
163 | - 'precise-grizzly/updates': 'precise-updates/grizzly', |
164 | - 'precise-grizzly/proposed': 'precise-proposed/grizzly' |
165 | - } |
166 | - |
167 | - |
168 | -def configure_source(): |
169 | - source = str(config_get('openstack-origin')) |
170 | - if not source: |
171 | - return |
172 | - if source.startswith('ppa:'): |
173 | - cmd = [ |
174 | - 'add-apt-repository', |
175 | - source |
176 | - ] |
177 | - subprocess.check_call(cmd) |
178 | - if source.startswith('cloud:'): |
179 | - install('ubuntu-cloud-keyring') |
180 | - pocket = source.split(':')[1] |
181 | - with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: |
182 | - apt.write(CLOUD_ARCHIVE.format(CLOUD_ARCHIVE_POCKETS[pocket])) |
183 | - if source.startswith('deb'): |
184 | - l = len(source.split('|')) |
185 | - if l == 2: |
186 | - (apt_line, key) = source.split('|') |
187 | - cmd = [ |
188 | - 'apt-key', |
189 | - 'adv', '--keyserver keyserver.ubuntu.com', |
190 | - '--recv-keys', key |
191 | - ] |
192 | - subprocess.check_call(cmd) |
193 | - elif l == 1: |
194 | - apt_line = source |
195 | - |
196 | - with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt: |
197 | - apt.write(apt_line + "\n") |
198 | - cmd = [ |
199 | - 'apt-get', |
200 | - 'update' |
201 | - ] |
202 | - subprocess.check_call(cmd) |
203 | - |
204 | -# Protocols |
205 | -TCP = 'TCP' |
206 | -UDP = 'UDP' |
207 | - |
208 | - |
209 | -def expose(port, protocol='TCP'): |
210 | - cmd = [ |
211 | - 'open-port', |
212 | - '{}/{}'.format(port, protocol) |
213 | - ] |
214 | - subprocess.check_call(cmd) |
215 | - |
216 | - |
217 | -def juju_log(severity, message): |
218 | - cmd = [ |
219 | - 'juju-log', |
220 | - '--log-level', severity, |
221 | - message |
222 | - ] |
223 | - subprocess.check_call(cmd) |
224 | - |
225 | - |
226 | -def relation_ids(relation): |
227 | - cmd = [ |
228 | - 'relation-ids', |
229 | - relation |
230 | - ] |
231 | - return subprocess.check_output(cmd).split() # IGNORE:E1103 |
232 | - |
233 | - |
234 | -def relation_list(rid): |
235 | - cmd = [ |
236 | - 'relation-list', |
237 | - '-r', rid, |
238 | - ] |
239 | - return subprocess.check_output(cmd).split() # IGNORE:E1103 |
240 | - |
241 | - |
242 | -def relation_get(attribute, unit=None, rid=None): |
243 | - cmd = [ |
244 | - 'relation-get', |
245 | - ] |
246 | - if rid: |
247 | - cmd.append('-r') |
248 | - cmd.append(rid) |
249 | - cmd.append(attribute) |
250 | - if unit: |
251 | - cmd.append(unit) |
252 | - value = subprocess.check_output(cmd).strip() # IGNORE:E1103 |
253 | - if value == "": |
254 | - return None |
255 | - else: |
256 | - return value |
257 | - |
258 | - |
259 | -def relation_set(**kwargs): |
260 | - cmd = [ |
261 | - 'relation-set' |
262 | - ] |
263 | - args = [] |
264 | - for k, v in kwargs.items(): |
265 | - if k == 'rid': |
266 | - cmd.append('-r') |
267 | - cmd.append(v) |
268 | - else: |
269 | - args.append('{}={}'.format(k, v)) |
270 | - cmd += args |
271 | - subprocess.check_call(cmd) |
272 | - |
273 | - |
274 | -def unit_get(attribute): |
275 | - cmd = [ |
276 | - 'unit-get', |
277 | - attribute |
278 | - ] |
279 | - return subprocess.check_output(cmd).strip() # IGNORE:E1103 |
280 | - |
281 | - |
282 | -def config_get(attribute): |
283 | - cmd = [ |
284 | - 'config-get', |
285 | - attribute |
286 | - ] |
287 | - return subprocess.check_output(cmd).strip() # IGNORE:E1103 |
288 | - |
289 | - |
290 | -def get_unit_hostname(): |
291 | - return socket.gethostname() |
292 | - |
293 | - |
294 | -def get_host_ip(hostname=unit_get('private-address')): |
295 | - try: |
296 | - # Test to see if already an IPv4 address |
297 | - socket.inet_aton(hostname) |
298 | - return hostname |
299 | - except socket.error: |
300 | - pass |
301 | - try: |
302 | - answers = dns.resolver.query(hostname, 'A') |
303 | - if answers: |
304 | - return answers[0].address |
305 | - except dns.resolver.NXDOMAIN: |
306 | - pass |
307 | - return None |
308 | - |
309 | - |
310 | -def restart(*services): |
311 | - for service in services: |
312 | - subprocess.check_call(['service', service, 'restart']) |
313 | - |
314 | - |
315 | -def stop(*services): |
316 | - for service in services: |
317 | - subprocess.check_call(['service', service, 'stop']) |
318 | - |
319 | - |
320 | -def start(*services): |
321 | - for service in services: |
322 | - subprocess.check_call(['service', service, 'start']) |
323 | - |
324 | - |
325 | -def running(service): |
326 | - try: |
327 | - output = subprocess.check_output(['service', service, 'status']) |
328 | - except subprocess.CalledProcessError: |
329 | - return False |
330 | - else: |
331 | - if ("start/running" in output or |
332 | - "is running" in output): |
333 | - return True |
334 | - else: |
335 | - return False |
336 | +import lib.utils as utils |
337 | + |
338 | + |
339 | +try: |
340 | + from netaddr import IPNetwork |
341 | +except ImportError: |
342 | + utils.install('python-netaddr') |
343 | + from netaddr import IPNetwork |
344 | |
345 | |
346 | def disable_upstart_services(*services): |
347 | |
348 | === added symlink 'hooks/hanode-relation-changed' |
349 | === target is u'hooks.py' |
350 | === modified file 'hooks/hooks.py' |
351 | --- hooks/hooks.py 2013-03-07 09:37:28 +0000 |
352 | +++ hooks/hooks.py 2013-03-25 14:21:21 +0000 |
353 | @@ -11,10 +11,13 @@ |
354 | import sys |
355 | import time |
356 | import os |
357 | +from base64 import b64decode |
358 | |
359 | import maas as MAAS |
360 | -import utils |
361 | +import lib.utils as utils |
362 | +import lib.cluster_utils as cluster |
363 | import pcmk |
364 | +import hacluster |
365 | |
366 | |
367 | def install(): |
368 | @@ -36,12 +39,12 @@ |
369 | for unit in utils.relation_list(relid): |
370 | conf = { |
371 | 'corosync_bindnetaddr': |
372 | - utils.get_network_address( |
373 | + hacluster.get_network_address( |
374 | utils.relation_get('corosync_bindiface', |
375 | unit, relid) |
376 | ), |
377 | 'corosync_mcastport': utils.relation_get('corosync_mcastport', |
378 | - unit, relid), |
379 | + unit, relid), |
380 | 'corosync_mcastaddr': utils.config_get('corosync_mcastaddr'), |
381 | 'corosync_pcmk_ver': utils.config_get('corosync_pcmk_ver'), |
382 | } |
383 | @@ -68,27 +71,27 @@ |
384 | with open('/etc/default/corosync', 'w') as corosync_default: |
385 | corosync_default.write(utils.render_template('corosync', |
386 | corosync_default_context)) |
387 | - |
388 | - # write the authkey |
389 | corosync_key = utils.config_get('corosync_key') |
390 | - with open('/etc/corosync/authkey', 'w') as corosync_key_file: |
391 | - corosync_key_file.write(corosync_key) |
392 | - os.chmod = ('/etc/corosync/authkey', 0400) |
393 | + if corosync_key: |
394 | + # write the authkey |
395 | + with open('/etc/corosync/authkey', 'w') as corosync_key_file: |
396 | + corosync_key_file.write(b64decode(corosync_key)) |
397 | + os.chmod = ('/etc/corosync/authkey', 0400) |
398 | |
399 | |
400 | def config_changed(): |
401 | utils.juju_log('INFO', 'Begin config-changed hook.') |
402 | |
403 | corosync_key = utils.config_get('corosync_key') |
404 | - if corosync_key == '': |
405 | + if not corosync_key: |
406 | utils.juju_log('CRITICAL', |
407 | 'No Corosync key supplied, cannot proceed') |
408 | sys.exit(1) |
409 | |
410 | if int(utils.config_get('corosync_pcmk_ver')) == 1: |
411 | - utils.enable_lsb_services('pacemaker') |
412 | + hacluster.enable_lsb_services('pacemaker') |
413 | else: |
414 | - utils.disable_lsb_services('pacemaker') |
415 | + hacluster.disable_lsb_services('pacemaker') |
416 | |
417 | # Create a new config file |
418 | emit_base_conf() |
419 | @@ -109,14 +112,6 @@ |
420 | utils.juju_log('INFO', 'End upgrade-charm hook.') |
421 | |
422 | |
423 | -def start(): |
424 | - pass |
425 | - |
426 | - |
427 | -def stop(): |
428 | - pass |
429 | - |
430 | - |
431 | def restart_corosync(): |
432 | if int(utils.config_get('corosync_pcmk_ver')) == 1: |
433 | if utils.running("pacemaker"): |
434 | @@ -136,17 +131,23 @@ |
435 | utils.juju_log('INFO', |
436 | 'HA already configured, not reconfiguring') |
437 | return |
438 | - # Check that there's enough nodes in order to perform the |
439 | - # configuration of the HA cluster |
440 | - if len(get_cluster_nodes()) < 2: |
441 | - utils.juju_log('WARNING', 'Not enough nodes in cluster, bailing') |
442 | - return |
443 | # Check that we are related to a principle and that |
444 | # it has already provided the required corosync configuration |
445 | if not get_corosync_conf(): |
446 | utils.juju_log('WARNING', |
447 | 'Unable to configure corosync right now, bailing') |
448 | return |
449 | + else: |
450 | + utils.juju_log('INFO', |
451 | + 'Ready to form cluster - informing peers') |
452 | + utils.relation_set(ready=True, |
453 | + rid=utils.relation_ids('hanode')[0]) |
454 | + # Check that there's enough nodes in order to perform the |
455 | + # configuration of the HA cluster |
456 | + if (len(get_cluster_nodes()) < |
457 | + int(utils.config_get('cluster_count'))): |
458 | + utils.juju_log('WARNING', 'Not enough nodes in cluster, bailing') |
459 | + return |
460 | |
461 | relids = utils.relation_ids('ha') |
462 | if len(relids) == 1: # Should only ever be one of these |
463 | @@ -225,98 +226,101 @@ |
464 | ' resource-stickiness="100"' |
465 | pcmk.commit(cmd) |
466 | |
467 | - utils.juju_log('INFO', 'Configuring Resources') |
468 | - utils.juju_log('INFO', str(resources)) |
469 | - |
470 | - for res_name, res_type in resources.iteritems(): |
471 | - # disable the service we are going to put in HA |
472 | - if res_type.split(':')[0] == "lsb": |
473 | - utils.disable_lsb_services(res_type.split(':')[1]) |
474 | - if utils.running(res_type.split(':')[1]): |
475 | - utils.stop(res_type.split(':')[1]) |
476 | - elif (len(init_services) != 0 and |
477 | - res_name in init_services and |
478 | - init_services[res_name]): |
479 | - utils.disable_upstart_services(init_services[res_name]) |
480 | - if utils.running(init_services[res_name]): |
481 | - utils.stop(init_services[res_name]) |
482 | - # Put the services in HA, if not already done so |
483 | - #if not pcmk.is_resource_present(res_name): |
484 | - if not pcmk.crm_opt_exists(res_name): |
485 | - if not res_name in resource_params: |
486 | - cmd = 'crm -F configure primitive %s %s' % (res_name, res_type) |
487 | - else: |
488 | - cmd = 'crm -F configure primitive %s %s %s' % \ |
489 | - (res_name, |
490 | - res_type, |
491 | - resource_params[res_name]) |
492 | - pcmk.commit(cmd) |
493 | - utils.juju_log('INFO', '%s' % cmd) |
494 | - |
495 | - utils.juju_log('INFO', 'Configuring Groups') |
496 | - utils.juju_log('INFO', str(groups)) |
497 | - for grp_name, grp_params in groups.iteritems(): |
498 | - if not pcmk.crm_opt_exists(grp_name): |
499 | - cmd = 'crm -F configure group %s %s' % (grp_name, grp_params) |
500 | - pcmk.commit(cmd) |
501 | - utils.juju_log('INFO', '%s' % cmd) |
502 | - |
503 | - utils.juju_log('INFO', 'Configuring Master/Slave (ms)') |
504 | - utils.juju_log('INFO', str(ms)) |
505 | - for ms_name, ms_params in ms.iteritems(): |
506 | - if not pcmk.crm_opt_exists(ms_name): |
507 | - cmd = 'crm -F configure ms %s %s' % (ms_name, ms_params) |
508 | - pcmk.commit(cmd) |
509 | - utils.juju_log('INFO', '%s' % cmd) |
510 | - |
511 | - utils.juju_log('INFO', 'Configuring Orders') |
512 | - utils.juju_log('INFO', str(orders)) |
513 | - for ord_name, ord_params in orders.iteritems(): |
514 | - if not pcmk.crm_opt_exists(ord_name): |
515 | - cmd = 'crm -F configure order %s %s' % (ord_name, ord_params) |
516 | - pcmk.commit(cmd) |
517 | - utils.juju_log('INFO', '%s' % cmd) |
518 | - |
519 | - utils.juju_log('INFO', 'Configuring Colocations') |
520 | - utils.juju_log('INFO', str(colocations)) |
521 | - for col_name, col_params in colocations.iteritems(): |
522 | - if not pcmk.crm_opt_exists(col_name): |
523 | - cmd = 'crm -F configure colocation %s %s' % (col_name, col_params) |
524 | - pcmk.commit(cmd) |
525 | - utils.juju_log('INFO', '%s' % cmd) |
526 | - |
527 | - utils.juju_log('INFO', 'Configuring Clones') |
528 | - utils.juju_log('INFO', str(clones)) |
529 | - for cln_name, cln_params in clones.iteritems(): |
530 | - if not pcmk.crm_opt_exists(cln_name): |
531 | - cmd = 'crm -F configure clone %s %s' % (cln_name, cln_params) |
532 | - pcmk.commit(cmd) |
533 | - utils.juju_log('INFO', '%s' % cmd) |
534 | - |
535 | - for res_name, res_type in resources.iteritems(): |
536 | - if len(init_services) != 0 and res_name in init_services: |
537 | - # Checks that the resources are running and started. |
538 | - # Ensure that clones are excluded as the resource is |
539 | - # not directly controllable (dealt with below) |
540 | - # Ensure that groups are cleaned up as a whole rather |
541 | - # than as individual resources. |
542 | - if (res_name not in clones.values() and |
543 | - res_name not in groups.values() and |
544 | - not pcmk.crm_res_running(res_name)): |
545 | - # Just in case, cleanup the resources to ensure they get |
546 | - # started in case they failed for some unrelated reason. |
547 | - cmd = 'crm resource cleanup %s' % res_name |
548 | - pcmk.commit(cmd) |
549 | - |
550 | - for cl_name in clones: |
551 | - # Always cleanup clones |
552 | - cmd = 'crm resource cleanup %s' % cl_name |
553 | - pcmk.commit(cmd) |
554 | - |
555 | - for grp_name in groups: |
556 | - # Always cleanup groups |
557 | - cmd = 'crm resource cleanup %s' % grp_name |
558 | - pcmk.commit(cmd) |
559 | + # Only configure the cluster resources |
560 | + # from the oldest peer unit. |
561 | + if cluster.oldest_peer(cluster.peer_units()): |
562 | + utils.juju_log('INFO', 'Configuring Resources') |
563 | + utils.juju_log('INFO', str(resources)) |
564 | + for res_name, res_type in resources.iteritems(): |
565 | + # disable the service we are going to put in HA |
566 | + if res_type.split(':')[0] == "lsb": |
567 | + hacluster.disable_lsb_services(res_type.split(':')[1]) |
568 | + if utils.running(res_type.split(':')[1]): |
569 | + utils.stop(res_type.split(':')[1]) |
570 | + elif (len(init_services) != 0 and |
571 | + res_name in init_services and |
572 | + init_services[res_name]): |
573 | + hacluster.disable_upstart_services(init_services[res_name]) |
574 | + if utils.running(init_services[res_name]): |
575 | + utils.stop(init_services[res_name]) |
576 | + # Put the services in HA, if not already done so |
577 | + #if not pcmk.is_resource_present(res_name): |
578 | + if not pcmk.crm_opt_exists(res_name): |
579 | + if not res_name in resource_params: |
580 | + cmd = 'crm -F configure primitive %s %s' % (res_name, |
581 | + res_type) |
582 | + else: |
583 | + cmd = 'crm -F configure primitive %s %s %s' % \ |
584 | + (res_name, |
585 | + res_type, |
586 | + resource_params[res_name]) |
587 | + pcmk.commit(cmd) |
588 | + utils.juju_log('INFO', '%s' % cmd) |
589 | + |
590 | + utils.juju_log('INFO', 'Configuring Groups') |
591 | + utils.juju_log('INFO', str(groups)) |
592 | + for grp_name, grp_params in groups.iteritems(): |
593 | + if not pcmk.crm_opt_exists(grp_name): |
594 | + cmd = 'crm -F configure group %s %s' % (grp_name, grp_params) |
595 | + pcmk.commit(cmd) |
596 | + utils.juju_log('INFO', '%s' % cmd) |
597 | + |
598 | + utils.juju_log('INFO', 'Configuring Master/Slave (ms)') |
599 | + utils.juju_log('INFO', str(ms)) |
600 | + for ms_name, ms_params in ms.iteritems(): |
601 | + if not pcmk.crm_opt_exists(ms_name): |
602 | + cmd = 'crm -F configure ms %s %s' % (ms_name, ms_params) |
603 | + pcmk.commit(cmd) |
604 | + utils.juju_log('INFO', '%s' % cmd) |
605 | + |
606 | + utils.juju_log('INFO', 'Configuring Orders') |
607 | + utils.juju_log('INFO', str(orders)) |
608 | + for ord_name, ord_params in orders.iteritems(): |
609 | + if not pcmk.crm_opt_exists(ord_name): |
610 | + cmd = 'crm -F configure order %s %s' % (ord_name, ord_params) |
611 | + pcmk.commit(cmd) |
612 | + utils.juju_log('INFO', '%s' % cmd) |
613 | + |
614 | + utils.juju_log('INFO', 'Configuring Colocations') |
615 | + utils.juju_log('INFO', str(colocations)) |
616 | + for col_name, col_params in colocations.iteritems(): |
617 | + if not pcmk.crm_opt_exists(col_name): |
618 | + cmd = 'crm -F configure colocation %s %s' % (col_name, col_params) |
619 | + pcmk.commit(cmd) |
620 | + utils.juju_log('INFO', '%s' % cmd) |
621 | + |
622 | + utils.juju_log('INFO', 'Configuring Clones') |
623 | + utils.juju_log('INFO', str(clones)) |
624 | + for cln_name, cln_params in clones.iteritems(): |
625 | + if not pcmk.crm_opt_exists(cln_name): |
626 | + cmd = 'crm -F configure clone %s %s' % (cln_name, cln_params) |
627 | + pcmk.commit(cmd) |
628 | + utils.juju_log('INFO', '%s' % cmd) |
629 | + |
630 | + for res_name, res_type in resources.iteritems(): |
631 | + if len(init_services) != 0 and res_name in init_services: |
632 | + # Checks that the resources are running and started. |
633 | + # Ensure that clones are excluded as the resource is |
634 | + # not directly controllable (dealt with below) |
635 | + # Ensure that groups are cleaned up as a whole rather |
636 | + # than as individual resources. |
637 | + if (res_name not in clones.values() and |
638 | + res_name not in groups.values() and |
639 | + not pcmk.crm_res_running(res_name)): |
640 | + # Just in case, cleanup the resources to ensure they get |
641 | + # started in case they failed for some unrelated reason. |
642 | + cmd = 'crm resource cleanup %s' % res_name |
643 | + pcmk.commit(cmd) |
644 | + |
645 | + for cl_name in clones: |
646 | + # Always cleanup clones |
647 | + cmd = 'crm resource cleanup %s' % cl_name |
648 | + pcmk.commit(cmd) |
649 | + |
650 | + for grp_name in groups: |
651 | + # Always cleanup groups |
652 | + cmd = 'crm resource cleanup %s' % grp_name |
653 | + pcmk.commit(cmd) |
654 | |
655 | for rel_id in utils.relation_ids('ha'): |
656 | utils.relation_set(rid=rel_id, |
657 | @@ -382,42 +386,28 @@ |
658 | pcmk.commit(cmd) |
659 | |
660 | |
661 | -def ha_relation_departed(): |
662 | - # TODO: Fin out which node is departing and put it in standby mode. |
663 | - # If this happens, and a new relation is created in the same machine |
664 | - # (which already has node), then check whether it is standby and put it |
665 | - # in online mode. This should be done in ha_relation_joined. |
666 | - pcmk.standby(utils.get_unit_hostname()) |
667 | - |
668 | - |
669 | def get_cluster_nodes(): |
670 | hosts = [] |
671 | - hosts.append('{}:6789'.format(utils.get_host_ip())) |
672 | - |
673 | + hosts.append(utils.unit_get('private-address')) |
674 | for relid in utils.relation_ids('hanode'): |
675 | for unit in utils.relation_list(relid): |
676 | - hosts.append( |
677 | - '{}:6789'.format(utils.get_host_ip( |
678 | - utils.relation_get('private-address', |
679 | - unit, relid))) |
680 | - ) |
681 | - |
682 | + if utils.relation_get('ready', |
683 | + rid=relid, |
684 | + unit=unit): |
685 | + hosts.append(utils.relation_get('private-address', |
686 | + unit, relid)) |
687 | hosts.sort() |
688 | return hosts |
689 | |
690 | |
691 | -utils.do_hooks({ |
692 | - 'install': install, |
693 | - 'config-changed': config_changed, |
694 | - 'start': start, |
695 | - 'stop': stop, |
696 | - 'upgrade-charm': upgrade_charm, |
697 | - 'ha-relation-joined': configure_cluster, |
698 | - 'ha-relation-changed': configure_cluster, |
699 | - 'ha-relation-departed': ha_relation_departed, |
700 | - 'hanode-relation-joined': configure_cluster, |
701 | - #'hanode-relation-departed': hanode_relation_departed, |
702 | - # TODO: should probably remove nodes from the cluster |
703 | - }) |
704 | +hooks = { |
705 | + 'install': install, |
706 | + 'config-changed': config_changed, |
707 | + 'upgrade-charm': upgrade_charm, |
708 | + 'ha-relation-joined': configure_cluster, |
709 | + 'ha-relation-changed': configure_cluster, |
710 | + 'hanode-relation-joined': configure_cluster, |
711 | + 'hanode-relation-changed': configure_cluster, |
712 | + } |
713 | |
714 | -sys.exit(0) |
715 | +utils.do_hooks(hooks) |
716 | |
717 | === added directory 'hooks/lib' |
718 | === added file 'hooks/lib/__init__.py' |
719 | === added file 'hooks/lib/cluster_utils.py' |
720 | --- hooks/lib/cluster_utils.py 1970-01-01 00:00:00 +0000 |
721 | +++ hooks/lib/cluster_utils.py 2013-03-25 14:21:21 +0000 |
722 | @@ -0,0 +1,130 @@ |
723 | +# |
724 | +# Copyright 2012 Canonical Ltd. |
725 | +# |
726 | +# This file is sourced from lp:openstack-charm-helpers |
727 | +# |
728 | +# Authors: |
729 | +# James Page <james.page@ubuntu.com> |
730 | +# Adam Gandelman <adamg@ubuntu.com> |
731 | +# |
732 | + |
733 | +from lib.utils import ( |
734 | + juju_log, |
735 | + relation_ids, |
736 | + relation_list, |
737 | + relation_get, |
738 | + get_unit_hostname, |
739 | + config_get |
740 | + ) |
741 | +import subprocess |
742 | +import os |
743 | + |
744 | + |
745 | +def is_clustered(): |
746 | + for r_id in (relation_ids('ha') or []): |
747 | + for unit in (relation_list(r_id) or []): |
748 | + clustered = relation_get('clustered', |
749 | + rid=r_id, |
750 | + unit=unit) |
751 | + if clustered: |
752 | + return True |
753 | + return False |
754 | + |
755 | + |
756 | +def is_leader(resource): |
757 | + cmd = [ |
758 | + "crm", "resource", |
759 | + "show", resource |
760 | + ] |
761 | + try: |
762 | + status = subprocess.check_output(cmd) |
763 | + except subprocess.CalledProcessError: |
764 | + return False |
765 | + else: |
766 | + if get_unit_hostname() in status: |
767 | + return True |
768 | + else: |
769 | + return False |
770 | + |
771 | + |
772 | +def peer_units(): |
773 | + peers = [] |
774 | + for r_id in (relation_ids('cluster') or []): |
775 | + for unit in (relation_list(r_id) or []): |
776 | + peers.append(unit) |
777 | + return peers |
778 | + |
779 | + |
780 | +def oldest_peer(peers): |
781 | + local_unit_no = int(os.getenv('JUJU_UNIT_NAME').split('/')[1]) |
782 | + for peer in peers: |
783 | + remote_unit_no = int(peer.split('/')[1]) |
784 | + if remote_unit_no < local_unit_no: |
785 | + return False |
786 | + return True |
787 | + |
788 | + |
789 | +def eligible_leader(resource): |
790 | + if is_clustered(): |
791 | + if not is_leader(resource): |
792 | + juju_log('INFO', 'Deferring action to CRM leader.') |
793 | + return False |
794 | + else: |
795 | + peers = peer_units() |
796 | + if peers and not oldest_peer(peers): |
797 | + juju_log('INFO', 'Deferring action to oldest service unit.') |
798 | + return False |
799 | + return True |
800 | + |
801 | + |
802 | +def https(): |
803 | + ''' |
804 | + Determines whether enough data has been provided in configuration |
805 | + or relation data to configure HTTPS |
806 | + . |
807 | + returns: boolean |
808 | + ''' |
809 | + if config_get('use-https') == "yes": |
810 | + return True |
811 | + if config_get('ssl_cert') and config_get('ssl_key'): |
812 | + return True |
813 | + for r_id in relation_ids('identity-service'): |
814 | + for unit in relation_list(r_id): |
815 | + if (relation_get('https_keystone', rid=r_id, unit=unit) and |
816 | + relation_get('ssl_cert', rid=r_id, unit=unit) and |
817 | + relation_get('ssl_key', rid=r_id, unit=unit) and |
818 | + relation_get('ca_cert', rid=r_id, unit=unit)): |
819 | + return True |
820 | + return False |
821 | + |
822 | + |
823 | +def determine_api_port(public_port): |
824 | + ''' |
825 | + Determine correct API server listening port based on |
826 | + existence of HTTPS reverse proxy and/or haproxy. |
827 | + |
828 | + public_port: int: standard public port for given service |
829 | + |
830 | + returns: int: the correct listening port for the API service |
831 | + ''' |
832 | + i = 0 |
833 | + if len(peer_units()) > 0 or is_clustered(): |
834 | + i += 1 |
835 | + if https(): |
836 | + i += 1 |
837 | + return public_port - (i * 10) |
838 | + |
839 | + |
840 | +def determine_haproxy_port(public_port): |
841 | + ''' |
842 | + Description: Determine correct proxy listening port based on public IP + |
843 | + existence of HTTPS reverse proxy. |
844 | + |
845 | + public_port: int: standard public port for given service |
846 | + |
847 | + returns: int: the correct listening port for the HAProxy service |
848 | + ''' |
849 | + i = 0 |
850 | + if https(): |
851 | + i += 1 |
852 | + return public_port - (i * 10) |
853 | |
854 | === added file 'hooks/lib/utils.py' |
855 | --- hooks/lib/utils.py 1970-01-01 00:00:00 +0000 |
856 | +++ hooks/lib/utils.py 2013-03-25 14:21:21 +0000 |
857 | @@ -0,0 +1,332 @@ |
858 | +# |
859 | +# Copyright 2012 Canonical Ltd. |
860 | +# |
861 | +# This file is sourced from lp:openstack-charm-helpers |
862 | +# |
863 | +# Authors: |
864 | +# James Page <james.page@ubuntu.com> |
865 | +# Paul Collins <paul.collins@canonical.com> |
866 | +# Adam Gandelman <adamg@ubuntu.com> |
867 | +# |
868 | + |
869 | +import json |
870 | +import os |
871 | +import subprocess |
872 | +import socket |
873 | +import sys |
874 | + |
875 | + |
876 | +def do_hooks(hooks): |
877 | + hook = os.path.basename(sys.argv[0]) |
878 | + |
879 | + try: |
880 | + hook_func = hooks[hook] |
881 | + except KeyError: |
882 | + juju_log('INFO', |
883 | + "This charm doesn't know how to handle '{}'.".format(hook)) |
884 | + else: |
885 | + hook_func() |
886 | + |
887 | + |
888 | +def install(*pkgs): |
889 | + cmd = [ |
890 | + 'apt-get', |
891 | + '-y', |
892 | + 'install' |
893 | + ] |
894 | + for pkg in pkgs: |
895 | + cmd.append(pkg) |
896 | + subprocess.check_call(cmd) |
897 | + |
898 | +TEMPLATES_DIR = 'templates' |
899 | + |
900 | +try: |
901 | + import jinja2 |
902 | +except ImportError: |
903 | + install('python-jinja2') |
904 | + import jinja2 |
905 | + |
906 | +try: |
907 | + import dns.resolver |
908 | +except ImportError: |
909 | + install('python-dnspython') |
910 | + import dns.resolver |
911 | + |
912 | + |
913 | +def render_template(template_name, context, template_dir=TEMPLATES_DIR): |
914 | + templates = jinja2.Environment( |
915 | + loader=jinja2.FileSystemLoader(template_dir) |
916 | + ) |
917 | + template = templates.get_template(template_name) |
918 | + return template.render(context) |
919 | + |
920 | +CLOUD_ARCHIVE = \ |
921 | +""" # Ubuntu Cloud Archive |
922 | +deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main |
923 | +""" |
924 | + |
925 | +CLOUD_ARCHIVE_POCKETS = { |
926 | + 'folsom': 'precise-updates/folsom', |
927 | + 'folsom/updates': 'precise-updates/folsom', |
928 | + 'folsom/proposed': 'precise-proposed/folsom', |
929 | + 'grizzly': 'precise-updates/grizzly', |
930 | + 'grizzly/updates': 'precise-updates/grizzly', |
931 | + 'grizzly/proposed': 'precise-proposed/grizzly' |
932 | + } |
933 | + |
934 | + |
935 | +def configure_source(): |
936 | + source = str(config_get('openstack-origin')) |
937 | + if not source: |
938 | + return |
939 | + if source.startswith('ppa:'): |
940 | + cmd = [ |
941 | + 'add-apt-repository', |
942 | + source |
943 | + ] |
944 | + subprocess.check_call(cmd) |
945 | + if source.startswith('cloud:'): |
946 | + # CA values should be formatted as cloud:ubuntu-openstack/pocket, eg: |
947 | + # cloud:precise-folsom/updates or cloud:precise-folsom/proposed |
948 | + install('ubuntu-cloud-keyring') |
949 | + pocket = source.split(':')[1] |
950 | + pocket = pocket.split('-')[1] |
951 | + with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: |
952 | + apt.write(CLOUD_ARCHIVE.format(CLOUD_ARCHIVE_POCKETS[pocket])) |
953 | + if source.startswith('deb'): |
954 | + l = len(source.split('|')) |
955 | + if l == 2: |
956 | + (apt_line, key) = source.split('|') |
957 | + cmd = [ |
958 | + 'apt-key', |
959 | + 'adv', '--keyserver keyserver.ubuntu.com', |
960 | + '--recv-keys', key |
961 | + ] |
962 | + subprocess.check_call(cmd) |
963 | + elif l == 1: |
964 | + apt_line = source |
965 | + |
966 | + with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt: |
967 | + apt.write(apt_line + "\n") |
968 | + cmd = [ |
969 | + 'apt-get', |
970 | + 'update' |
971 | + ] |
972 | + subprocess.check_call(cmd) |
973 | + |
974 | +# Protocols |
975 | +TCP = 'TCP' |
976 | +UDP = 'UDP' |
977 | + |
978 | + |
979 | +def expose(port, protocol='TCP'): |
980 | + cmd = [ |
981 | + 'open-port', |
982 | + '{}/{}'.format(port, protocol) |
983 | + ] |
984 | + subprocess.check_call(cmd) |
985 | + |
986 | + |
987 | +def juju_log(severity, message): |
988 | + cmd = [ |
989 | + 'juju-log', |
990 | + '--log-level', severity, |
991 | + message |
992 | + ] |
993 | + subprocess.check_call(cmd) |
994 | + |
995 | + |
996 | +cache = {} |
997 | + |
998 | + |
999 | +def cached(func): |
1000 | + def wrapper(*args, **kwargs): |
1001 | + global cache |
1002 | + key = str((func, args, kwargs)) |
1003 | + try: |
1004 | + return cache[key] |
1005 | + except KeyError: |
1006 | + res = func(*args, **kwargs) |
1007 | + cache[key] = res |
1008 | + return res |
1009 | + return wrapper |
1010 | + |
1011 | + |
1012 | +@cached |
1013 | +def relation_ids(relation): |
1014 | + cmd = [ |
1015 | + 'relation-ids', |
1016 | + relation |
1017 | + ] |
1018 | + result = str(subprocess.check_output(cmd)).split() |
1019 | + if result == "": |
1020 | + return None |
1021 | + else: |
1022 | + return result |
1023 | + |
1024 | + |
1025 | +@cached |
1026 | +def relation_list(rid): |
1027 | + cmd = [ |
1028 | + 'relation-list', |
1029 | + '-r', rid, |
1030 | + ] |
1031 | + result = str(subprocess.check_output(cmd)).split() |
1032 | + if result == "": |
1033 | + return None |
1034 | + else: |
1035 | + return result |
1036 | + |
1037 | + |
1038 | +@cached |
1039 | +def relation_get(attribute, unit=None, rid=None): |
1040 | + cmd = [ |
1041 | + 'relation-get', |
1042 | + ] |
1043 | + if rid: |
1044 | + cmd.append('-r') |
1045 | + cmd.append(rid) |
1046 | + cmd.append(attribute) |
1047 | + if unit: |
1048 | + cmd.append(unit) |
1049 | + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 |
1050 | + if value == "": |
1051 | + return None |
1052 | + else: |
1053 | + return value |
1054 | + |
1055 | + |
1056 | +@cached |
1057 | +def relation_get_dict(relation_id=None, remote_unit=None): |
1058 | + """Obtain all relation data as dict by way of JSON""" |
1059 | + cmd = [ |
1060 | + 'relation-get', '--format=json' |
1061 | + ] |
1062 | + if relation_id: |
1063 | + cmd.append('-r') |
1064 | + cmd.append(relation_id) |
1065 | + if remote_unit: |
1066 | + remote_unit_orig = os.getenv('JUJU_REMOTE_UNIT', None) |
1067 | + os.environ['JUJU_REMOTE_UNIT'] = remote_unit |
1068 | + j = subprocess.check_output(cmd) |
1069 | + if remote_unit and remote_unit_orig: |
1070 | + os.environ['JUJU_REMOTE_UNIT'] = remote_unit_orig |
1071 | + d = json.loads(j) |
1072 | + settings = {} |
1073 | + # convert unicode to strings |
1074 | + for k, v in d.iteritems(): |
1075 | + settings[str(k)] = str(v) |
1076 | + return settings |
1077 | + |
1078 | + |
1079 | +def relation_set(**kwargs): |
1080 | + cmd = [ |
1081 | + 'relation-set' |
1082 | + ] |
1083 | + args = [] |
1084 | + for k, v in kwargs.items(): |
1085 | + if k == 'rid': |
1086 | + if v: |
1087 | + cmd.append('-r') |
1088 | + cmd.append(v) |
1089 | + else: |
1090 | + args.append('{}={}'.format(k, v)) |
1091 | + cmd += args |
1092 | + subprocess.check_call(cmd) |
1093 | + |
1094 | + |
1095 | +@cached |
1096 | +def unit_get(attribute): |
1097 | + cmd = [ |
1098 | + 'unit-get', |
1099 | + attribute |
1100 | + ] |
1101 | + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 |
1102 | + if value == "": |
1103 | + return None |
1104 | + else: |
1105 | + return value |
1106 | + |
1107 | + |
1108 | +@cached |
1109 | +def config_get(attribute): |
1110 | + cmd = [ |
1111 | + 'config-get', |
1112 | + '--format', |
1113 | + 'json', |
1114 | + ] |
1115 | + out = subprocess.check_output(cmd).strip() # IGNORE:E1103 |
1116 | + cfg = json.loads(out) |
1117 | + |
1118 | + try: |
1119 | + return cfg[attribute] |
1120 | + except KeyError: |
1121 | + return None |
1122 | + |
1123 | + |
1124 | +@cached |
1125 | +def get_unit_hostname(): |
1126 | + return socket.gethostname() |
1127 | + |
1128 | + |
1129 | +@cached |
1130 | +def get_host_ip(hostname=unit_get('private-address')): |
1131 | + try: |
1132 | + # Test to see if already an IPv4 address |
1133 | + socket.inet_aton(hostname) |
1134 | + return hostname |
1135 | + except socket.error: |
1136 | + answers = dns.resolver.query(hostname, 'A') |
1137 | + if answers: |
1138 | + return answers[0].address |
1139 | + return None |
1140 | + |
1141 | + |
1142 | +def _svc_control(service, action): |
1143 | + subprocess.check_call(['service', service, action]) |
1144 | + |
1145 | + |
1146 | +def restart(*services): |
1147 | + for service in services: |
1148 | + _svc_control(service, 'restart') |
1149 | + |
1150 | + |
1151 | +def stop(*services): |
1152 | + for service in services: |
1153 | + _svc_control(service, 'stop') |
1154 | + |
1155 | + |
1156 | +def start(*services): |
1157 | + for service in services: |
1158 | + _svc_control(service, 'start') |
1159 | + |
1160 | + |
1161 | +def reload(*services): |
1162 | + for service in services: |
1163 | + try: |
1164 | + _svc_control(service, 'reload') |
1165 | + except subprocess.CalledProcessError: |
1166 | + # Reload failed - either service does not support reload |
1167 | + # or it was not running - restart will fixup most things |
1168 | + _svc_control(service, 'restart') |
1169 | + |
1170 | + |
1171 | +def running(service): |
1172 | + try: |
1173 | + output = subprocess.check_output(['service', service, 'status']) |
1174 | + except subprocess.CalledProcessError: |
1175 | + return False |
1176 | + else: |
1177 | + if ("start/running" in output or |
1178 | + "is running" in output): |
1179 | + return True |
1180 | + else: |
1181 | + return False |
1182 | + |
1183 | + |
1184 | +def is_relation_made(relation, key='private-address'): |
1185 | + for r_id in (relation_ids(relation) or []): |
1186 | + for unit in (relation_list(r_id) or []): |
1187 | + if relation_get(key, rid=r_id, unit=unit): |
1188 | + return True |
1189 | + return False |
1190 | |
1191 | === modified file 'hooks/maas.py' |
1192 | --- hooks/maas.py 2013-02-20 10:10:42 +0000 |
1193 | +++ hooks/maas.py 2013-03-25 14:21:21 +0000 |
1194 | @@ -3,7 +3,7 @@ |
1195 | import json |
1196 | import subprocess |
1197 | |
1198 | -import utils |
1199 | +import lib.utils as utils |
1200 | |
1201 | MAAS_STABLE_PPA = 'ppa:maas-maintainers/stable ' |
1202 | MAAS_PROFILE_NAME = 'maas-juju-hacluster' |
1203 | |
1204 | === modified file 'hooks/pcmk.py' |
1205 | --- hooks/pcmk.py 2013-02-20 10:10:42 +0000 |
1206 | +++ hooks/pcmk.py 2013-03-25 14:21:21 +0000 |
1207 | @@ -1,4 +1,4 @@ |
1208 | -import utils |
1209 | +import lib.utils as utils |
1210 | import commands |
1211 | import subprocess |
1212 | |
1213 | |
1214 | === removed symlink 'hooks/start' |
1215 | === target was u'hooks.py' |
1216 | === removed symlink 'hooks/stop' |
1217 | === target was u'hooks.py' |
The branch looks good to me. However, I believe we need to make sure the pacemaker initscript is being set to defaults and that starts after corosync since it is disabled in packaging.