#!/usr/bin/env bash
#
# Copyright (c) 2018-2023 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0

# This file contains common functions that
# are being used by our metrics and integration tests

this_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export repo_root_dir="$(cd "${this_script_dir}/../" && pwd)"

source "${this_script_dir}/error.sh"

# Kata tests directory used for storing various test-related artifacts.
KATA_TESTS_BASEDIR="${KATA_TESTS_BASEDIR:-/var/log/kata-tests}"

# Directory that can be used for storing test logs.
KATA_TESTS_LOGDIR="${KATA_TESTS_LOGDIR:-${KATA_TESTS_BASEDIR}/logs}"

# Directory that can be used for storing test data.
KATA_TESTS_DATADIR="${KATA_TESTS_DATADIR:-${KATA_TESTS_BASEDIR}/data}"

# Directory that can be used for storing cache kata components
KATA_TESTS_CACHEDIR="${KATA_TESTS_CACHEDIR:-${KATA_TESTS_BASEDIR}/cache}"

KATA_HYPERVISOR="${KATA_HYPERVISOR:-qemu}"

RUNTIME="${RUNTIME:-containerd-shim-kata-v2}"

export branch="${target_branch:-"$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')"}"

function die() {
	local msg="$*"

	if [ -z "${KATA_TEST_VERBOSE:-}" ]; then
		echo -e "[$(basename $0):${BASH_LINENO[0]}] ERROR: $msg" >&2
		exit 1
	fi

	echo >&2 "ERROR: $msg"

	# This function is called to indicate a fatal error occurred, so
	# the caller of this function is the site of the detected error.
	local error_location
	error_location=$(caller 0)

	local line
	local func
	local file

	line=$(echo "$error_location"|awk '{print $1}')
	func=$(echo "$error_location"|awk '{print $2}')
	file=$(echo "$error_location"|awk '{print $3}')

	local path
	path=$(resolve_path "$file")

	dump_details \
		"${line}" \
		"${func}" \
		"${path}"

	exit 1
}

function warn() {
	local msg="$*"
	echo -e "[$(basename $0):${BASH_LINENO[0]}] WARNING: $msg"
}

function info() {
	local msg="$*"
	echo -e "[$(basename $0):${BASH_LINENO[0]}] INFO: $msg"
}

function bats_unbuffered_info() {
	local msg="$*"
	# Ask bats to print this text immediately rather than buffering until the end of a test case.
	echo -e "[$(basename $0):${BASH_LINENO[0]}] UNBUFFERED: INFO: $msg" >&3
}

function handle_error() {
	local exit_code="${?}"
	local line_number="${1:-}"
	echo -e "[$(basename $0):$line_number] ERROR: $(eval echo "$BASH_COMMAND")"
	exit "${exit_code}"
}
trap 'handle_error $LINENO' ERR

# A wrapper function for kubectl with retry logic
# runs the command up to 5 times with a 15-second interval
# to ensure successful execution
function kubectl_retry() {
	local max_tries=5
	local interval=15
	local i=0
	while true; do
		kubectl "$@" && return 0 || true
		i=$((i + 1))
		[ $i -lt $max_tries ] && echo "'kubectl $*' failed, retrying in $interval seconds" 1>&2 || break
		sleep $interval
	done
	echo "'kubectl $*' failed after $max_tries tries" 1>&2 && return 1
}

function waitForProcess() {
	wait_time="$1"
	sleep_time="$2"
	cmd="$3"
	while [ "$wait_time" -gt 0 ]; do
		if eval "$cmd"; then
			return 0
		else
			sleep "$sleep_time"
			wait_time=$((wait_time-sleep_time))
		fi
	done
	return 1
}

# Check if the $1 argument is the name of a 'known'
# Kata runtime. Of course, the end user can choose any name they
# want in reality, but this function knows the names of the default
# and recommended Kata docker runtime install names.
function is_a_kata_runtime() {
	if [ "$1" = "containerd-shim-kata-v2" ] || [ "$1" = "io.containerd.kata.v2" ]; then
		echo "1"
	else
		echo "0"
	fi
}

