charm-interface-keystone/requires.py

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 {}