#!/bin/bash

# Copyright (c) 2015-2017 by Cisco Systems, Inc.

# Provides app hosting functions

. /etc/init.d/functions

OPERNS="/etc/netns/global-vrf"
OPERNS_NAME="global-vrf"
TP_STORAGE_MOUNT="/misc/app_host"

if [ -f /etc/rc.d/init.d/calvados_bootstrap.cfg ]; then
    source /etc/rc.d/init.d/calvados_bootstrap.cfg
fi

if [[ "$app_hosting_functions_sourced" != true ]]; then
    app_hosting_functions_sourced=true

    . /etc/init.d/functions

    if [[ -f /etc/rc.d/init.d/spirit-functions ]]; then
        source /etc/rc.d/init.d/spirit-functions
    fi

    if [[ -f /etc/init.d/app_volume_config.conf ]]; then
        source /etc/init.d/app_volume_config.conf
    fi

    if [[ -f /etc/init.d/app_hosting_vrf.sh ]]; then
        source /etc/init.d/app_hosting_vrf.sh
    fi

    if [[ -f /etc/init.d/app_hosting_vrf_namespace.sh ]]; then
        source /etc/init.d/app_hosting_vrf_namespace.sh
    fi

    if [[ -f /usr/lib/app-hosting-log-functions ]]; then
        source /usr/lib/app-hosting-log-functions
    fi 
fi

# Create user scratch space in app_host volume

function create_user_scratch_space()
{
    # On LXC platforms, this is called from hostos, and we get
    # $SCRATCH_DIR_PATH from app_volume_config.conf
    if [[ -z "$SCRATCH_DIR_PATH" ]]; then
        # On KVM platforms, this is called from XR, and we don't have
        # app_volume_config.conf, but we also don't need it, as
        # /misc/app_host is locally owned
        local SCRATCH_DIR_PATH="${TP_STORAGE_MOUNT}/scratch"
    fi
    # Either way, the parent directory should already exist
    if [[ ! -e `dirname $SCRATCH_DIR_PATH` ]]; then
        platform_log "Parent directory of ${SCRATCH_DIR_PATH} not found!"
        return
    fi

    if [[ ! -e "$SCRATCH_DIR_PATH" ]]; then
        platform_log "Creating ${SCRATCH_DIR_PATH}"
        $PLATFORM_LOG_EXEC mkdir -p "${SCRATCH_DIR_PATH}"
        # Let any users in the sudoers group have write access to this dir
        $PLATFORM_LOG_EXEC chgrp sudo "${SCRATCH_DIR_PATH}"
        $PLATFORM_LOG_EXEC chmod 775 "${SCRATCH_DIR_PATH}"
    else
        # This function shouldn't be called repeatedly, so:
        platform_log "Scratch dir ${SCRATCH_DIR_PATH} already exists"
    fi
}

# Create namespace specific resolv.conf,hosts for global-vrf

function create_global_vrf_dirs()
{
    if [[ ! -f "$OPERNS" ]]; then
        platform_log "Creating ${OPERNS}"
        $PLATFORM_LOG_EXEC mkdir -p ${OPERNS}
    fi
    if [[ ! -f /etc/resolv.conf ]]; then
        $PLATFORM_LOG_EXEC touch /etc/resolv.conf
    fi
    if [[ ! -f ${OPERNS}/resolv.conf ]]; then
        $PLATFORM_LOG_EXEC touch ${OPERNS}/resolv.conf
    fi
    if [[ ! -f ${OPERNS}/hosts ]]; then
        $PLATFORM_LOG_EXEC cp /etc/hosts ${OPERNS}/hosts
    fi
}

# Create staging directory to be shared with the XR lxc

function create_inotify_staging_dir()
{
    local TP_LV_DIR_PATH=$1

    platform_log "Creating ${TP_LV_DIR_PATH}${OPERNS}"
    $PLATFORM_LOG_EXEC mkdir -p ${TP_LV_DIR_PATH}${OPERNS}
    $PLATFORM_LOG_EXEC touch ${TP_LV_DIR_PATH}${OPERNS}/resolv.conf
    if [[ ! -f ${TP_LV_DIR_PATH}${OPERNS}/hosts ]]; then
        $PLATFORM_LOG_EXEC cp /etc/hosts ${TP_LV_DIR_PATH}${OPERNS}/hosts
    fi 
    platform_log "Creating ${TP_LV_DIR_PATH}/etc/sysconfig"
    $PLATFORM_LOG_EXEC mkdir -p ${TP_LV_DIR_PATH}/etc/sysconfig

    platform_log "Creating ${TP_LV_DIR_PATH}/etc/docker/certs.d"
    $PLATFORM_LOG_EXEC mkdir -p ${TP_LV_DIR_PATH}/etc/docker/certs.d
}

# Create per network namespace resolv.conf in XR LXC

