diff --git a/config.yaml b/config.yaml index f3754f19..c0067eb8 100644 --- a/config.yaml +++ b/config.yaml @@ -166,3 +166,9 @@ options: order for this charm to function correctly, the privacy extension must be disabled and a non-temporary address must be configured/available on your network interface. + min-cluster-size: + type: int + default: + description: | + Minimum number of units expected to exist before charm will attempt to + form a rabbitmq cluster. diff --git a/hooks/rabbitmq_server_relations.py b/hooks/rabbitmq_server_relations.py index 713d9bd8..9dcc96bf 100755 --- a/hooks/rabbitmq_server_relations.py +++ b/hooks/rabbitmq_server_relations.py @@ -194,6 +194,26 @@ def amqp_changed(relation_id=None, remote_unit=None): relation_settings=relation_settings) +def is_sufficient_peers(): + """If min-cluster-size has been provided, check that we have sufficient + number of peers to proceed with creating rabbitmq cluster. + """ + min_size = config('min-cluster-size') + if min_size: + size = 0 + for rid in relation_ids('cluster'): + size = len(related_units(rid)) + + # Include this unit + size += 1 + if min_size > size: + log("Insufficient number of peer units to form cluster " + "(expected=%s, got=%s)" % (min_size, size), level=INFO) + return False + + return True + + @hooks.hook('cluster-relation-joined') def cluster_joined(relation_id=None): if config('prefer-ipv6'): @@ -240,8 +260,13 @@ def cluster_joined(relation_id=None): log('erlang cookie missing from %s' % rabbit.COOKIE_PATH, level=ERROR) return - cookie = open(rabbit.COOKIE_PATH, 'r').read().strip() - peer_store('cookie', cookie) + + if not is_sufficient_peers(): + return + + if is_elected_leader('res_rabbitmq_vip'): + cookie = open(rabbit.COOKIE_PATH, 'r').read().strip() + peer_store('cookie', cookie) @hooks.hook('cluster-relation-changed') @@ -262,6 +287,11 @@ def cluster_changed(): whitelist = [a for a in rdata.keys() if a not in blacklist] peer_echo(includes=whitelist) + if not is_sufficient_peers(): + # Stop rabbit until leader has finished configuring + service_stop('rabbitmq-server') + return + # sync the cookie with peers if necessary update_cookie() @@ -276,6 +306,7 @@ def cluster_changed(): if rabbit.cluster_with(): # resync nrpe user after clustering update_nrpe_checks() + # If cluster has changed peer db may have changed so run amqp_changed # to sync any changes for rid in relation_ids('amqp'): diff --git a/unit_tests/test_rabbitmq_server_relations.py b/unit_tests/test_rabbitmq_server_relations.py index 4f3b881c..b7af05f6 100644 --- a/unit_tests/test_rabbitmq_server_relations.py +++ b/unit_tests/test_rabbitmq_server_relations.py @@ -129,3 +129,20 @@ class RelationUtil(TestCase): mock_peer_store_and_set.assert_called_with( relation_settings={'private-address': ipv6_addr}, relation_id=None) + + @patch.object(rabbitmq_server_relations, 'related_units') + @patch.object(rabbitmq_server_relations, 'relation_ids') + @patch.object(rabbitmq_server_relations, 'config') + def test_is_sufficient_peers(self, mock_config, mock_relation_ids, + mock_related_units): + _config = {'min-cluster-size': None} + mock_config.side_effect = lambda key: _config.get(key) + self.assertTrue(rabbitmq_server_relations.is_sufficient_peers()) + + mock_relation_ids.return_value = ['cluster:0'] + mock_related_units.return_value = ['test/0'] + _config = {'min-cluster-size': 3} + self.assertFalse(rabbitmq_server_relations.is_sufficient_peers()) + + mock_related_units.return_value = ['test/0', 'test/1'] + self.assertTrue(rabbitmq_server_relations.is_sufficient_peers())