Merge lp:~stub/charms/trusty/cassandra/wait-for-joining into lp:charms/trusty/cassandra

Proposed by Stuart Bishop
Status: Merged
Merge reported by: Stuart Bishop
Merged at revision: not available
Proposed branch: lp:~stub/charms/trusty/cassandra/wait-for-joining
Merge into: lp:charms/trusty/cassandra
Prerequisite: lp:~stub/charms/trusty/cassandra/ensure-thrift
Diff against target: 116 lines (+36/-17)
2 files modified
hooks/actions.py (+34/-15)
tests/test_actions.py (+2/-2)
To merge this branch: bzr merge lp:~stub/charms/trusty/cassandra/wait-for-joining
Reviewer Review Type Date Requested Status
Cory Johns (community) Needs Information
Review via email: mp+286993@code.launchpad.net

Commit message

Make bootstrap procedure safer

Description of the change

We were previously capping the replication factor of the system_auth keyspace to three. I have since found documentation recommending increasing the replication factor so every node has a copy of this data.

Also, a somewhat undocumented caveat to bootstrap is that, at least when using vnodes, only one node should be JOINING at a time. Keep hold of the lock until the bootstrapping node has become NORMAL, which also neatly stops other nodes from rebooting while it is attempting to stream data.

To post a comment you must log in.
Revision history for this message
Cory Johns (johnsca) wrote :

Overall this looks good, but I'm a bit concerned about the potential for a deadlock in the charm due to the JOINING / NORMAL wait. Would it be possible to add a timeout to that loop that, if exceeded, puts the charm into a "waiting" state and have it re-try on the next update-status hook?

review: Needs Information
Revision history for this message
Stuart Bishop (stub) wrote :

@johnsca Yes, I think that would be best. It may not be a minor operation though, as I need to ensure that other units don't attempt to join until the waiting unit has completed and the charmhelpers.coordinator lock being held only lasts for the duration of the hook. Or perhaps Cassandra is robust enough now in the supported versions that I can reliably add several units to the cluster at once and they will happily block until it is their turn to bootstrap (which would greatly simplify things). The other issue I need to avoid is to keep failed units in their blocked state, but that seems fairly minor. This will of course fall out much more naturally when I get a chance to rewrite the charm for charms.reactive.

