
KeystoneRequires.ep_changed() now returns a dictionary rather than json. As a result KeystoneRequires.endpoint_checksums now throws an exception when it tries to decode the return from ep_changed. Change-Id: I440104679c900ce8b67ff1fca1d0ce003e5f0ef4
277 lines
9.5 KiB
Python
277 lines
9.5 KiB
Python
#!/usr/bin/python
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import base64
|
|
import json
|
|
|
|
import charms.reactive as reactive
|
|
|
|
|
|
# NOTE: fork of relations.AutoAccessors for forwards compat behaviour
|
|
class KeystoneAutoAccessors(type):
|
|
"""
|
|
Metaclass that converts fields referenced by ``auto_accessors`` into
|
|
accessor methods with very basic doc strings.
|
|
"""
|
|
|
|
def __new__(cls, name, parents, dct):
|
|
for field in dct.get('auto_accessors', []):
|
|
meth_name = field.replace('-', '_')
|
|
meth = cls._accessor(field)
|
|
meth.__name__ = meth_name
|
|
meth.__module__ = dct.get('__module__')
|
|
meth.__doc__ = 'Get the %s, if available, or None.' % field
|
|
dct[meth_name] = meth
|
|
return super(KeystoneAutoAccessors, cls).__new__(
|
|
cls, name, parents, dct
|
|
)
|
|
|
|
@staticmethod
|
|
def _accessor(field):
|
|
def _accessor_internal(self):
|
|
# Use remapped or transposed key for application
|
|
# 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):
|
|
|
|
auto_accessors = [
|
|
'service_host',
|
|
'service_protocol',
|
|
'service_port',
|
|
'service_tenant',
|
|
'service_username',
|
|
'service_password',
|
|
'service_tenant_id',
|
|
'auth_host',
|
|
'auth_protocol',
|
|
'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')
|
|
def joined(self):
|
|
reactive.set_flag(self.expand_name('{endpoint_name}.connected'))
|
|
|
|
@reactive.when('endpoint.{endpoint_name}.changed')
|
|
def changed(self):
|
|
self.update_flags()
|
|
reactive.clear_flag(
|
|
self.expand_name(
|
|
'endpoint.{endpoint_name}.changed'))
|
|
|
|
def update_flags(self):
|
|
if self.base_data_complete():
|
|
reactive.set_flag(self.expand_name('{endpoint_name}.available'))
|
|
reactive.set_flag(
|
|
self.expand_name('{endpoint_name}.available.auth'))
|
|
if self.ssl_data_complete():
|
|
reactive.set_flag(
|
|
self.expand_name('{endpoint_name}.available.ssl'))
|
|
else:
|
|
reactive.clear_flag(
|
|
self.expand_name('{endpoint_name}.available.ssl'))
|
|
if self.ssl_data_complete_legacy():
|
|
reactive.set_flag(
|
|
self.expand_name('{endpoint_name}.available.ssl_legacy'))
|
|
else:
|
|
reactive.clear_flag(
|
|
self.expand_name('{endpoint_name}.available.ssl_legacy'))
|
|
else:
|
|
reactive.clear_flag(
|
|
self.expand_name('{endpoint_name}.available'))
|
|
reactive.clear_flag(
|
|
self.expand_name('{endpoint_name}.available.ssl'))
|
|
reactive.clear_flag(
|
|
self.expand_name('{endpoint_name}.available.ssl_legacy'))
|
|
reactive.clear_flag(
|
|
self.expand_name('{endpoint_name}.available.auth'))
|
|
|
|
@reactive.when('endpoint.{endpoint_name}.departed')
|
|
def departed(self):
|
|
self.update_flags()
|
|
reactive.clear_flag(
|
|
self.expand_name(
|
|
'endpoint.{endpoint_name}.departed'))
|
|
|
|
def base_data_complete(self):
|
|
data = {
|
|
'service_host': self.service_host(),
|
|
'service_protocol': self.service_protocol(),
|
|
'service_port': self.service_port(),
|
|
'auth_host': self.auth_host(),
|
|
'auth_protocol': self.auth_protocol(),
|
|
'auth_port': self.auth_port(),
|
|
'service_tenant': self.service_tenant(),
|
|
'service_username': self.service_username(),
|
|
'service_password': self.service_password(),
|
|
'service_tenant_id': self.service_tenant_id(),
|
|
}
|
|
if all(data.values()):
|
|
return True
|
|
return False
|
|
|
|
def ssl_data_complete(self):
|
|
data = {
|
|
'ssl_cert_admin': self.ssl_cert_admin(),
|
|
'ssl_cert_internal': self.ssl_cert_internal(),
|
|
'ssl_cert_public': self.ssl_cert_public(),
|
|
'ssl_key_admin': self.ssl_key_admin(),
|
|
'ssl_key_internal': self.ssl_key_internal(),
|
|
'ssl_key_public': self.ssl_key_public(),
|
|
'ca_cert': self.ca_cert(),
|
|
}
|
|
for value in data.values():
|
|
if not value or value == '__null__':
|
|
return False
|
|
return True
|
|
|
|
def ssl_data_complete_legacy(self):
|
|
data = {
|
|
'ssl_key': self.ssl_key(),
|
|
'ssl_cert': self.ssl_cert(),
|
|
'ca_cert': self.ca_cert(),
|
|
}
|
|
for value in data.values():
|
|
if not value or value == '__null__':
|
|
return False
|
|
return True
|
|
|
|
def register_endpoints(self, service, region, public_url, internal_url,
|
|
admin_url, requested_roles=None,
|
|
add_role_to_admin=None,
|
|
service_type=None,
|
|
service_description=None):
|
|
"""
|
|
Register this service with keystone
|
|
"""
|
|
relation_info = {
|
|
'service': service,
|
|
'public_url': public_url,
|
|
'internal_url': internal_url,
|
|
'admin_url': admin_url,
|
|
'region': region,
|
|
}
|
|
if requested_roles:
|
|
relation_info.update(
|
|
{'requested_roles': ','.join(requested_roles)})
|
|
if add_role_to_admin:
|
|
relation_info.update(
|
|
{'add_role_to_admin': ','.join(add_role_to_admin)})
|
|
for relation in self.relations:
|
|
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):
|
|
self.register_endpoints('None', 'None', 'None', 'None', 'None')
|
|
|
|
def request_notification(self, services):
|
|
"""
|
|
Request notification about changes to endpoints
|
|
|
|
:param services: services to request notification about
|
|
:type: list[str]
|
|
"""
|
|
relation_info = {
|
|
"subscribe_ep_change": " ".join(services),
|
|
}
|
|
for relation in self.relations:
|
|
relation.to_publish_raw.update(relation_info)
|
|
|
|
def get_ssl_key(self, cn=None):
|
|
relation_key = 'ssl_key_{}'.format(cn) if cn else 'ssl_key'
|
|
key = self.all_joined_units.received.get(relation_key)
|
|
if key:
|
|
key = base64.b64decode(key).decode('utf-8')
|
|
return key
|
|
|
|
def get_ssl_cert(self, cn=None):
|
|
relation_key = 'ssl_cert_{}'.format(cn) if cn else 'ssl_cert'
|
|
cert = self.all_joined_units.received.get(relation_key)
|
|
if cert:
|
|
cert = base64.b64decode(cert).decode('utf-8')
|
|
return cert
|
|
|
|
def get_ssl_ca(self, cn=None):
|
|
ca = None
|
|
if self.ca_cert():
|
|
ca = base64.b64decode(self.ca_cert()).decode('utf-8')
|
|
return ca
|
|
|
|
def endpoint_checksums(self):
|
|
"""Read any endpoint notification checksums from the interface
|
|
|
|
:returns: endpoint->checksum data dictionary
|
|
:rtype: dict
|
|
"""
|
|
if self.ep_changed():
|
|
return self.ep_changed()
|
|
return {}
|