From 4faa7f927149a5c4ef7a03523f7bc14523cb9baa Mon Sep 17 00:00:00 2001 From: Stuart Mackie Date: Fri, 7 Oct 2016 12:24:58 -0700 Subject: Charms for Contrail 3.1 with Mitaka Change-Id: Id37f3b9743d1974e31fcd7cd9c54be41bb0c47fb Signed-off-by: Stuart Mackie --- .../hooks/neutron_contrail_utils.py | 478 +++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 charms/trusty/neutron-contrail/hooks/neutron_contrail_utils.py (limited to 'charms/trusty/neutron-contrail/hooks/neutron_contrail_utils.py') diff --git a/charms/trusty/neutron-contrail/hooks/neutron_contrail_utils.py b/charms/trusty/neutron-contrail/hooks/neutron_contrail_utils.py new file mode 100644 index 0000000..4bb8002 --- /dev/null +++ b/charms/trusty/neutron-contrail/hooks/neutron_contrail_utils.py @@ -0,0 +1,478 @@ +import functools +import os +import pwd +import shutil +from socket import gethostbyname, gethostname +from subprocess import ( + CalledProcessError, + check_call, + check_output +) +from time import sleep, time + +import apt_pkg +import yaml + +import netaddr +import netifaces + +from charmhelpers.core.hookenv import ( + config, + log, + related_units, + relation_get, + relation_ids, + relation_type, + remote_unit +) + +from charmhelpers.core.host import service_restart, service_start + +from charmhelpers.core.templating import render + +apt_pkg.init() + +def dpkg_version(pkg): + try: + return check_output(["dpkg-query", "-f", "${Version}\\n", "-W", pkg]).rstrip() + except CalledProcessError: + return None + +CONTRAIL_VERSION = dpkg_version("contrail-vrouter-agent") +OPENSTACK_VERSION = dpkg_version("nova-compute") + +config = config() + +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 + if delay <= remaining: + sleep(delay) + else: + sleep(remaining) + raise error + return func + +def configure_vrouter(): + # run external script to configure vrouter + args = ["./create-vrouter.sh"] + iface = config.get("vhost-interface") + if iface: + args.append(iface) + check_call(args, cwd="scripts") + +def contrail_api_ctx(): + ip = config.get("contrail-api-ip") + if ip: + port = config.get("contrail-api-port") + return { "api_server": ip, + "api_port": port if port is not None else 8082 } + + ctxs = [ { "api_server": gethostbyname(relation_get("private-address", unit, rid)), + "api_port": port } + for rid in relation_ids("contrail-api") + for unit, port in + ((unit, relation_get("port", unit, rid)) for unit in related_units(rid)) + if port ] + return ctxs[0] if ctxs else {} + +def contrail_discovery_ctx(): + ip = config.get("discovery-server-ip") + if ip: + return { "discovery_server": ip, + "discovery_port": 5998 } + + ctxs = [ { "discovery_server": vip if vip \ + else gethostbyname(relation_get("private-address", unit, rid)), + "discovery_port": port } + for rid in relation_ids("contrail-discovery") + for unit, port, vip in + ((unit, relation_get("port", unit, rid), relation_get("vip", unit, rid)) + for unit in related_units(rid)) + if port ] + return ctxs[0] if ctxs else {} + +@retry(timeout=300) +def contrail_provision_linklocal(api_ip, api_port, service_name, service_ip, + service_port, fabric_ip, fabric_port, op, + user, password): + check_call(["contrail-provision-linklocal", + "--api_server_ip", api_ip, + "--api_server_port", str(api_port), + "--linklocal_service_name", service_name, + "--linklocal_service_ip", service_ip, + "--linklocal_service_port", str(service_port), + "--ipfabric_service_ip", fabric_ip, + "--ipfabric_service_port", str(fabric_port), + "--oper", op, + "--admin_user", user, + "--admin_password", password]) + +@retry(timeout=300) +def contrail_provision_vrouter(hostname, ip, api_ip, api_port, op, + user, password, tenant): + check_call(["contrail-provision-vrouter", + "--host_name", hostname, + "--host_ip", ip, + "--api_server_ip", api_ip, + "--api_server_port", str(api_port), + "--oper", op, + "--admin_user", user, + "--admin_password", password, + "--admin_tenant_name", tenant]) + +def control_node_ctx(): + return { "control_nodes": [ gethostbyname(relation_get("private-address", unit, rid)) + for rid in relation_ids("control-node") + for unit in related_units(rid) ] } + +def disable_vrouter_vgw(): + if os.path.exists("/etc/sysctl.d/60-vrouter-vgw.conf"): + # unset sysctl options + os.remove("/etc/sysctl.d/60-vrouter-vgw.conf") + check_call(["sysctl", "-qw", "net.ipv4.ip_forward=0"]) + +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 enable_vrouter_vgw(): + if not os.path.exists("/etc/sysctl.d/60-vrouter-vgw.conf"): + # set sysctl options + shutil.copy("files/60-vrouter-vgw.conf", "/etc/sysctl.d") + service_start("procps") + +def fix_nodemgr(): + # add files missing from contrail-nodemgr package + shutil.copy("files/contrail-nodemgr-vrouter.ini", + "/etc/contrail/supervisord_vrouter_files") + pw = pwd.getpwnam("contrail") + os.chown("/etc/contrail/supervisord_vrouter_files/contrail-nodemgr-vrouter.ini", + pw.pw_uid, pw.pw_gid) + shutil.copy("files/contrail-vrouter.rules", + "/etc/contrail/supervisord_vrouter_files") + os.chown("/etc/contrail/supervisord_vrouter_files/contrail-vrouter.rules", + pw.pw_uid, pw.pw_gid) + shutil.copy("files/contrail-vrouter-nodemgr", "/etc/init.d") + os.chmod("/etc/init.d/contrail-vrouter-nodemgr", 0755) + service_restart("supervisor-vrouter") + +def fix_permissions(): + os.chmod("/etc/contrail", 0755) + os.chown("/etc/contrail", 0, 0) + +def fix_vrouter_scripts(): + # certain files need to be present for packages + if not os.path.exists("/opt/contrail/bin"): + os.makedirs("/opt/contrail/bin") + os.symlink("/bin/true", "/opt/contrail/bin/vrouter-pre-start.sh") + os.symlink("/bin/true", "/opt/contrail/bin/vrouter-post-start.sh") + os.symlink("/bin/true", "/opt/contrail/bin/vrouter-pre-stop.sh") + +def identity_admin_ctx(): + ctxs = [ { "auth_host": gethostbyname(hostname), + "auth_port": relation_get("service_port", unit, rid), + "admin_user": relation_get("service_username", unit, rid), + "admin_password": relation_get("service_password", unit, rid), + "admin_tenant_name": relation_get("service_tenant_name", unit, rid), + "auth_region": relation_get("service_region", unit, rid) } + for rid in relation_ids("identity-admin") + for unit, hostname in + ((unit, relation_get("service_hostname", unit, rid)) for unit in related_units(rid)) + if hostname ] + return ctxs[0] if ctxs else {} + +def ifdown(interfaces=None): + """ifdown an interface or all interfaces""" + log("Taking down {}".format(interfaces if interfaces else "interfaces")) + check_call(["ifdown"] + interfaces if interfaces else ["-a"]) + +def ifup(interfaces=None): + """ifup an interface or all interfaces""" + log("Bringing up {}".format(interfaces if interfaces else "interfaces")) + check_call(["ifup"] + interfaces if interfaces else ["-a"]) + +def lsmod(module): + """Check if a kernel module is loaded""" + with open("/proc/modules", "r") as modules: + for line in modules: + if line.split()[0] == module: + return True + return False + +def modprobe(module, auto_load=False, dkms_autoinstall=False): + """Load a kernel 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 + :param auto_load: load module on boot (default False) + :param dkms_autoinstall: invoke DKMS autoinstall for other kernels + (default False) + """ + if not lsmod(module): + log("Loading kernel module {}".format(module)) + check_call(["modprobe", module]) + if auto_load: + with open("/etc/modules", "a") as modules: + modules.write(module) + modules.write("\n") + if dkms_autoinstall: + 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 network_ctx(): + iface = config.get("control-interface") + return { "control_network_ip": netifaces.ifaddresses(iface)[netifaces.AF_INET][0]["addr"] } + +def neutron_metadata_ctx(): + if "local-metadata-secret" in config: + return { "metadata_secret": config["local-metadata-secret"] } + + ctxs = [ { "metadata_secret": relation_get("shared-secret", unit, rid) } + for rid in relation_ids("neutron-metadata") + for unit in related_units(rid) ] + return ctxs[0] if ctxs else {} + +def provision_local_metadata(): + api_port = None + api_ip = config.get("contrail-api-ip") + if api_ip: + api_port = config.get("contrail-api-port") + if api_port is None: + api_port = 8082 + else: + api_ip, api_port = [ (gethostbyname(relation_get("private-address", unit, rid)), + port) + for rid in relation_ids("contrail-api") + for unit, port in + ((unit, relation_get("port", unit, rid)) for unit in related_units(rid)) + if port ][0] + user, password = [ (relation_get("service_username", unit, rid), + relation_get("service_password", unit, rid)) + for rid in relation_ids("identity-admin") + for unit in related_units(rid) + if relation_get("service_hostname", unit, rid) ][0] + log("Provisioning local metadata service 127.0.0.1:8775") + contrail_provision_linklocal(api_ip, api_port, "metadata", + "169.254.169.254", 80, "127.0.0.1", 8775, + "add", user, password) + +def provision_vrouter(): + hostname = gethostname() + ip = netifaces.ifaddresses("vhost0")[netifaces.AF_INET][0]["addr"] + api_port = None + api_ip = config.get("contrail-api-ip") + if api_ip: + api_port = config.get("contrail-api-port") + if api_port is None: + api_port = 8082 + else: + api_ip, api_port = [ (gethostbyname(relation_get("private-address", unit, rid)), + port) + for rid in relation_ids("contrail-api") + for unit, port in + ((unit, relation_get("port", unit, rid)) for unit in related_units(rid)) + if port ][0] + user, password, tenant = [ (relation_get("service_username", unit, rid), + relation_get("service_password", unit, rid), + relation_get("service_tenant_name", unit, rid)) + for rid in relation_ids("identity-admin") + for unit in related_units(rid) + if relation_get("service_hostname", unit, rid) ][0] + log("Provisioning vrouter {}".format(ip)) + contrail_provision_vrouter(hostname, ip, api_ip, api_port, "add", + user, password, tenant) + +def remove_juju_bridge(): + # run external script to remove bridge + check_call(["./remove-juju-bridge.sh"], cwd="scripts") + +def units(relation): + """Return a list of units for the specified relation""" + return [ unit for rid in relation_ids(relation) + for unit in related_units(rid) ] + +def unprovision_local_metadata(): + relation = relation_type() + if relation and not remote_unit(): + return + api_ip = config.previous("contrail-api-ip") + api_port = None + if api_ip: + api_port = config.previous("contrail-api-port") + if api_port is None: + api_port = 8082 + elif relation == "contrail-api": + api_ip = gethostbyname(relation_get("private-address")) + api_port = relation_get("port") + else: + api_ip, api_port = [ (gethostbyname(relation_get("private-address", unit, rid)), + relation_get("port", unit, rid)) + for rid in relation_ids("contrail-api") + for unit in related_units(rid) ][0] + user = None + password = None + if relation == "identity-admin": + user = relation_get("service_username") + password = relation_get("service_password") + else: + user, password = [ (relation_get("service_username", unit, rid), + relation_get("service_password", unit, rid)) + for rid in relation_ids("identity-admin") + for unit in related_units(rid) ][0] + log("Unprovisioning local metadata service 127.0.0.1:8775") + contrail_provision_linklocal(api_ip, api_port, "metadata", + "169.254.169.254", 80, "127.0.0.1", 8775, + "del", user, password) + +def unprovision_vrouter(): + relation = relation_type() + if relation and not remote_unit(): + return + hostname = gethostname() + ip = netifaces.ifaddresses("vhost0")[netifaces.AF_INET][0]["addr"] + api_ip = config.previous("contrail-api-ip") + api_port = None + if api_ip: + api_port = config.previous("contrail-api-port") + if api_port is None: + api_port = 8082 + elif relation == "contrail-api": + api_ip = gethostbyname(relation_get("private-address")) + api_port = relation_get("port") + else: + api_ip, api_port = [ (gethostbyname(relation_get("private-address", unit, rid)), + relation_get("port", unit, rid)) + for rid in relation_ids("contrail-api") + for unit in related_units(rid) ][0] + user = None + password = None + tenant = None + if relation == "identity-admin": + user = relation_get("service_username") + password = relation_get("service_password") + tenant = relation_get("service_tenant_name") + else: + user, password, tenant = [ (relation_get("service_username", unit, rid), + relation_get("service_password", unit, rid), + relation_get("service_tenant_name", unit, rid)) + for rid in relation_ids("identity-admin") + for unit in related_units(rid) ][0] + log("Unprovisioning vrouter {}".format(ip)) + contrail_provision_vrouter(hostname, ip, api_ip, api_port, "del", + user, password, tenant) + +def vhost_gateway(): + # determine vhost gateway + gateway = config.get("vhost-gateway") + if gateway == "auto": + for line in check_output(["route", "-n"]).splitlines()[2:]: + l = line.split() + if "G" in l[3] and l[7] == "vhost0": + return l[1] + gateway = None + return gateway + +def vhost_ip(iface): + # return a vhost formatted address and mask - x.x.x.x/xx + addr = netifaces.ifaddresses(iface)[netifaces.AF_INET][0] + ip = addr["addr"] + cidr = netaddr.IPNetwork(ip + "/" + addr["netmask"]).prefixlen + return ip + "/" + str(cidr) + +def vhost_phys(): + # run external script to determine physical interface of vhost0 + return check_output(["scripts/vhost-phys.sh"]).rstrip() + +def vrouter_ctx(): + return { "vhost_ip": vhost_ip("vhost0"), + "vhost_gateway": vhost_gateway(), + "vhost_physical": vhost_phys() } + +def vrouter_vgw_ctx(): + ctx = {} + vgws = config.get("virtual-gateways") + if vgws: + vgws = yaml.safe_load(vgws) + map(lambda item: item.update(domain="default-domain"), vgws) + ctx["vgws"] = vgws + return ctx + +def write_barbican_auth_config(): + ctx = identity_admin_ctx() + render("contrail-barbican-auth.conf", + "/etc/contrail/contrail-barbican-auth.conf", ctx, "root", "contrail", + 0440) + +def write_nodemgr_config(): + ctx = contrail_discovery_ctx() + render("contrail-vrouter-nodemgr.conf", + "/etc/contrail/contrail-vrouter-nodemgr.conf", ctx) + +def write_vnc_api_config(): + ctx = {} + ctx.update(contrail_api_ctx()) + ctx.update(identity_admin_ctx()) + render("vnc_api_lib.ini", "/etc/contrail/vnc_api_lib.ini", ctx) + +def write_vrouter_config(): + ctx = {} + ctx.update(control_node_ctx()) + ctx.update(contrail_discovery_ctx()) + ctx.update(neutron_metadata_ctx()) + ctx.update(network_ctx()) + ctx.update(vrouter_ctx()) + ctx.update(vrouter_vgw_ctx()) + render("contrail-vrouter-agent.conf", + "/etc/contrail/contrail-vrouter-agent.conf", ctx, perms=0440) + +def write_vrouter_vgw_interfaces(): + ctx = vrouter_vgw_ctx() + render("vrouter-vgw.cfg", "/etc/network/interfaces.d/vrouter-vgw.cfg", ctx) -- cgit 1.2.3-korg