diff options
Diffstat (limited to 'contrail-agent/hooks/contrail_agent_utils.py')
-rw-r--r-- | contrail-agent/hooks/contrail_agent_utils.py | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/contrail-agent/hooks/contrail_agent_utils.py b/contrail-agent/hooks/contrail_agent_utils.py new file mode 100644 index 0000000..1f9e617 --- /dev/null +++ b/contrail-agent/hooks/contrail_agent_utils.py @@ -0,0 +1,373 @@ +from base64 import b64decode +import functools +import os +from socket import gethostname +from subprocess import ( + check_call, + check_output, +) +from time import sleep, time + +import apt_pkg +import json + +import netaddr +import netifaces + +from charmhelpers.contrib.network.ip import get_address_in_network +from charmhelpers.core.hookenv import ( + config, + log, + related_units, + relation_get, + relation_ids, + status_set, + ERROR, + WARNING, +) + +from charmhelpers.core.host import ( + restart_on_change, + write_file, + service_restart, +) + +from charmhelpers.core.templating import render + +apt_pkg.init() +config = config() + + +# as it's hardcoded in several scripts/configs +VROUTER_INTERFACE = "vhost0" + + +def retry(f=None, timeout=10, delay=2): + """Retry decorator. + + Provides a decorator that can be used to retry a function if it raises + an exception. + + :param timeout: timeout in seconds (default 10) + :param delay: retry delay in seconds (default 2) + + Examples:: + + # retry fetch_url function + @retry + def fetch_url(): + # fetch url + + # retry fetch_url function for 60 secs + @retry(timeout=60) + def fetch_url(): + # fetch url + """ + if not f: + return functools.partial(retry, timeout=timeout, delay=delay) + + @functools.wraps(f) + def func(*args, **kwargs): + start = time() + error = None + while True: + try: + return f(*args, **kwargs) + except Exception as e: + error = e + elapsed = time() - start + if elapsed >= timeout: + raise error + remaining = timeout - elapsed + sleep(delay if delay <= remaining else remaining) + return func + + +def configure_vrouter_interface(): + # run external script to configure vrouter + args = ["./create-vrouter.sh"] + if config["remove-juju-bridge"]: + args.append("-b") + iface = config.get("physical-interface") + if iface: + args.append(iface) + check_call(args, cwd="scripts") + + +def drop_caches(): + """Clears OS pagecache""" + log("Clearing pagecache") + check_call(["sync"]) + with open("/proc/sys/vm/drop_caches", "w") as f: + f.write("3\n") + + +def dkms_autoinstall(module): + """Allows loading of a kernel module. + + 'dkms_autoinstall' is useful for DKMS kernel modules. Juju often upgrades + units to newer kernels before charm install, which won't be used until the + machine is rebooted. In these cases, some modules may not be compiled for + the newer kernel. Setting this argument to True will ensure these modules + are compiled for newer kernels. + + :param module: module to load + """ + current = check_output(["uname", "-r"]).rstrip() + for kernel in os.listdir("/lib/modules"): + if kernel == current: + continue + log("DKMS auto installing for kernel {}".format(kernel)) + check_call(["dkms", "autoinstall", "-k", kernel]) + + +def update_vrouter_provision_status(): + # TODO: update this logic with various scenario for data in relation + info = _load_json_from_config("orchestrator_info") + ready = ( + config.get("api_port") + and (config.get("api_ip") or config.get("api_vip")) + and config.get("analytics_servers") + and info.get("cloud_orchestrator")) + if config.get("vrouter-expected-provision-state"): + if ready and not config.get("vrouter-provisioned"): + try: + provision_vrouter("add") + config["vrouter-provisioned"] = True + except Exception as e: + # vrouter is not up yet + log("Couldn't provision vrouter: " + str(e), level=WARNING) + elif config.get("vrouter-provisioned"): + try: + provision_vrouter("del") + except Exception as e: + log("Couldn't unprovision vrouter: " + str(e), level=WARNING) + config["vrouter-provisioned"] = False + + +def get_control_network_ip(control_network=None): + network = control_network + if not network: + network = config.get("control-network") + ip = get_address_in_network(network) if network else None + if not ip: + ip = iface_addr(VROUTER_INTERFACE)["addr"] + return ip + + +def reprovision_vrouter(old_ip): + if not config.get("vrouter-provisioned"): + return + + old_ip = get_control_network_ip(config.prev("control-network")) + try: + provision_vrouter("del", old_ip) + except Exception as e: + log("Couldn't unprovision vrouter: " + str(e), level=WARNING) + try: + provision_vrouter("add") + except Exception as e: + # vrouter is not up yet + log("Couldn't provision vrouter: " + str(e), level=WARNING) + + +def provision_vrouter(op, self_ip=None): + ip = self_ip if self_ip else get_control_network_ip() + api_ip, api_port = get_controller_address() + identity = _load_json_from_config("auth_info") + params = [ + "contrail-provision-vrouter", + "--host_name", gethostname(), + "--host_ip", ip, + "--api_server_ip", api_ip, + "--api_server_port", str(api_port), + "--oper", op] + if "keystone_admin_user" in identity: + params += [ + "--admin_user", identity.get("keystone_admin_user"), + "--admin_password", identity.get("keystone_admin_password"), + "--admin_tenant_name", identity.get("keystone_admin_tenant")] + + @retry(timeout=65, delay=20) + def _call(): + check_call(params) + log("vrouter operation '{}' was successful".format(op)) + + log("{} vrouter {}".format(op, ip)) + _call() + + +def get_controller_address(): + ip = config.get("api_ip") + port = config.get("api_port") + api_vip = config.get("api_vip") + if api_vip: + ip = api_vip + return (ip, port) if ip and port else (None, None) + + +def iface_addr(iface): + return netifaces.ifaddresses(iface)[netifaces.AF_INET][0] + + +def vhost_ip(addr): + # return a vhost formatted address and mask - x.x.x.x/xx + addr = iface_addr(VROUTER_INTERFACE) + ip = addr["addr"] + cidr = netaddr.IPNetwork(ip + "/" + addr["netmask"]).prefixlen + return ip + "/" + str(cidr) + + +def vhost_gateway(iface): + # determine vhost gateway + gateway = config.get("vhost-gateway") + if gateway == "auto": + for line in check_output(["route", "-n"]).splitlines()[2:]: + l = line.decode('UTF-8').split() + if "G" in l[3] and l[7] == iface: + return l[1] + gateway = None + return gateway + + +def vhost_phys(iface): + # run external script to determine physical interface of 'vhost0' + cmd = ["scripts/vhost-phys.sh", iface] + return (check_output(cmd).decode('UTF-8').rstrip()) + + +def _load_json_from_config(key): + value = config.get(key) + return json.loads(value) if value else {} + + +def get_context(): + ctx = {} + ssl_ca = _decode_cert("ssl_ca") + ctx["ssl_ca"] = ssl_ca + ctx["ssl_enabled"] = (ssl_ca is not None and len(ssl_ca) > 0) + + ip, port = get_controller_address() + ctx["api_server"] = ip + ctx["api_port"] = port + ctx["control_nodes"] = [ + relation_get("private-address", unit, rid) + for rid in relation_ids("contrail-controller") + for unit in related_units(rid)] + ctx["analytics_nodes"] = _load_json_from_config("analytics_servers") + info = _load_json_from_config("orchestrator_info") + ctx["metadata_shared_secret"] = info.get("metadata_shared_secret") + + ctx["control_network_ip"] = get_control_network_ip() + + ctx["vhost_ip"] = vhost_ip(VROUTER_INTERFACE) + ctx["vhost_gateway"] = vhost_gateway(VROUTER_INTERFACE) + ctx["vhost_physical"] = vhost_phys(VROUTER_INTERFACE) + + log("CTX: " + str(ctx)) + + ctx.update(_load_json_from_config("auth_info")) + return ctx + + +def _decode_cert(key): + val = config.get(key) + if not val: + return None + try: + return b64decode(val) + except Exception as e: + log("Couldn't decode certificate from config['{}']: {}".format( + key, str(e)), level=ERROR) + return None + + +def _save_file(path, data): + if data: + fdir = os.path.dirname(path) + if not os.path.exists(fdir): + os.makedirs(fdir) + write_file(path, data, perms=0o400) + elif os.path.exists(path): + os.remove(path) + + +@restart_on_change({ + "/etc/contrail/ssl/certs/ca-cert.pem": + ["contrail-vrouter-agent", "contrail-vrouter-nodemgr"], + "/etc/contrail/contrail-vrouter-agent.conf": + ["contrail-vrouter-agent"], + "/etc/contrail/contrail-vrouter-nodemgr.conf": + ["contrail-vrouter-nodemgr"]}) +def write_configs(): + ctx = get_context() + + # TODO: what we should do with two other certificates? + # NOTE: store files in the same paths as in tepmlates + ca_path = "/etc/contrail/ssl/certs/ca-cert.pem" + ssl_ca = ctx["ssl_ca"] + _save_file(ca_path, ssl_ca) + ctx["ssl_ca_path"] = ca_path + + render("contrail-vrouter-nodemgr.conf", + "/etc/contrail/contrail-vrouter-nodemgr.conf", ctx) + render("vnc_api_lib.ini", "/etc/contrail/vnc_api_lib.ini", ctx) + render("contrail-vrouter-agent.conf", + "/etc/contrail/contrail-vrouter-agent.conf", ctx, perms=0o440) + + +def update_unit_status(): + if not config.get("vrouter-provisioned"): + units = [unit for rid in relation_ids("contrail-controller") + for unit in related_units(rid)] + if units: + status_set("waiting", "There is no enough info to provision.") + else: + status_set("blocked", "Missing relation to contrail-controller") + + status, _ = _get_agent_status() + if status == 'initializing': + # some hacks + log("Run agent hack: service restart") + service_restart("contrail-vrouter-agent") + sleep(10) + status, msg = _get_agent_status() + if status == 'initializing' and "(No Configuration for self)" in msg: + log("Run agent hack: reinitialize config client") + ip = config.get("api_ip") + try: + # TODO: apply SSL if needed + check_call( + ["curl", "-s", + "http://{}:8083/Snh_ConfigClientReinitReq?".format(ip)]) + sleep(5) + status, _ = _get_agent_status() + except Exception as e: + log("Reinitialize returns error: " + str(e)) + + if status == 'active': + status_set("active", "Unit is ready") + return + + status_set("waiting", "vrouter-agent is not up") + + +def _get_agent_status(): + """ Analyzes output of 'contrail-status' utility + + returns status from agent service: + """ + output = check_output("contrail-status", shell=True) + for line in output.splitlines()[1:]: + if len(line) == 0: + return + lst = line.decode('UTF-8').split() + if len(lst) < 2: + continue + s_name = lst[0].strip() + s_status = lst[1].strip() + if 'contrail-vrouter-agent' not in s_name: + continue + + log("contrail-status: " + line) + return s_status, line |