diff --git a/src/layer.yaml b/src/layer.yaml index d9837b53..1915a32c 100644 --- a/src/layer.yaml +++ b/src/layer.yaml @@ -1,4 +1,5 @@ includes: + - layer:leadership - layer:openstack-api - interface:mysql-shared - interface:rabbitmq diff --git a/src/lib/charm/openstack/octavia.py b/src/lib/charm/openstack/octavia.py index c5d32d04..c52d79cc 100644 --- a/src/lib/charm/openstack/octavia.py +++ b/src/lib/charm/openstack/octavia.py @@ -20,6 +20,8 @@ import charms_openstack.charm import charms_openstack.adapters import charms_openstack.ip as os_ip +import charms.leadership as leadership + import charmhelpers.core.host as ch_host OCTAVIA_DIR = '/etc/octavia' @@ -95,3 +97,7 @@ class OctaviaCharm(charms_openstack.charm.HAOpenStackCharm): OCTAVIA_WEBSERVER_SITE]) ch_host.service_reload('apache2', restart_on_failure=True) + + @charms_openstack.adapters.config_property + def heartbeat_key(self): + return leadership.leader_get('heartbeat-key') diff --git a/src/reactive/octavia_handlers.py b/src/reactive/octavia_handlers.py index 41cb3bf4..42b98a1c 100644 --- a/src/reactive/octavia_handlers.py +++ b/src/reactive/octavia_handlers.py @@ -12,7 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import uuid + import charms.reactive as reactive +import charms.leadership as leadership import charms_openstack.charm as charm @@ -28,9 +31,17 @@ charm.use_defaults( 'update-status') +@reactive.when('leadership.is_leader') +@reactive.when_not('leadership.set.heartbeat-key') +def generate_heartbeat_key(): + """Generate a unique key for ``heartbeat_key`` configuration option.""" + leadership.leader_set({'heartbeat-key': str(uuid.uuid4())}) + + @reactive.when('shared-db.available') @reactive.when('identity-service.available') @reactive.when('amqp.available') +@reactive.when('leadership.set.heartbeat-key') def render(*args): """ Render the configuration for Octavia when all interfaces are available. diff --git a/src/templates/rocky/octavia.conf b/src/templates/rocky/octavia.conf index 60d74f6d..d1fc1ba3 100644 --- a/src/templates/rocky/octavia.conf +++ b/src/templates/rocky/octavia.conf @@ -1,6 +1,9 @@ [DEFAULT] debug = {{ options.debug }} +[health_manager] +heartbeat_key = {{ options.heartbeat_key }} + [database] {% include "parts/database" %} diff --git a/src/tests/bundles/smoke-bionic-rocky.yaml b/src/tests/bundles/smoke-bionic-rocky.yaml index f2401eb5..9c1af3e3 100644 --- a/src/tests/bundles/smoke-bionic-rocky.yaml +++ b/src/tests/bundles/smoke-bionic-rocky.yaml @@ -19,7 +19,7 @@ applications: num_units: 1 octavia: series: bionic - charm: octavia + charm: ../../../octavia num_units: 1 options: openstack-origin: cloud:bionic-rocky diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 7b5dac4f..3616592d 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -20,3 +20,8 @@ sys.path.append('src/lib') # Mock out charmhelpers so that we can test without it. import charms_openstack.test_mocks # noqa charms_openstack.test_mocks.mock_charmhelpers() + +import mock +import charms +charms.leadership = mock.MagicMock() +sys.modules['charms.leadership'] = charms.leadership diff --git a/unit_tests/test_lib_charm_openstack_octavia.py b/unit_tests/test_lib_charm_openstack_octavia.py index 93e13b97..dcd6e84c 100644 --- a/unit_tests/test_lib_charm_openstack_octavia.py +++ b/unit_tests/test_lib_charm_openstack_octavia.py @@ -55,3 +55,12 @@ class TestOctaviaCharm(Helper): self.sp_check_call.assert_called_with(['a2ensite', 'octavia-api']) self.service_reload.assert_called_with( 'apache2', restart_on_failure=True) + + def test_heartbeat_key(self): + self.patch('charms.leadership.leader_get', 'leader_get') + self.leader_get.return_value = None + c = octavia.OctaviaCharm() + self.assertEqual(c.heartbeat_key(), None) + self.leader_get.return_value = 'FAKE-STORED-UUID-STRING' + self.assertEqual(c.heartbeat_key(), 'FAKE-STORED-UUID-STRING') + self.leader_get.assert_called_with('heartbeat-key') diff --git a/unit_tests/test_octavia_handlers.py b/unit_tests/test_octavia_handlers.py index 4c442de5..a24b77f9 100644 --- a/unit_tests/test_octavia_handlers.py +++ b/unit_tests/test_octavia_handlers.py @@ -37,13 +37,16 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks): 'when': { 'render': ('shared-db.available', 'identity-service.available', - 'amqp.available',), + 'amqp.available', + 'leadership.set.heartbeat-key',), 'init_db': ('config.rendered',), 'cluster_connected': ('ha.connected',), + 'generate_heartbeat_key': ('leadership.is_leader',), }, 'when_not': { 'init_db': ('db.synced',), 'cluster_connected': ('ha.available',), + 'generate_heartbeat_key': ('leadership.set.heartbeat-key',), }, } # test that the hooks were registered via the @@ -51,7 +54,7 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks): self.registered_hooks_test_helper(handlers, hook_set, defaults) -class TestRender(test_utils.PatchHelper): +class TestOctaviaHandlers(test_utils.PatchHelper): def setUp(self): super().setUp() @@ -62,6 +65,15 @@ class TestRender(test_utils.PatchHelper): self.octavia_charm self.provide_charm_instance().__exit__.return_value = None + def test_generate_heartbeat_key(self): + self.patch('charms.leadership.leader_set', 'leader_set') + self.patch('uuid.uuid4', 'uuid4') + self.uuid4.return_value = fake_uuid4 = 'FAKE-UUID4-STRING' + handlers.generate_heartbeat_key() + self.leader_set.assert_called_once_with( + {'heartbeat-key': fake_uuid4}) + self.uuid4.assert_called_once_with() + def test_render(self): self.patch('charms.reactive.set_state', 'set_state') handlers.render('arg1', 'arg2')