# Gets versions and paths of all the components
# list in kata-env
function extract_kata_env() {
	local cmd
	local config_path
	local runtime_version
	local runtime_version_semver
	local runtime_version_commit
	local runtime_path
	local hypervisor_path
	local virtiofsd_path
	local initrd_path
	local kata_env
	local req_memory_amount
	local req_num_vcpus

	case "${KATA_HYPERVISOR}" in
		dragonball)
			cmd=kata-ctl
			config_path=".runtime.config.path"
			runtime_version=".runtime.version"
			runtime_version_semver="semver"
			runtime_version_commit="commit"
			runtime_path=".runtime.path"
			hypervisor_path=".hypervisor.path"
			virtio_fs_daemon_path=".hypervisor.virtio_fs_daemon"
			initrd_path=".initrd.path"
			shared_fs=".hypervisor.shared_fs"
			;;
		*)
			cmd=kata-runtime
			config_path=".Runtime.Config.Path"
			runtime_version=".Runtime.Version"
			runtime_version_semver="Semver"
			runtime_version_commit="Commit"
			runtime_path=".Runtime.Path"
			hypervisor_path=".Hypervisor.Path"
			virtio_fs_daemon_path=".Hypervisor.VirtioFSDaemon"
			initrd_path=".Initrd.Path"
			shared_fs=".Hypervisor.SharedFS"
			req_memory_amount=".Host.Memory.Total"
			req_num_vcpus=""
			;;
	esac
	kata_env="$(sudo ${cmd} env --json)"

	RUNTIME_CONFIG_PATH="$(echo "${kata_env}" | jq -r ${config_path})"
	RUNTIME_VERSION="$(echo "${kata_env}" | jq -r ${runtime_version} | grep ${runtime_version_semver} | cut -d'"' -f4)"
	RUNTIME_COMMIT="$(echo "${kata_env}" | jq -r ${runtime_version} | grep ${runtime_version_commit} | cut -d'"' -f4)"
	RUNTIME_PATH="$(echo "${kata_env}" | jq -r ${runtime_path})"
	SHARED_FS="$(echo "${kata_env}" | jq -r ${shared_fs})"

	# get the requested memory and num of vcpus from the kata config file.
	config_content="$(cat ${RUNTIME_CONFIG_PATH} | grep -vE "^#")"
	REQ_MEMORY="$(echo "${config_content}" | grep -i 'default_memory =' | cut -d  "=" -f2 | awk '{print $1}')"
	REQ_NUM_VCPUS="$(echo "${config_content}" | grep -i 'default_vcpus =' | cut -d  "=" -f2 | awk '{print $1}')"

	# Shimv2 path is being affected by https://github.com/kata-containers/kata-containers/issues/1151
	SHIM_PATH=$(command -v containerd-shim-kata-v2)
	[ -L ${SHIM_PATH} ] && SHIM_PATH=$(readlink ${SHIM_PATH})

	SHIM_VERSION=${RUNTIME_VERSION}

	HYPERVISOR_PATH=$(echo "${kata_env}" | jq -r ${hypervisor_path})
	VIRTIOFSD_PATH=$(echo "${kata_env}" | jq -r ${virtio_fs_daemon_path})
	INITRD_PATH=$(echo "${kata_env}" | jq -r ${initrd_path})

	# TODO: there is no ${cmd} of rust version currently
	if [ "${KATA_HYPERVISOR}" != "dragonball" ]; then
		if [ "${KATA_HYPERVISOR}" = "stratovirt" ]; then
			HYPERVISOR_VERSION=$(sudo -E ${HYPERVISOR_PATH} -version | head -n1)
		else
			HYPERVISOR_VERSION=$(sudo -E ${HYPERVISOR_PATH} --version | head -n1)
		fi
	fi
}

