125 lines
4.5 KiB
Python
125 lines
4.5 KiB
Python
from charms import layer
|
|
from charmhelpers.core.hookenv import config
|
|
from charmhelpers.core.hookenv import is_leader
|
|
from charmhelpers.core.hookenv import leader_get, leader_set
|
|
from charmhelpers.core import unitdata
|
|
from charms.reactive import is_state
|
|
from etcd_lib import get_ingress_address
|
|
from etcd_lib import get_bind_address
|
|
|
|
import string
|
|
import random
|
|
import os
|
|
|
|
|
|
class EtcdDatabag:
|
|
"""
|
|
This class represents a configuration object to ease configuration of an
|
|
etcd unit during deployment and reconfiguration. The full dict of data
|
|
when expanded looks like the following:
|
|
|
|
{'public_address': '127.0.0.1',
|
|
'cluster_bind_address': '127.0.0.1',
|
|
'db_bind_address': '127.0.0.1',
|
|
'cluster_address': '127.0.0.1',
|
|
'db_address': '127.0.0.1',
|
|
'unit_name': 'etcd0',
|
|
'port': '2380',
|
|
'management_port': '2379',
|
|
'ca_certificate': '/etc/ssl/etcd/ca.crt',
|
|
'server_certificate': '/etc/ssl/etcd/server.crt',
|
|
'server_key': '/etc/ssl/etcd/server.key',
|
|
'tls_cipher_suites': '',
|
|
'token': '8XG27B',
|
|
'cluster_state': 'existing'}
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.db = unitdata.kv()
|
|
self.cluster_bind_address = self.get_bind_address("cluster")
|
|
self.db_bind_address = self.get_bind_address("db")
|
|
self.port = config("port")
|
|
self.management_port = config("management_port")
|
|
# Live polled properties
|
|
self.cluster_address = get_ingress_address("cluster")
|
|
self.db_address = get_ingress_address("db")
|
|
self.unit_name = os.getenv("JUJU_UNIT_NAME").replace("/", "")
|
|
|
|
# Pull the TLS certificate paths from layer data
|
|
tls_opts = layer.options("tls-client")
|
|
ca_path = tls_opts["ca_certificate_path"]
|
|
crt_path = tls_opts["server_certificate_path"]
|
|
key_path = tls_opts["server_key_path"]
|
|
|
|
# Pull the static etcd configuration from layer-data
|
|
etcd_opts = layer.options("etcd")
|
|
self.etcd_conf_dir = etcd_opts["etcd_conf_dir"]
|
|
# This getter determines the current context of the storage path
|
|
# depending on if durable storage is mounted.
|
|
self.etcd_data_dir = self.storage_path()
|
|
self.etcd_daemon = etcd_opts["etcd_daemon_process"]
|
|
|
|
self.ca_certificate = ca_path
|
|
self.server_certificate = crt_path
|
|
self.server_key = key_path
|
|
|
|
self.tls_cipher_suites = config("tls_cipher_suites")
|
|
|
|
# Cluster concerns
|
|
self.cluster = self.db.get("etcd.cluster", "")
|
|
self.token = self.cluster_token()
|
|
self.cluster_state = self.db.get("etcd.cluster-state", "existing")
|
|
|
|
def set_cluster(self, value):
|
|
"""Set the cluster string for peer registration"""
|
|
self.cluster = value
|
|
self.db.set("etcd.cluster", value)
|
|
|
|
def set_cluster_state(self, value):
|
|
"""Set the cluster state"""
|
|
self.cluster_state = value
|
|
self.db.set("etcd.cluster-state", value)
|
|
|
|
def cluster_token(self):
|
|
"""Getter to return the unique cluster token."""
|
|
token = leader_get("token")
|
|
if not token and is_leader():
|
|
token = self.id_generator()
|
|
leader_set({"token": token})
|
|
return token
|
|
|
|
def id_generator(self, size=6):
|
|
"""Return a random 6 character string for use in cluster init.
|
|
|
|
@params size - The size of the string to return in characters
|
|
"""
|
|
chars = string.ascii_uppercase + string.digits
|
|
return "".join(random.choice(chars) for _ in range(size))
|
|
|
|
def storage_path(self):
|
|
"""Storage mounts are limited in snap confinement. Default behavior
|
|
is to version the database files in $SNAP_DATA. However the user can
|
|
attach durable storage, which is mounted in /media. We need a common
|
|
method to determine which storage path we are concerned with"""
|
|
|
|
etcd_opts = layer.options("etcd")
|
|
|
|
if is_state("data.volume.attached"):
|
|
return "/media/etcd/data"
|
|
else:
|
|
return etcd_opts["etcd_data_dir"]
|
|
|
|
def get_bind_address(self, endpoint_name):
|
|
"""Returns the address that the service binds to. If the config
|
|
parameter 'bind_to_all_interfaces' is set to true, it returns 0.0.0.0
|
|
If 'bind_to_all_interfaces' is set to false, it returns the
|
|
bind address of the endpoint_name received as parameter
|
|
|
|
@param endpoint_name name the endpoint from where the
|
|
bind address is obtained
|
|
"""
|
|
if bool(config("bind_to_all_interfaces")):
|
|
return "0.0.0.0"
|
|
|
|
return get_bind_address(endpoint_name)
|