function create_global_vrf_dirs_xr()
{
    local XR_PATH=$1

    platform_log "Creating ${XR_PATH}${OPERNS}"
    $PLATFORM_LOG_EXEC mkdir -p  ${XR_PATH}${OPERNS}
    $PLATFORM_LOG_EXEC touch ${XR_PATH}${OPERNS}/resolv.conf
    if [[ ! -f ${XR_PATH}${OPERNS}/hosts ]]; then
        $PLATFORM_LOG_EXEC cp /etc/hosts  ${XR_PATH}${OPERNS}/hosts
    fi
    $PLATFORM_LOG_EXEC touch  ${XR_PATH}/etc/resolv.conf
    if [[ ! -f ${XR_PATH}/etc/hosts ]]; then
        $PLATFORM_LOG_EXEC cp /etc/hosts ${XR_PATH}/etc/hosts
    fi 
}

# Check VMTYPE and emit event for docker to start
# We need to confirm we're running in VM mode for docker to start
# in XR VM
function check_and_start_docker()
{
    if [[ "$VIRT_METHOD" == "vm" ]]; then
        if [[ `grep -c $TP_STORAGE_MOUNT /proc/mounts` -ne 0 ]]; then
            platform_log "/misc/app_host has been mounted, emitting event to start docker"
            initctl emit app-volume-mounted
        fi
    fi
}

#
# app_hosting_get_vm_type
#
function app_hosting_get_vm_type()
{
    if [[ $VMTYPE = "" ]]; then
        VMTYPE=`cat /proc/cmdline | sed 's/^.*vmtype=//' | cut -d" " -f1`
    fi
}

#
# app_hosting_get_virt_method
#
function app_hosting_get_virt_method()
{
    if [[ $VIRT_METHOD = "" ]]; then
        VIRT_METHOD=$(grep VIRT_METHOD /etc/init.d/calvados_bootstrap.cfg | awk -F '=' '{print $2}')
    fi
}

#
# app_hosting_is_vm
#
# Is this a KVM initiated VM ?
#
function app_hosting_is_vm()
{
    app_hosting_get_virt_method
    if [[ $VIRT_METHOD = "vm" ]]; then
        true
    else
        false
    fi
}

#
# app_host_create_global_vrf
#
# If we are invoked here in VM mode then we need to create the global-vrf.
# In LXC mode it gets synced from XR to host.
#
function app_host_create_global_vrf()
{
    app_hosting_log "$OPERNS_NAME" "Creating the global VRF, VM $VMTYPE"

    app_hosting_get_vm_type
    if [[ $VIRT_METHOD = "vm" ]]; then
        app_hosting_log "$VRF" "Creating the global VRF locally"
        $APP_HOSTING_LOG_EXEC ip netns add global-vrf 
        return
    fi

    app_hosting_log "$OPERNS_NAME" "Create not needed; is created in host scripts"
}

#
# Is app hosting feature x enabled?
#
function app_host_is_feature_supported()
{
    local feature=$1

    if ! app_host_is_enabled; then
        false
        return
    fi

    if [[ $APP_HOST_ENABLE_FEATURE =~ (^|,)"$feature"(,|$) ]]; then
        true
        return
    fi

    false
    return
}

#
# app_host_vm_name_is_vrf_supported
#
# Is this event occuring on a node where we want to support VRFs?
#
function app_host_vm_name_is_vrf_supported()
{
    local vm_name=$1

    #
    # Check the platform supports this feature.
    #
    declare -F app_host_is_feature_supported &>/dev/null && \
        app_host_is_feature_supported vrf_namespace
    if [[ $? -ne 0 ]]; then
        platform_log "VRF: Not supported on this platform"
        false
        return
    fi

    #
    # RP check should not be needed, as app hosting rules are only for the RP
    # but keeping *just* in case.
    #
    if [[ "$BOARDTYPE" = "" ]]; then
        get_board_type
    fi

    #
    # Don't want to do this for XR VM on LCs.
    #
    if [[ "$BOARDTYPE" != "RP" ]]; then
        false
        return
    fi

    #
    # Only on XR, not on the sunstone LC. Again RP check should suffice,
    # but just in case.
    #
    case "$vm_name" in
    default-sdr--1)
        true
        return
        ;;
    esac

    false
    return
}

#
# app_host_mount_vrfs
#
# Mount the host netns into XR so we can share VRFs.
#
function app_host_mount_vrfs()
{
    local vm_name=$1
    local XR_PATH=$2

    app_host_vm_name_is_vrf_supported $vm_name
    if [[ $? -ne 0 ]]; then
        true
        return
    fi

    $PLATFORM_LOG_EXEC mkdir -p ${XR_PATH}/bindmnt_netns/

    #
    # We need to share /var/run/netns with XR.
    #
    findmnt -o TARGET,PROPAGATION /var/run/netns | grep -q shared
    if [[ $? -ne 0 ]]; then
        platform_log "VRF: Making netns shared between host and XR"
        $PLATFORM_LOG_EXEC mount --make-rshared /var/run/netns
    fi

    mount | grep -q $XR_PATH/bindmnt_netns
    if [[ $? -ne 0 ]]; then
        platform_log "VRF: Mounting host netns into XR"
        $PLATFORM_LOG_EXEC mount --rbind /var/run/netns $XR_PATH/bindmnt_netns
    fi 
}