# Checks that processes are not running
function check_processes() {
	extract_kata_env

	# Only check the kata-env if we have managed to find the kata executable...
	if [ -x "$RUNTIME_PATH" ]; then
		local vsock_configured=$($RUNTIME_PATH env | awk '/UseVSock/ {print $3}')
		local vsock_supported=$($RUNTIME_PATH env | awk '/SupportVSock/ {print $3}')
	else
		local vsock_configured="false"
		local vsock_supported="false"
	fi

	general_processes=( ${HYPERVISOR_PATH} ${SHIM_PATH} )

	for i in "${general_processes[@]}"; do
		if pgrep -f "$i"; then
			die "Found unexpected ${i} present"
		fi
	done
}

# Clean environment, this function will try to remove all
# stopped/running containers.
function clean_env()
{
	# If the timeout has not been set, default it to 30s
	# Docker has a built in 10s default timeout, so make ours
	# longer than that.
	KATA_DOCKER_TIMEOUT=${KATA_DOCKER_TIMEOUT:-30}
	containers_running=$(sudo timeout ${KATA_DOCKER_TIMEOUT} docker ps -q)

	if [ ! -z "$containers_running" ]; then
		# First stop all containers that are running
		# Use kill, as the containers are generally benign, and most
		# of the time our 'stop' request ends up doing a `kill` anyway
		sudo timeout ${KATA_DOCKER_TIMEOUT} docker kill $containers_running

		# Remove all containers
		sudo timeout ${KATA_DOCKER_TIMEOUT} docker rm -f $(docker ps -qa)
	fi
}

function clean_env_ctr()
{
	local count_running="$(sudo ctr c list -q | wc -l)"
	local remaining_attempts=10
	declare -a running_tasks=()
	local count_tasks=0
	local sleep_time=1
	local time_out=10

	[ "$count_running" -eq "0" ] && return 0

	readarray -t running_tasks < <(sudo ctr t list -q)

	info "Wait until the containers gets removed"

	for task_id in "${running_tasks[@]}"; do
		sudo timeout -s SIGKILL 30s ctr t kill -a -s SIGKILL ${task_id} >/dev/null 2>&1 || true
		sleep 0.5
	done

	# do not stop if the command fails, it will be evaluated by waitForProcess
	local cmd="[[ $(sudo ctr tasks list | grep -c "STOPPED") == "$count_running" ]]" || true

	local res="ok"
	waitForProcess "${time_out}" "${sleep_time}" "$cmd" || res="fail"

	[ "$res" == "ok" ] || sudo systemctl restart containerd

	while (( remaining_attempts > 0 )); do
		[ "${RUNTIME}" == "runc" ] && sudo ctr tasks rm -f $(sudo ctr task list -q)
		sudo ctr c rm $(sudo ctr c list -q) >/dev/null 2>&1

		count_running="$(sudo ctr c list -q | wc -l)"

		[ "$count_running" -eq 0 ] && break

		remaining_attempts=$((remaining_attempts-1))
		sleep 0.5
	done

	count_tasks="$(sudo ctr t list -q | wc -l)"

	if (( count_tasks > 0 )); then
		die "Can't remove running containers."
	fi
}

# Restarts a systemd service while ensuring the start-limit-burst is set to 0.
# Outputs warnings to stdio if something has gone wrong.
#
# Returns 0 on success, 1 otherwise
function restart_systemd_service_with_no_burst_limit() {
	local service=$1
	info "restart $service service"

	local active=$(systemctl show "$service.service" -p ActiveState | cut -d'=' -f2)
	[ "$active" == "active" ] || warn "Service $service is not active"

	local start_burst=$(systemctl show "$service".service -p StartLimitBurst | cut -d'=' -f2)
	if [ "$start_burst" -ne 0 ]
	then
		local unit_file=$(systemctl show "$service.service" -p FragmentPath | cut -d'=' -f2)
		[ -f "$unit_file" ] || { warn "Can't find $service's unit file: $unit_file"; return 1; }

		# If the unit file is in /lib or /usr/lib, copy it to /etc
		if [[ $unit_file =~ ^/(usr/)?lib/ ]]; then
			tmp_unit_file="/etc/${unit_file#*lib/}"
			sudo cp "$unit_file" "$tmp_unit_file"
			unit_file="$tmp_unit_file"
		fi

		local start_burst_set=$(sudo grep StartLimitBurst $unit_file | wc -l)
		if [ "$start_burst_set" -eq 0 ]
		then
			sudo sed -i '/\[Service\]/a StartLimitBurst=0' "$unit_file"
		else
			sudo sed -i 's/StartLimitBurst.*$/StartLimitBurst=0/g' "$unit_file"
		fi

		sudo systemctl daemon-reload
	fi

	sudo systemctl restart "$service"

	local state=$(systemctl show "$service.service" -p SubState | cut -d'=' -f2)
	[ "$state" == "running" ] || { warn "Can't restart the $service service"; return 1; }

	start_burst=$(systemctl show "$service.service" -p StartLimitBurst | cut -d'=' -f2)
	[ "$start_burst" -eq 0 ] || { warn "Can't set start burst limit for $service service"; return 1; }

	return 0
}