It would still be good to land this as is IMO, as a deadlocked unit is preferable to the current behaviour of not noticing the issue and ploughing ahead with operations potentially dangerous to the whole cluster.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/actions.py'
--- hooks/actions.py 2016-02-24 10:02:13 +0000
+++ hooks/actions.py 2016-02-24 10:02:13 +0000
@@ -432,18 +432,13 @@
432def needs_reset_auth_keyspace_replication():432def needs_reset_auth_keyspace_replication():
433 '''Guard for reset_auth_keyspace_replication.'''433 '''Guard for reset_auth_keyspace_replication.'''
434 num_nodes = helpers.num_nodes()434 num_nodes = helpers.num_nodes()
435 n = min(num_nodes, 3)
436 datacenter = hookenv.config()['datacenter']435 datacenter = hookenv.config()['datacenter']
437 with helpers.connect() as session:436 with helpers.connect() as session:
438 strategy_opts = helpers.get_auth_keyspace_replication(session)437 strategy_opts = helpers.get_auth_keyspace_replication(session)
439 rf = int(strategy_opts.get(datacenter, -1))438 rf = int(strategy_opts.get(datacenter, -1))
440 hookenv.log('system_auth rf={!r}'.format(strategy_opts))439 hookenv.log('system_auth rf={!r}'.format(strategy_opts))
441 # If the node count has increased, we will change the rf.440 # If the node count has changed, we should change the rf.
442 # If the node count is decreasing, we do nothing as the service441 return rf != num_nodes
443 # may be being destroyed.
444 if rf < n:
445 return True
446 return False
447442
448443
449@leader_only444@leader_only
@@ -453,23 +448,23 @@
453def reset_auth_keyspace_replication():448def reset_auth_keyspace_replication():
454 # Cassandra requires you to manually set the replication factor of449 # Cassandra requires you to manually set the replication factor of
455 # the system_auth keyspace, to ensure availability and redundancy.450 # the system_auth keyspace, to ensure availability and redundancy.
456 # We replication factor in this service's DC can be no higher than451 # The recommendation is to set the replication factor so that every
457 # the number of bootstrapped nodes. We also cap this at 3 to ensure452 # node has a copy.
458 # we don't have too many seeds.
459 num_nodes = helpers.num_nodes()453 num_nodes = helpers.num_nodes()
460 n = min(num_nodes, 3)
461 datacenter = hookenv.config()['datacenter']454 datacenter = hookenv.config()['datacenter']
462 with helpers.connect() as session:455 with helpers.connect() as session:
463 strategy_opts = helpers.get_auth_keyspace_replication(session)456 strategy_opts = helpers.get_auth_keyspace_replication(session)
464 rf = int(strategy_opts.get(datacenter, -1))457 rf = int(strategy_opts.get(datacenter, -1))
465 hookenv.log('system_auth rf={!r}'.format(strategy_opts))458 hookenv.log('system_auth rf={!r}'.format(strategy_opts))
466 if rf != n:459 if rf != num_nodes:
467 strategy_opts['class'] = 'NetworkTopologyStrategy'460 strategy_opts['class'] = 'NetworkTopologyStrategy'
468 strategy_opts[datacenter] = n461 strategy_opts[datacenter] = num_nodes
469 if 'replication_factor' in strategy_opts:462 if 'replication_factor' in strategy_opts:
470 del strategy_opts['replication_factor']463 del strategy_opts['replication_factor']
471 helpers.set_auth_keyspace_replication(session, strategy_opts)464 helpers.set_auth_keyspace_replication(session, strategy_opts)
472 helpers.repair_auth_keyspace()465 if rf < num_nodes:
466 # Increasing rf, need to run repair.
467 helpers.repair_auth_keyspace()
473 helpers.set_active()468 helpers.set_active()
474469
475470
@@ -565,7 +560,9 @@
565560
566 Per documented procedure for adding new units to a cluster, wait 2561 Per documented procedure for adding new units to a cluster, wait 2
567 minutes if the unit has just bootstrapped to ensure other units562 minutes if the unit has just bootstrapped to ensure other units
568 do not attempt bootstrap too soon.563 do not attempt bootstrap too soon. Also, wait until completed joining
564 to ensure we keep the lock and ensure other nodes don't restart or
565 bootstrap.
569 '''566 '''
570 if not helpers.is_bootstrapped():567 if not helpers.is_bootstrapped():
571 if coordinator.relid is not None:568 if coordinator.relid is not None:
@@ -573,6 +570,28 @@
573 hookenv.log('Post-bootstrap 2 minute delay')570 hookenv.log('Post-bootstrap 2 minute delay')
574 time.sleep(120) # Must wait 2 minutes between bootstrapping nodes.571 time.sleep(120) # Must wait 2 minutes between bootstrapping nodes.
575572
573 join_msg_set = False
574 while True:
575 status = helpers.get_node_status()
576 if status == 'NORMAL':
577 break
578 elif status == 'JOINING':
579 if not join_msg_set:
580 helpers.status_set('maintenance', 'Still joining cluster')
581 join_msg_set = True
582 time.sleep(10)
583 continue
584 else:
585 if status is None:
586 helpers.status_set('blocked',
587 'Unexpectedly shutdown during '
588 'bootstrap')
589 else:
590 helpers.status_set('blocked',
591 'Failed to bootstrap ({})'
592 ''.format(status))
593 raise SystemExit(0)
594
576 # Unconditionally call this to publish the bootstrapped flag to595 # Unconditionally call this to publish the bootstrapped flag to
577 # the peer relation, as the first unit was bootstrapped before596 # the peer relation, as the first unit was bootstrapped before
578 # the peer relation existed.597 # the peer relation existed.
579598
=== modified file 'tests/test_actions.py'
--- tests/test_actions.py 2016-02-24 10:02:13 +0000
+++ tests/test_actions.py 2016-02-24 10:02:13 +0000
@@ -535,7 +535,7 @@
535 connect().__enter__.return_value = sentinel.session535 connect().__enter__.return_value = sentinel.session
536 connect().__exit__.return_value = False536 connect().__exit__.return_value = False
537537
538 num_nodes.return_value = 4538 num_nodes.return_value = 3
539 get_auth_ks_rep.return_value = {'another': '8',539 get_auth_ks_rep.return_value = {'another': '8',
540 'mydc': '3'}540 'mydc': '3'}
541 self.assertFalse(actions.needs_reset_auth_keyspace_replication())541 self.assertFalse(actions.needs_reset_auth_keyspace_replication())
@@ -565,7 +565,7 @@
565 actions.reset_auth_keyspace_replication('')565 actions.reset_auth_keyspace_replication('')
566 set_auth_ks_rep.assert_called_once_with(566 set_auth_ks_rep.assert_called_once_with(
567 sentinel.session,567 sentinel.session,
568 {'class': 'NetworkTopologyStrategy', 'another': '8', 'mydc': 3})568 {'class': 'NetworkTopologyStrategy', 'another': '8', 'mydc': 4})
569 repair.assert_called_once_with()569 repair.assert_called_once_with()
570 set_active.assert_called_once_with()570 set_active.assert_called_once_with()
571571

Subscribers

People subscribed via source and target branches

to all changes: