endpoint notification: provide full endpoint

When a remote service requests notification about changes to
endpoints, provide the full detail on each endpoint rather than
just a checksum of the internal/admin/public URL's.

This allows consuming services which require explicit configuration
of service endpoint URL's to configure everything via their
relation to keystone rather than directly relating to all required
services.

Change-Id: I39b6e3df17e44c801f5f6bb122407623cbf1c937
This commit is contained in:
James Page 2020-03-05 09:53:21 +00:00
parent 6ca8e9b508
commit f984c4ec9b
5 changed files with 80 additions and 34 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ func-results.json
__pycache__
.stestr
.idea
.pydevproject

View File

@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">python3</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/keystone/hooks</path>
<path>/keystone/unit_tests</path>
<path>/${PROJECT_DIR_NAME}</path>
</pydev_pathproperty>
</pydev_project>

View File

@ -128,6 +128,7 @@ from keystone_utils import (
remove_old_packages,
stop_manager_instance,
assemble_endpoints,
endpoints_dict,
endpoints_checksum,
)
@ -426,7 +427,8 @@ def db_departed_or_broken():
@hooks.hook('identity-service-relation-changed')
@restart_on_change(restart_map(), restart_functions=restart_function_map())
def identity_changed(relation_id=None, remote_unit=None):
notifications = {}
notifications_checksums = {}
notifications_endpoints = {}
if is_elected_leader(CLUSTER_RES):
if not is_db_ready():
log("identity-service-relation-changed hook fired before db "
@ -454,7 +456,8 @@ def identity_changed(relation_id=None, remote_unit=None):
service = settings.get('service')
if service:
key = '%s-endpoint-changed' % service
notifications[key] = endpoints_checksum(settings)
notifications_endpoints[key] = endpoints_dict(settings)
notifications_checksums[key] = endpoints_checksum(settings)
else:
# Some services don't set their name in the 'service' key in the
# relation, for those their name is calculated from the prefix of
@ -466,7 +469,12 @@ def identity_changed(relation_id=None, remote_unit=None):
if single.issubset(endpoints[ep]):
key = '%s-endpoint-changed' % ep
log('endpoint: %s' % ep)
notifications[key] = endpoints_checksum(endpoints[ep])
notifications_endpoints[key] = (
endpoints_dict(endpoints[ep])
)
notifications_checksums[key] = (
endpoints_checksum(endpoints[ep])
)
else:
# Each unit needs to set the db information otherwise if the unit
# with the info dies the settings die with it Bug# 1355848
@ -479,8 +487,9 @@ def identity_changed(relation_id=None, remote_unit=None):
log('Deferring identity_changed() to service leader.')
if notifications:
send_notifications(notifications)
if notifications_endpoints or notifications_checksums:
send_notifications(notifications_checksums,
notifications_endpoints)
@hooks.hook('identity-credentials-relation-joined',

View File

@ -2003,7 +2003,13 @@ def send_id_service_notifications(data):
"""
id_svc_rel_ids = relation_ids('identity-service')
for rid in id_svc_rel_ids:
changed = {}
changed = relation_get(unit=local_unit(),
rid=rid,
attribute='ep_changed')
if changed:
changed = json.loads(changed)
else:
changed = {}
for unit in related_units(rid):
rs = relation_get(
unit=unit,
@ -2021,9 +2027,9 @@ def send_id_service_notifications(data):
'ep_changed': json.dumps(changed, sort_keys=True)})
def send_notifications(data, force=False):
send_id_notifications(data, force=force)
send_id_service_notifications(data)
def send_notifications(checksum_data, endpoint_data, force=False):
send_id_notifications(checksum_data, force=force)
send_id_service_notifications(endpoint_data)
def send_id_notifications(data, force=False):
@ -2565,3 +2571,18 @@ def endpoints_checksum(settings):
csum.update(settings.get('admin_url', None).encode('utf-8'))
csum.update(settings.get('internal_url', None).encode('utf-8'))
return csum.hexdigest()
def endpoints_dict(settings):
"""
Build a dictionary of endpoint types using settings
:param settings: dict with urls registered in keystone.
:returns: dict of endpoints from settings
"""
endpoints = {
'public': settings.get('public_url', None),
'admin': settings.get('admin_url', None),
'internal': settings.get('internal_url', None),
}
return endpoints

View File

@ -818,29 +818,28 @@ class TestKeystoneUtils(CharmTestCase):
def test_send_id_notifications(self, mock_is_elected_leader,
mock_relation_ids, mock_relation_get,
mock_relation_set, mock_uuid):
checksums = {'foo-endpoint-changed': 1}
relation_id = 'testrel:0'
mock_uuid.uuid4.return_value = '1234'
mock_relation_ids.return_value = [relation_id]
mock_is_elected_leader.return_value = False
utils.send_notifications({'foo-endpoint-changed': 1})
utils.send_id_notifications(checksums)
self.assertFalse(mock_relation_set.called)
mock_is_elected_leader.return_value = True
utils.send_notifications({})
utils.send_id_notifications({})
self.assertFalse(mock_relation_set.called)
settings = {'foo-endpoint-changed': 1}
utils.send_notifications(settings)
utils.send_id_notifications(checksums)
self.assertTrue(mock_relation_set.called)
mock_relation_set.assert_called_once_with(relation_id=relation_id,
relation_settings=settings)
relation_settings=checksums)
mock_relation_set.reset_mock()
settings = {'foo-endpoint-changed': 1}
utils.send_notifications(settings, force=True)
utils.send_id_notifications(checksums, force=True)
self.assertTrue(mock_relation_set.called)
settings['trigger'] = '1234'
checksums['trigger'] = '1234'
mock_relation_set.assert_called_once_with(relation_id=relation_id,
relation_settings=settings)
relation_settings=checksums)
@patch.object(utils, 'relation_ids')
@patch.object(utils, 'related_units')
@ -873,7 +872,9 @@ class TestKeystoneUtils(CharmTestCase):
'glance/0': {
'admin_url': 'http://172.20.0.32:9292'},
'glance/1': {},
'glance/2': {}}
'glance/2': {},
'keystone/0': {}
}
def _relation_get(unit, rid, attribute):
return id_svc_rel_data[unit].get(attribute)
@ -881,29 +882,40 @@ class TestKeystoneUtils(CharmTestCase):
mock_relation_ids.return_value = id_svc_rel_units.keys()
mock_related_units.side_effect = _related_units
mock_relation_get.side_effect = _relation_get
self.local_unit.return_value = 'keystone/0'
# Check all services subscribed to placement changes are notified.
mock_relation_set.reset_mock()
utils.send_id_service_notifications(
{'placement-endpoint-changed': '4d0633ee'})
{'placement-endpoint-changed': {"internal": "http://demo.com"}})
mock_relation_set.assert_called_once_with(
relation_id='identity-service:2',
relation_settings={
'ep_changed': '{"placement": "4d0633ee"}'})
'ep_changed':
'{"placement": {"internal": "http://demo.com"}}'
}
)
# Check all services subscribed to neutron changes are notified.
mock_relation_set.reset_mock()
utils.send_id_service_notifications(
{'neutron-endpoint-changed': '1c261658'})
{'neutron-endpoint-changed': {"internal": "http://demo.com"}})
expected_rel_set_calls = [
call(
relation_id='identity-service:1',
relation_settings={
'ep_changed': '{"neutron": "1c261658"}'}),
'ep_changed':
'{"neutron": {"internal": "http://demo.com"}}'
}
),
call(
relation_id='identity-service:2',
relation_settings={
'ep_changed': '{"neutron": "1c261658"}'})]
'ep_changed':
'{"neutron": {"internal": "http://demo.com"}}'
}
)
]
mock_relation_set.assert_has_calls(
expected_rel_set_calls,
any_order=True)
@ -911,19 +923,26 @@ class TestKeystoneUtils(CharmTestCase):
# Check multiple ep changes with app subscribing to multiple eps
mock_relation_set.reset_mock()
utils.send_id_service_notifications(
{'neutron-endpoint-changed': '1c261658',
'placement-endpoint-changed': '4d0633ee'})
{'neutron-endpoint-changed': {"internal": "http://demo.com"},
'placement-endpoint-changed': {"internal": "http://demo.com"}})
expected_rel_set_calls = [
call(
relation_id='identity-service:1',
relation_settings={
'ep_changed': '{"neutron": "1c261658"}'}),
'ep_changed':
'{"neutron": {"internal": "http://demo.com"}}'
}
),
call(
relation_id='identity-service:2',
relation_settings={
'ep_changed': (
'{"neutron": "1c261658", '
'"placement": "4d0633ee"}')})]
'{"neutron": {"internal": "http://demo.com"}, '
'"placement": {"internal": "http://demo.com"}}'
)
}
)
]
mock_relation_set.assert_has_calls(
expected_rel_set_calls,
any_order=True)