
Enable passing the certificate authority on the relation in order to enable TLS communication to the MySQL DB. In order to enable MySQL to use TLS for client connections all that is required is the CA certificate. A certificate and key may be used for client certificate authentication. However, since almost all deployments will not use certificate authentication, focusing on the CA as SSL data complete makes sense. Change-Id: I785afe7f64cb57caa857178d529e3cabdcf63517
214 lines
7.0 KiB
Python
214 lines
7.0 KiB
Python
from charmhelpers.core import hookenv
|
|
from charms.reactive import RelationBase
|
|
from charms.reactive import hook
|
|
from charms.reactive import scopes
|
|
|
|
|
|
class MySQLSharedRequires(RelationBase):
|
|
scope = scopes.GLOBAL
|
|
|
|
# These remote data fields will be automatically mapped to accessors
|
|
# with a basic documentation string provided.
|
|
auto_accessors = ['access-network', 'db_host', 'db_port',
|
|
'ssl_ca', 'ssl_cert', 'ssl_key',
|
|
'cluster-series-upgrading', 'wait_timeout']
|
|
|
|
@hook('{requires:mysql-shared}-relation-joined')
|
|
def joined(self):
|
|
self.set_state('{relation_name}.connected')
|
|
|
|
@hook('{requires:mysql-shared}-relation-changed')
|
|
def changed(self):
|
|
if self.cluster_series_upgrading() == 'True':
|
|
self.remove_state('{relation_name}.available')
|
|
self.remove_state('{relation_name}.available.access_network')
|
|
self.remove_state('{relation_name}.available.ssl')
|
|
else:
|
|
if self.base_data_complete() and self.unit_allowed_all_dbs():
|
|
self.set_state('{relation_name}.available')
|
|
if self.access_network_data_complete():
|
|
self.set_state('{relation_name}.available.access_network')
|
|
if self.ssl_data_complete():
|
|
self.set_state('{relation_name}.available.ssl')
|
|
|
|
@hook('{requires:mysql-shared}-relation-{broken,departed}')
|
|
def departed(self):
|
|
# Clear state
|
|
self.remove_state('{relation_name}.connected')
|
|
self.remove_state('{relation_name}.available')
|
|
self.remove_state('{relation_name}.available.access_network')
|
|
self.remove_state('{relation_name}.available.ssl')
|
|
# Check if this is the last unit
|
|
for conversation in self.conversations():
|
|
for rel_id in conversation.relation_ids:
|
|
if len(hookenv.related_units(rel_id)) > 0:
|
|
# This is not the last unit so reevaluate state
|
|
self.joined()
|
|
self.changed()
|
|
|
|
def configure(self, database, username, hostname=None, prefix=None):
|
|
"""
|
|
Called by charm layer that uses this interface to configure a database.
|
|
"""
|
|
if not hostname:
|
|
conversation = self.conversation()
|
|
try:
|
|
hostname = hookenv.network_get_primary_address(
|
|
conversation.relation_name
|
|
)
|
|
except NotImplementedError:
|
|
hostname = hookenv.unit_private_ip()
|
|
|
|
if prefix:
|
|
relation_info = {
|
|
prefix + '_database': database,
|
|
prefix + '_username': username,
|
|
prefix + '_hostname': hostname,
|
|
}
|
|
self.set_prefix(prefix)
|
|
else:
|
|
relation_info = {
|
|
'database': database,
|
|
'username': username,
|
|
'hostname': hostname,
|
|
}
|
|
self.set_remote(**relation_info)
|
|
self.set_local(**relation_info)
|
|
|
|
def set_prefix(self, prefix):
|
|
"""
|
|
Store all of the database prefixes in a list.
|
|
"""
|
|
prefixes = self.get_local('prefixes')
|
|
if prefixes:
|
|
if prefix not in prefixes:
|
|
self.set_local('prefixes', prefixes + [prefix])
|
|
else:
|
|
self.set_local('prefixes', [prefix])
|
|
|
|
def get_prefixes(self):
|
|
"""
|
|
Return the list of saved prefixes.
|
|
"""
|
|
return self.get_local('prefixes')
|
|
|
|
def database(self, prefix=None):
|
|
"""
|
|
Return a configured database name.
|
|
"""
|
|
if prefix:
|
|
return self.get_local(prefix + '_database')
|
|
return self.get_local('database')
|
|
|
|
def username(self, prefix=None):
|
|
"""
|
|
Return a configured username.
|
|
"""
|
|
if prefix:
|
|
return self.get_local(prefix + '_username')
|
|
return self.get_local('username')
|
|
|
|
def hostname(self, prefix=None):
|
|
"""
|
|
Return a configured hostname.
|
|
"""
|
|
if prefix:
|
|
return self.get_local(prefix + '_hostname')
|
|
return self.get_local('hostname')
|
|
|
|
def password(self, prefix=None):
|
|
"""
|
|
Return a database password.
|
|
"""
|
|
if prefix:
|
|
return self.get_remote(prefix + '_password')
|
|
return self.get_remote('password')
|
|
|
|
def allowed_units(self, prefix=None):
|
|
"""
|
|
Return a database's allowed_units.
|
|
"""
|
|
if prefix:
|
|
return self.get_remote(prefix + '_allowed_units')
|
|
return self.get_remote('allowed_units')
|
|
|
|
def base_data_complete(self):
|
|
"""
|
|
Check if required base data is complete.
|
|
"""
|
|
data = {
|
|
'db_host': self.db_host(),
|
|
}
|
|
if self.get_prefixes():
|
|
suffixes = ['_password', '_allowed_units']
|
|
for prefix in self.get_prefixes():
|
|
for suffix in suffixes:
|
|
key = prefix + suffix
|
|
data[key] = self.get_remote(key)
|
|
else:
|
|
data['password'] = self.get_remote('password')
|
|
data['allowed_units'] = self.get_remote('allowed_units')
|
|
if all(data.values()):
|
|
return True
|
|
return False
|
|
|
|
def unit_allowed_db(self, prefix=None):
|
|
""""
|
|
Check unit can access requested database.
|
|
|
|
:param prefix: Prefix used to distinguish multiple db requests.
|
|
:type prefix: str
|
|
:returns: Whether db acl has been setup.
|
|
:rtype: bool
|
|
"""
|
|
allowed = False
|
|
allowed_units = self.allowed_units(prefix=prefix) or ''
|
|
hookenv.log("Checking {} is in {}".format(
|
|
hookenv.local_unit(),
|
|
allowed_units.split()))
|
|
if allowed_units and hookenv.local_unit() in allowed_units.split():
|
|
allowed = True
|
|
hookenv.log("Unit allowed: {}".format(allowed))
|
|
return allowed
|
|
|
|
def unit_allowed_all_dbs(self):
|
|
""""
|
|
Check unit can access all requested databases.
|
|
|
|
:returns: Whether db acl has been setup for all dbs.
|
|
:rtype: bool
|
|
"""
|
|
if self.get_prefixes():
|
|
_allowed = [self.unit_allowed_db(prefix=p)
|
|
for p in self.get_prefixes()]
|
|
else:
|
|
_allowed = [self.unit_allowed_db()]
|
|
hookenv.log("Allowed: {}".format(_allowed))
|
|
if all(_allowed):
|
|
hookenv.log("Returning unit_allowed_all_dbs True")
|
|
return True
|
|
hookenv.log("Returning unit_allowed_all_dbs False")
|
|
return False
|
|
|
|
def access_network_data_complete(self):
|
|
"""
|
|
Check if optional access network data provided by mysql is complete.
|
|
"""
|
|
data = {
|
|
'access-network': self.access_network(),
|
|
}
|
|
if all(data.values()):
|
|
return True
|
|
return False
|
|
|
|
def ssl_data_complete(self):
|
|
"""
|
|
Check if optional ssl data provided by mysql is complete.
|
|
"""
|
|
data = {
|
|
'ssl_ca': self.ssl_ca(),
|
|
}
|
|
if all(data.values()):
|
|
return True
|
|
return False
|