Add forwards compat with k8s operator
The new keystone-k8s operator uses the application data bag and more up-to-date key names for endpoint and authentication information. Check for this information and then fallback to the existing keystone charm unit data bag data set if not found. Update register_endpoints to also provide new application data bag JSON encoded data when this method is called from a lead unit. This relies on the type and description of the endpoint being provided which will require a charm change on adoption. Change-Id: I921d173c64b12c35f5ffc17270a0fc2bb83891c4
This commit is contained in:
parent
9ad5cade97
commit
1a5142bac6
93
requires.py
93
requires.py
@ -23,6 +23,7 @@ class KeystoneAutoAccessors(type):
|
|||||||
Metaclass that converts fields referenced by ``auto_accessors`` into
|
Metaclass that converts fields referenced by ``auto_accessors`` into
|
||||||
accessor methods with very basic doc strings.
|
accessor methods with very basic doc strings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, name, parents, dct):
|
def __new__(cls, name, parents, dct):
|
||||||
for field in dct.get('auto_accessors', []):
|
for field in dct.get('auto_accessors', []):
|
||||||
meth_name = field.replace('-', '_')
|
meth_name = field.replace('-', '_')
|
||||||
@ -37,24 +38,64 @@ class KeystoneAutoAccessors(type):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _accessor(field):
|
def _accessor(field):
|
||||||
def __accessor(self):
|
def _accessor_internal(self):
|
||||||
return self.all_joined_units.received.get(field)
|
# Use remapped or transposed key for application
|
||||||
return __accessor
|
# data bag lookup for forwards compat
|
||||||
|
app_field = self._forward_compat_remaps.get(
|
||||||
|
field,
|
||||||
|
field.replace('_', '-')
|
||||||
|
)
|
||||||
|
return self.relations[0].received_app_raw.get(
|
||||||
|
app_field,
|
||||||
|
self.all_joined_units.received.get(field)
|
||||||
|
)
|
||||||
|
return _accessor_internal
|
||||||
|
|
||||||
|
|
||||||
class KeystoneRequires(reactive.Endpoint, metaclass=KeystoneAutoAccessors):
|
class KeystoneRequires(reactive.Endpoint, metaclass=KeystoneAutoAccessors):
|
||||||
|
|
||||||
auto_accessors = ['service_host', 'service_protocol',
|
auto_accessors = [
|
||||||
'service_port', 'service_tenant', 'service_username',
|
'service_host',
|
||||||
'service_password', 'service_tenant_id', 'auth_host',
|
'service_protocol',
|
||||||
'auth_protocol', 'auth_port', 'admin_token', 'ssl_key',
|
'service_port',
|
||||||
'ca_cert', 'ssl_cert', 'https_keystone',
|
'service_tenant',
|
||||||
'ssl_cert_admin', 'ssl_cert_internal',
|
'service_username',
|
||||||
'ssl_cert_public', 'ssl_key_admin', 'ssl_key_internal',
|
'service_password',
|
||||||
'ssl_key_public', 'api_version', 'service_domain',
|
'service_tenant_id',
|
||||||
'service_domain_id', 'ep_changed',
|
'auth_host',
|
||||||
'admin_domain_id', 'admin_user_id', 'admin_project_id',
|
'auth_protocol',
|
||||||
'service_type']
|
'auth_port',
|
||||||
|
'admin_token',
|
||||||
|
'ssl_key',
|
||||||
|
'ca_cert',
|
||||||
|
'ssl_cert',
|
||||||
|
'https_keystone',
|
||||||
|
'ssl_cert_admin',
|
||||||
|
'ssl_cert_internal',
|
||||||
|
'ssl_cert_public',
|
||||||
|
'ssl_key_admin',
|
||||||
|
'ssl_key_internal',
|
||||||
|
'ssl_key_public',
|
||||||
|
'api_version',
|
||||||
|
'service_domain',
|
||||||
|
'service_domain_id',
|
||||||
|
'ep_changed',
|
||||||
|
'admin_domain_id',
|
||||||
|
'admin_user_id',
|
||||||
|
'admin_project_id',
|
||||||
|
'service_type',
|
||||||
|
'public-auth-url',
|
||||||
|
'internal-auth-url',
|
||||||
|
'admin-auth-url',
|
||||||
|
]
|
||||||
|
|
||||||
|
_forward_compat_remaps = {
|
||||||
|
'admin_user': 'admin-user-name',
|
||||||
|
'service_username': 'service-user-name',
|
||||||
|
'service_tenant': 'service-project-name',
|
||||||
|
'service_tenant_id': 'service-project-id',
|
||||||
|
'service_domain': 'service-domain-name',
|
||||||
|
}
|
||||||
|
|
||||||
@reactive.when('endpoint.{endpoint_name}.joined')
|
@reactive.when('endpoint.{endpoint_name}.joined')
|
||||||
def joined(self):
|
def joined(self):
|
||||||
@ -146,7 +187,9 @@ class KeystoneRequires(reactive.Endpoint, metaclass=KeystoneAutoAccessors):
|
|||||||
|
|
||||||
def register_endpoints(self, service, region, public_url, internal_url,
|
def register_endpoints(self, service, region, public_url, internal_url,
|
||||||
admin_url, requested_roles=None,
|
admin_url, requested_roles=None,
|
||||||
add_role_to_admin=None):
|
add_role_to_admin=None,
|
||||||
|
service_type=None,
|
||||||
|
service_description=None):
|
||||||
"""
|
"""
|
||||||
Register this service with keystone
|
Register this service with keystone
|
||||||
"""
|
"""
|
||||||
@ -166,6 +209,26 @@ class KeystoneRequires(reactive.Endpoint, metaclass=KeystoneAutoAccessors):
|
|||||||
for relation in self.relations:
|
for relation in self.relations:
|
||||||
relation.to_publish_raw.update(relation_info)
|
relation.to_publish_raw.update(relation_info)
|
||||||
|
|
||||||
|
# NOTE: forwards compatible data presentation for keystone-k8s
|
||||||
|
if all((service_type,
|
||||||
|
service_description,
|
||||||
|
reactive.is_flag_set('leadership.is_leader'),)):
|
||||||
|
application_info = {
|
||||||
|
'region': region,
|
||||||
|
'service-endpoints': json.dumps([
|
||||||
|
{
|
||||||
|
'service_name': service,
|
||||||
|
'type': service_type,
|
||||||
|
'description': service_description,
|
||||||
|
'internal_url': internal_url,
|
||||||
|
'admin_url': admin_url,
|
||||||
|
'public_url': public_url,
|
||||||
|
}
|
||||||
|
], sort_keys=True)
|
||||||
|
}
|
||||||
|
for relation in self.relations:
|
||||||
|
relation.to_publish_app_raw.update(application_info)
|
||||||
|
|
||||||
def request_keystone_endpoint_information(self):
|
def request_keystone_endpoint_information(self):
|
||||||
self.register_endpoints('None', 'None', 'None', 'None', 'None')
|
self.register_endpoints('None', 'None', 'None', 'None', 'None')
|
||||||
|
|
||||||
|
2
tox.ini
2
tox.ini
@ -81,4 +81,4 @@ commands = {posargs}
|
|||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# E402 ignore necessary for path append before sys module import in actions
|
# E402 ignore necessary for path append before sys module import in actions
|
||||||
ignore = E402
|
ignore = E402 W503
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
@ -19,6 +20,35 @@ import charms_openstack.test_utils as test_utils
|
|||||||
|
|
||||||
_hook_args = {}
|
_hook_args = {}
|
||||||
|
|
||||||
|
IDENTITY_APP_DATA = {
|
||||||
|
'api-version': '3',
|
||||||
|
'auth-host': 'authhost',
|
||||||
|
'auth-port': '5000',
|
||||||
|
'auth-protocol': 'http',
|
||||||
|
'internal-host': 'internalhost',
|
||||||
|
'internal-port': '5000',
|
||||||
|
'internal-protocol': 'http',
|
||||||
|
'service-host': 'servicehost',
|
||||||
|
'service-port': '5000',
|
||||||
|
'service-protocol': 'http',
|
||||||
|
'admin-domain-name': 'admin-domain',
|
||||||
|
'admin-domain-id': 'ca9e66dd-920c-493c-8ebd-dcc893afcc3b',
|
||||||
|
'admin-project-name': 'admin',
|
||||||
|
'admin-project-id': '5c9fd12c-87eb-4688-931a-05da83db14ad',
|
||||||
|
'admin-user-name': 'admin',
|
||||||
|
'admin-user-id': 'cc28fa26-70bc-4acb-97a4-5614799257bb',
|
||||||
|
'service-domain-name': 'service-domain',
|
||||||
|
'service-domain-id': '8fa8e4c1-b9f6-44ae-b646-0626d44786c2',
|
||||||
|
'service-project-name': 'services',
|
||||||
|
'service-project-id': '0626e4d8-0846-4fd5-98c9-324fbbe24301',
|
||||||
|
'service-user-name': 'gnocchi',
|
||||||
|
'service-user-id': 'fa8c4a9a-f97c-41e7-a204-73571c5a7b51',
|
||||||
|
'service-password': 'foobar',
|
||||||
|
'internal-auth-url': 'http://internalhost:80/keystone',
|
||||||
|
'admin-auth-url': 'http://adminhost:80/keystone',
|
||||||
|
'public-auth-url': 'http://publichost:80/keystone',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestKeystoneRequires(test_utils.PatchHelper):
|
class TestKeystoneRequires(test_utils.PatchHelper):
|
||||||
|
|
||||||
@ -73,6 +103,25 @@ class TestKeystoneRequires(test_utils.PatchHelper):
|
|||||||
self.service_tenant.return_value = None
|
self.service_tenant.return_value = None
|
||||||
assert self.target.base_data_complete() is False
|
assert self.target.base_data_complete() is False
|
||||||
|
|
||||||
|
def test_app_data_complete(self):
|
||||||
|
relation = mock.MagicMock()
|
||||||
|
relation.received_app_raw.get.side_effect = (
|
||||||
|
lambda k, d: IDENTITY_APP_DATA.get(k, d)
|
||||||
|
)
|
||||||
|
self.target._relations = [relation]
|
||||||
|
self.assertEqual(self.target.service_host(), 'servicehost')
|
||||||
|
self.assertEqual(self.target.auth_host(), 'authhost')
|
||||||
|
self.assertEqual(
|
||||||
|
self.target.public_auth_url(), 'http://publichost:80/keystone')
|
||||||
|
self.assertEqual(self.target.service_tenant(), 'services')
|
||||||
|
self.assertEqual(self.target.service_password(), 'foobar')
|
||||||
|
self.assertEqual(
|
||||||
|
self.target.service_tenant_id(),
|
||||||
|
'0626e4d8-0846-4fd5-98c9-324fbbe24301')
|
||||||
|
self.assertTrue(self.target.base_data_complete())
|
||||||
|
self.assertFalse(self.target.ssl_data_complete())
|
||||||
|
self.assertFalse(self.target.ssl_data_complete_legacy())
|
||||||
|
|
||||||
def test_ssl_data_complete(self):
|
def test_ssl_data_complete(self):
|
||||||
self.patch_target('ssl_cert_admin', '1')
|
self.patch_target('ssl_cert_admin', '1')
|
||||||
self.patch_target('ssl_cert_internal', '2')
|
self.patch_target('ssl_cert_internal', '2')
|
||||||
@ -152,10 +201,14 @@ class TestKeystoneRequires(test_utils.PatchHelper):
|
|||||||
'endpoint.some-relation.changed')
|
'endpoint.some-relation.changed')
|
||||||
|
|
||||||
def test_register_endpoints(self):
|
def test_register_endpoints(self):
|
||||||
|
self.patch_object(requires.reactive, 'is_flag_set')
|
||||||
|
self.is_flag_set.return_value = True
|
||||||
relation = mock.MagicMock()
|
relation = mock.MagicMock()
|
||||||
self.patch_target('_relations')
|
self.patch_target('_relations')
|
||||||
self._relations.__iter__.return_value = [relation]
|
self._relations.__iter__.return_value = [relation]
|
||||||
self.target.register_endpoints('s', 'r', 'p_url', 'i_url', 'a_url')
|
self.target.register_endpoints(
|
||||||
|
's', 'r', 'p_url', 'i_url', 'a_url',
|
||||||
|
service_type='stype', service_description='sdesc')
|
||||||
result = {
|
result = {
|
||||||
'service': 's',
|
'service': 's',
|
||||||
'public_url': 'p_url',
|
'public_url': 'p_url',
|
||||||
@ -164,6 +217,21 @@ class TestKeystoneRequires(test_utils.PatchHelper):
|
|||||||
'region': 'r',
|
'region': 'r',
|
||||||
}
|
}
|
||||||
relation.to_publish_raw.update.assert_called_once_with(result)
|
relation.to_publish_raw.update.assert_called_once_with(result)
|
||||||
|
# This should only happen when the charm is the leader and
|
||||||
|
# register_endpoints is called with type and description
|
||||||
|
# information.
|
||||||
|
relation.to_publish_app_raw.update.assert_called_once_with({
|
||||||
|
'region': 'r',
|
||||||
|
'service-endpoints': json.dumps([{
|
||||||
|
"admin_url": "a_url",
|
||||||
|
"description": "sdesc",
|
||||||
|
"internal_url": "i_url",
|
||||||
|
"public_url": "p_url",
|
||||||
|
"service_name": "s",
|
||||||
|
"type": "stype"}],
|
||||||
|
sort_keys=True
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
def test_register_endpoints_requested_roles(self):
|
def test_register_endpoints_requested_roles(self):
|
||||||
relation = mock.MagicMock()
|
relation = mock.MagicMock()
|
||||||
|
Loading…
Reference in New Issue
Block a user