function restart_containerd_service() {
	restart_systemd_service_with_no_burst_limit containerd || return 1

	local retries=5
	local counter=0
	until [ "$counter" -ge "$retries" ] || sudo ctr --connect-timeout 1s version > /dev/null 2>&1
	do
		info "Waiting for containerd socket..."
		((counter++))
	done

	[ "$counter" -ge "$retries" ] && { warn "Can't connect to containerd socket"; return 1; }

	clean_env_ctr
	return 0
}

function restart_crio_service() {
	sudo systemctl restart crio
}

# Configures containerd
function overwrite_containerd_config() {
	containerd_config="/etc/containerd/config.toml"
	base_config_dir=$(dirname "${containerd_config}")
	sudo mkdir -p "${base_config_dir}"
	sudo tee "${containerd_config}" << EOF
version = 2

[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    [plugins."io.containerd.grpc.v1.cri".containerd]
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          base_runtime_spec = ""
          cni_conf_dir = ""
          cni_max_conf_num = 0
          container_annotations = []
          pod_annotations = []
          privileged_without_host_devices = false
          runtime_engine = ""
          runtime_path = ""
          runtime_root = ""
          runtime_type = "io.containerd.runc.v2"
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            BinaryName = ""
            CriuImagePath = ""
            CriuPath = ""
            CriuWorkPath = ""
            IoGid = 0
            IoUid = 0
            NoNewKeyring = false
            NoPivotRoot = false
            Root = ""
            ShimCgroup = ""
            SystemdCgroup = false
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
          runtime_type = "io.containerd.kata.v2"
EOF
}

# Configures CRI-O
function overwrite_crio_config() {
	crio_conf_d="/etc/crio/crio.conf.d"
	sudo mkdir -p ${crio_conf_d}

	kata_config="${crio_conf_d}/99-kata-containers"
	sudo tee "${kata_config}" << EOF
[crio.runtime.runtimes.kata]
runtime_path = "/usr/local/bin/containerd-shim-kata-v2"
runtime_type = "vm"
runtime_root = "/run/vc"
runtime_config_path = "/opt/kata/share/defaults/kata-containers/configuration.toml"
privileged_without_host_devices = true
EOF

	debug_config="${crio_conf_d}/100-debug"
	sudo tee "${debug_config}" << EOF
[crio]
log_level = "debug"
EOF
}

function install_kata_core() {
	declare -r katadir="$1"
	declare -r destdir="/"
	declare -r kata_tarball="kata-static.tar.zst"

	# Removing previous kata installation
	sudo rm -rf "${katadir}"

	pushd "${kata_tarball_dir}"
	sudo tar --zstd -xvf "${kata_tarball}" -C "${destdir}"
	popd
}

function install_kata_tools() {
	declare -r katadir="/opt/kata"

	# TODO: implement a better way to install the tools - see issue #8864.
	install_kata_core "${katadir}"
}

function install_kata() {
	declare -r katadir="/opt/kata"
	declare -r local_bin_dir="/usr/local/bin/"

	install_kata_core "${katadir}"

	# create symbolic links to kata components
	for b in "${katadir}"/bin/* ; do
		sudo ln -sf "${b}" "${local_bin_dir}/$(basename $b)"
	done

	if [ "${CONTAINER_ENGINE:=containerd}" = "containerd" ]; then
		check_containerd_config_for_kata
		restart_containerd_service
	else
		overwrite_crio_config
		restart_crio_service
	fi

	load_vhost_mods
}

# creates a new kata configuration.toml hard link that
# points to the hypervisor passed by KATA_HYPERVISOR env var.
function enabling_hypervisor() {
	declare -r KATA_DIR="/opt/kata"
	declare -r CONTAINERD_SHIM_KATA="/usr/local/bin/containerd-shim-kata-${KATA_HYPERVISOR}-v2"

	case "${KATA_HYPERVISOR}" in
		dragonball|cloud-hypervisor|qemu-runtime-rs|qemu-se-runtime-rs)
			sudo ln -sf "${KATA_DIR}/runtime-rs/bin/containerd-shim-kata-v2" "${CONTAINERD_SHIM_KATA}"
			declare -r CONFIG_DIR="${KATA_DIR}/share/defaults/kata-containers/runtime-rs"
			;;
		*)
			sudo ln -sf "${KATA_DIR}/bin/containerd-shim-kata-v2" "${CONTAINERD_SHIM_KATA}"
			declare -r CONFIG_DIR="${KATA_DIR}/share/defaults/kata-containers"
			;;
	esac

	declare -r SRC_HYPERVISOR_CONFIG="${CONFIG_DIR}/configuration-${KATA_HYPERVISOR}.toml"
	declare -r DEST_KATA_CONFIG="${CONFIG_DIR}/configuration.toml"

	sudo ln -sf "${SRC_HYPERVISOR_CONFIG}" "${DEST_KATA_CONFIG}"

	export KATA_CONFIG_PATH="${DEST_KATA_CONFIG}"
}


function check_containerd_config_for_kata() {
	# check containerd config
	declare -r line1="default_runtime_name = \"kata\""
	declare -r line2="runtime_type = \"io.containerd.kata.v2\""
	declare -r num_lines_containerd=2
	declare -r containerd_path="/etc/containerd/config.toml"
	local count_matches=$(grep -ic  "$line1\|$line2" "${containerd_path}")

	if [ "${count_matches}" = "${num_lines_containerd}" ]; then
		info "containerd ok"
	else
		info "overwriting containerd configuration w/ a valid one"
		overwrite_containerd_config
	fi
}

function ensure_yq() {
	: "${GOPATH:=${GITHUB_WORKSPACE:-$HOME/go}}"
	export GOPATH
	export PATH="${GOPATH}/bin:${PATH}"
	INSTALL_IN_GOPATH=true "${repo_root_dir}/ci/install_yq.sh"
	hash -d yq 2> /dev/null || true # yq is preinstalled on GHA Ubuntu 22.04 runners so we clear Bash's PATH cache.
}

function ensure_helm() {
	ensure_yq
	# The get-helm-3 script will take care of downloaading and installing Helm
	# properly on the system respecting ARCH, OS and other configurations.
	DESIRED_VERSION=$(get_from_kata_deps ".externals.helm.version")
	export DESIRED_VERSION

	# Check if helm is available in the system's PATH
	if ! command -v helm &> /dev/null; then
		echo "Helm is not installed. Installing Helm..."
		curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
		# Verify the installation
		if command -v helm &> /dev/null; then
			echo "Helm installed successfully."
		else
			echo "Failed to install Helm."
			exit 1
		fi
	else
		echo "Helm is already installed."
	fi
}

# dependency: What we want to get the version from the versions.yaml file
function get_from_kata_deps() {
        versions_file="${repo_root_dir}/versions.yaml"

        command -v yq &>/dev/null || die 'yq command is not in your $PATH'

        yq_version=$(yq --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | cut -d. -f1)
        if [ "$yq_version" -eq 3 ]; then
          dependency=$(echo "$1" | sed "s/^\.//g")
          result=$("yq" read "$versions_file" "$dependency")
        else
          dependency=$1
          result=$("yq" "$dependency | explode (.)" "$versions_file")
        fi

        [ "$result" = "null" ] && result=""
        echo "$result"
}

# project: org/repo format
# base_version: ${major}.${minor}
# allow_unstable: Whether alpha / beta releases should be considered (default: false)
function get_latest_patch_release_from_a_github_project() {
        project="${1}"
        base_version="${2}"
        allow_unstable="${3:-false}"

        regex="^${base_version}.[0-9]*$"
        if [[ "${allow_unstable}" == "true" ]]; then
                regex="^${base_version}.[0-9]*"
        fi

        curl \
          ${GH_TOKEN:+--header "Authorization: Bearer ${GH_TOKEN:-}"} \
          --fail-with-body \
          --show-error \
          --silent \
          "https://api.github.com/repos/${project}/releases" \
          | jq -r .[].tag_name \
          | grep "${regex}" -m1
}

# base_version: The version to be intalled in the ${major}.${minor} format
function clone_cri_containerd() {
	base_version="${1}"

	project="containerd/containerd"
	version=$(get_latest_patch_release_from_a_github_project "${project}" "${base_version}")

	rm -rf containerd
	git clone -b ${version} https://github.com/${project}
}

# project: org/repo format
# version: the version of the tarball that will be downloaded
# tarball-name: the name of the tarball that will be downloaded
function download_github_project_tarball() {
	project="${1}"
	version="${2}"
	tarball_name="${3}"

	wget https://github.com/${project}/releases/download/${version}/${tarball_name}
}

# version: The version to be intalled
function install_cni_plugins() {
	version="${1}"

	project="containernetworking/plugins"
	tarball_name="cni-plugins-linux-$(${repo_root_dir}/tests/kata-arch.sh -g)-${version}.tgz"

	download_github_project_tarball "${project}" "${version}" "${tarball_name}"
	sudo mkdir -p /opt/cni/bin
	sudo tar -xvf "${tarball_name}" -C /opt/cni/bin
	rm -f "${tarball_name}"

	cni_config="/etc/cni/net.d/10-containerd-net.conflist"
	if [ ! -f ${cni_config} ];then
		sudo mkdir -p /etc/cni/net.d
		sudo tee "${cni_config}" << EOF
{
  "cniVersion": "1.0.0",
  "name": "containerd-net",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "cni0",
      "isGateway": true,
      "ipMasq": true,
      "promiscMode": true,
      "ipam": {
        "type": "host-local",
        "ranges": [
          [{
            "subnet": "10.88.0.0/16"
          }],
          [{
            "subnet": "2001:4860:4860::/64"
          }]
        ],
        "routes": [
          { "dst": "0.0.0.0/0" },
          { "dst": "::/0" }
        ]
      }
    },
    {
      "type": "portmap",
      "capabilities": {"portMappings": true}
    }
  ]
}
EOF
	fi
}

# version: The version to be installed
function install_runc() {
	base_version="${1}"
	project="opencontainers/runc"
	version=$(get_latest_patch_release_from_a_github_project "${project}" "${base_version}")

	if [ -f /usr/local/sbin/runc ]; then
		return
	fi

	binary_name="runc.$(${repo_root_dir}/tests/kata-arch.sh -g)"
	download_github_project_tarball "${project}" "${version}" "${binary_name}"

	sudo mkdir -p /usr/local/sbin
	sudo mv $binary_name /usr/local/sbin/runc
	sudo chmod +x /usr/local/sbin/runc
}

# base_version: The version to be intalled in the ${major}.${minor} format
function install_cri_containerd() {
	base_version="${1}"

	project="containerd/containerd"
	version=$(get_latest_patch_release_from_a_github_project "${project}" "${base_version}" "true")

	tarball_name="containerd-${version//v}-linux-$(${repo_root_dir}/tests/kata-arch.sh -g).tar.gz"

	download_github_project_tarball "${project}" "${version}" "${tarball_name}"
	#add the "--keep-directory-symlink" option to make sure the untar wouldn't override the
	#system rootfs's bin/sbin directory which would be a symbol link to /usr/bin or /usr/sbin.
	if [ ! -f /usr/local ]; then
		sudo mkdir -p /usr/local
	fi
	sudo tar --keep-directory-symlink -xvf "${tarball_name}" -C /usr/local/
	rm -f "${tarball_name}"

	sudo mkdir -p /etc/containerd
	containerd config default | sudo tee /etc/containerd/config.toml

	containerd_service="/etc/systemd/system/containerd.service"

	if [ ! -f ${containerd_service} ]; then
		sudo mkdir -p /etc/systemd/system
		sudo tee ${containerd_service}  <<EOF
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target
EOF
	fi
}

# base_version: The version to be intalled in the ${major}.${minor} format
function install_cri_tools() {
	base_version="${1}"

	project="kubernetes-sigs/cri-tools"
	version=$(get_latest_patch_release_from_a_github_project "${project}" "${base_version}")

	tarball_name="crictl-${version}-linux-$(${repo_root_dir}/tests/kata-arch.sh -g).tar.gz"

	download_github_project_tarball "${project}" "${version}" "${tarball_name}"
	sudo tar -xvf "${tarball_name}" -C /usr/local/bin
	rm -f "${tarball_name}"
}

function install_nydus() {
	version="${1}"

	project="dragonflyoss/image-service"
	tarball_name="nydus-static-${version}-linux-$(${repo_root_dir}/tests/kata-arch.sh -g).tgz"

	download_github_project_tarball "${project}" "${version}" "${tarball_name}"
	sudo tar xfz "${tarball_name}" -C /usr/local/bin --strip-components=1
	rm -f "${tarball_name}"
}

function install_nydus_snapshotter() {
	version="${1}"

	project="containerd/nydus-snapshotter"
	tarball_name="nydus-snapshotter-${version}-$(uname -s| tr A-Z a-z)-$(${repo_root_dir}/tests/kata-arch.sh -g).tar.gz"

	download_github_project_tarball "${project}" "${version}" "${tarball_name}"
	sudo tar xfz "${tarball_name}" -C /usr/local/bin --strip-components=1
	rm -f "${tarball_name}"
}

# version: the CRI-O version to be installe
function install_crio() {
	local version=${1}

	sudo mkdir -p /etc/apt/keyrings
	sudo mkdir -p /etc/apt/sources.list.d

	curl -fsSL https://pkgs.k8s.io/addons:/cri-o:/stable:/v${version}/deb/Release.key | \
		sudo gpg --dearmor -o /etc/apt/keyrings/cri-o-apt-keyring.gpg
	echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://pkgs.k8s.io/addons:/cri-o:/stable:/v${version}/deb/ /" | \
		sudo tee /etc/apt/sources.list.d/cri-o.list

	sudo apt update
	sudo apt install -y cri-o

	# We need to set the default capabilities to ensure our tests will pass
	# See: https://github.com/kata-containers/kata-containers/issues/8034
	sudo mkdir -p /etc/crio/crio.conf.d/
	cat <<EOF | sudo tee /etc/crio/crio.conf.d/00-default-capabilities
[crio]
storage_option = [
	"overlay.skip_mount_home=true",
]
[crio.runtime]
default_capabilities = [
       "CHOWN",
       "DAC_OVERRIDE",
       "FSETID",
       "FOWNER",
       "SETGID",
       "SETUID",
       "SETPCAP",
       "NET_BIND_SERVICE",
       "KILL",
       "SYS_CHROOT",
]
EOF

	sudo systemctl enable --now crio
}

function install_docker() {
	# Add Docker's official GPG key
	sudo apt-get update
	sudo apt-get -y install ca-certificates curl gnupg
	sudo install -m 0755 -d /etc/apt/keyrings
	curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
	sudo chmod a+r /etc/apt/keyrings/docker.gpg

	# Add the repository to Apt sources:
	echo \
		"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
		"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
		sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
	sudo apt-get update

	sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
}

# Convert architecture to the name used by golang
function arch_to_golang() {
	local -r arch="$(uname -m)"

	case "${arch}" in
		aarch64|arm64) echo "arm64";;
		ppc64le) echo "${arch}";;
		riscv64) echo "${arch}";;
		x86_64) echo "amd64";;
		s390x) echo "s390x";;
		*) die "unsupported architecture: ${arch}";;
	esac
}

# Convert architecture to the name used by rust
function arch_to_rust() {
	local -r arch="$(uname -m)"

	case "${arch}" in
		aarch64|arm64) echo "aarch64";;
		ppc64le) echo "powerpc64le";;
		riscv64) echo "riscv64gc";;
		x86_64) echo "${arch}";;
		s390x) echo "${arch}";;
		*) die "unsupported architecture: ${arch}";;
	esac
}

# Convert architecture to the name used by the Linux kernel build system
function arch_to_kernel() {
	local -r arch="$(uname -m)"

	case "${arch}" in
		aarch64|arm64) echo "arm64";;
		ppc64le) echo "powerpc";;
		x86_64) echo "${arch}";;
		s390x) echo "s390x";;
		*) die "unsupported architecture: ${arch}";;
	esac
}

# Obtain a list of the files the PR changed.
# Returns the information in format "${filter}\t${file}".
get_pr_changed_file_details_full()
{
        # List of filters used to restrict the types of file changes.
        # See git-diff-tree(1) for further info.
        local filters=""

        # Added file
        filters+="A"

        # Copied file
        filters+="C"

        # Modified file
        filters+="M"

        # Renamed file
        filters+="R"

        git diff-tree \
                -r \
                --name-status \
                --diff-filter="${filters}" \
                "origin/${branch}" HEAD
}

# Obtain a list of the files the PR changed, ignoring vendor files.
# Returns the information in format "${filter}\t${file}".
get_pr_changed_file_details()
{
        get_pr_changed_file_details_full | grep -v "vendor/"
}

function get_dep_from_yaml_db(){
        local versions_file="$1"
        local dependency="$2"

        [ ! -f "$versions_file" ] && die "cannot find $versions_file"

        "${repo_root_dir}/ci/install_yq.sh" >&2

        result=$("${GOPATH}/bin/yq" "$dependency" "$versions_file")
        [ "$result" = "null" ] && result=""
        echo "$result"
}

function get_test_version(){
        local dependency="$1"

        local db
        local cidir

        # directory of this script, not the caller
        local cidir=$(dirname "${BASH_SOURCE[0]}")

        db="${cidir}/../versions.yaml"

        get_dep_from_yaml_db "${db}" ".${dependency}"
}

# Load vhost, vhost_net, vhost_vsock modules.
function load_vhost_mods() {
	sudo modprobe vhost
	sudo modprobe vhost_net
	sudo modprobe vhost_vsock
}

function run_static_checks()
{
	# Make sure we have the targeting branch
	git remote set-branches --add origin "${branch}"
	git fetch -a
	bash "$this_script_dir/static-checks.sh" "$@"
}

function run_docs_url_alive_check()
{
	# Make sure we have the targeting branch
	git remote set-branches --add origin "${branch}"
	git fetch -a
	bash "$this_script_dir/static-checks.sh" --docs --all "github.com/kata-containers/kata-containers"
}

function run_get_pr_changed_file_details()
{
	# Make sure we have the targeting branch
	git remote set-branches --add origin "${branch}"
	git fetch -a
	get_pr_changed_file_details || true
}

# Check if the 1st argument version is greater than and equal to 2nd one
# Version format: [0-9]+ separated by period (e.g. 2.4.6, 1.11.3 and etc.)
#
# Parameters:
#	$1	- a version to be tested
#	$2	- a target version
#
# Return:
# 	0 if $1 is greater than and equal to $2
#	1 otherwise
function version_greater_than_equal() {
	local current_version=$1
	local target_version=$2
	smaller_version=$(echo -e "$current_version\n$target_version" | sort -V | head -1)
	if [ "${smaller_version}" = "${target_version}" ]; then
		return 0
	else
		return 1
	fi
}