#
# app_host_unmount_vrfs
#
# Unmount the host netns from XR so we can clean up XR.
#
function app_host_unmount_vrfs()
{
    local vm_name=$1
    local XR_PATH=$2

    app_host_vm_name_is_vrf_supported $vm_name
    if [[ $? -ne 0 ]]; then
        true
        return
    fi

    #
    # Need to make the bind mount private before we unmount as 
    # otherwise the unmount will destroy all namespaces. We will
    # then reboot and find global-vrf gone.
    #
    platform_log "VRF: Unmounting host netns from XR"
    $PLATFORM_LOG_EXEC mount --make-rprivate $XR_PATH/bindmnt_netns

    #
    # Try to unmount the bindmnt now that it is private. We make it
    # private as if it was left shared, the unmount destroys the net
    # namespaces.
    #
    # For lazy unmount it will be removed from the filesystem namespace 
    # (so you won't see it under /mnt/... anymore) but it stays mounted,
    # so programs accessing it can continue to do so. When the last 
    # program accessing it exits, the unmount will actually occur.
    #
    platform_log "VRF: Making netns private again for host"
    $PLATFORM_LOG_EXEC umount -R $XR_PATH/bindmnt_netns
    if [[ $? -ne 0 ]]; then
        platform_log "VRF: Recurstive unmount failed, try lazy"
        $PLATFORM_LOG_EXEC umount -l $XR_PATH/bindmnt_netns
        if [[ $? -ne 0 ]]; then
            platform_log "VRF: Lazy unmount failed"

            #
            # Force unmount had lead to kernel crashes in netbroker in 
            # the past (which were fixes). However it seems risky, so 
            # leave this.
            #
        fi
    fi
}

#
# Function to check if given VRF exists or not
#
function check_if_vrf_exists()
{
    local vrf_name="$1"
    local vrf_list=($(ip netns list))
    for netns in "${vrf_list[@]}"; do
        if [[ "$vrf_name" == "$netns" ]]; then
            return 0
        fi
    done
    return 1
}

# 
# app_hosting_docker_daemon_cleanup_bind_mounts
#
# Unmount the mounts used by system LXCs from docker daemon's
# mount namespace 
#
function app_hosting_docker_daemon_cleanup_bind_mounts()
{
    app_hosting_log "Remove unwanted mounts from docker daemon's mount namespace"

    local check_lxcs="$@"
    local unmount_flag=0
    local lxc_file=""
    local docker_daemon_pid_pattern="[d]ocker daemon"
    local docker_pid=""
    local docker_pid_start=`cat /var/run/docker.pid 2>/dev/null`
    local docker_pid_end=""

    for lxc in $check_lxcs
    do
        lxc_file="/var/run/libvirt/lxc/${lxc}.xml"
        if ! [[ -f "$lxc_file" ]]; then 
            continue
        fi 
        
        #
        # Parse the libvirt xml to get the list of mounts from 
        # the host into the container
        #
        local source_dirs=$(xmllint --xpath "//domain/devices/filesystem/source/@dir" "$lxc_file" 2>/dev/null)
        local mount_list=()
        
        #
        # Clean and parse the output from xmllint and store in an array
        #
        for dir in $source_dirs
        do 
            dir_trim=$(echo $dir | cut -d'=' -f2 | tr -d '"')
            mount_list+=("$dir_trim")
        done 

        #
        # For each of the mounts used by the lxc
        # check if it is in docker daemons's mount namespace and 
        # and unmount, if present 
        #
        for line in "${mount_list[@]}"
        do
            if [[ "$line" == "/misc/app_host" ]]; then
                continue
            fi 

            docker_pid=`cat /var/run/docker.pid 2>/dev/null`

            if [[ -z "$docker_pid" ]]; then 
                app_hosting_log "Docker daemon is not running"
                true
                return
            fi 

            grep -q " $line " /proc/$docker_pid/mounts &>/dev/null
            if [[ $? -eq 0 ]]; then
                #
                # Recursively unmount the bind mounts from the docker
                # daemon's mount namespace
                #
                app_hosting_log "Unmounting $line from docker daemon's mount namespace"
                nsenter -t $docker_pid -m -- umount "$line" -d -R
                if [[ $? -ne 0 ]]; then
                    app_hosting_log "Failed to unmount $line from docker daemon's mount namespace"                  
                    unmount_flag=1
                fi
            fi
        done
    done
    
    #
    # If the docker daemon's pid changed during the execution of this
    # function or unmount of any mount failed , the docker daemon 
    # has to be stopped on XR/Calvados shutdown
    #
    if [[ $unmount_flag -ne 0 ]] ; then
        app_hosting_log "Failed to unmount one or more mounts from docker's mount namespace"
        false
        return
    fi

    docker_pid_end=`cat /var/run/docker.pid 2>/dev/null`
    if [[ "$docker_pid_start" != "$docker_pid_end" ]] ; then
        app_hosting_log "Docker daemon's pid changed while removing mounts."
        false
        return
    fi

    true
    return
}