/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved.
 * iSulad licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *     http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 * Author: tanyifeng
 * Create: 2017-11-22
 * Description: provide cri runtime service function
 *********************************************************************************/
#include "cri_runtime_service.h"

#include <iostream>
#include <memory>
#include <vector>
#include <map>
#include <string>
#include <grpc++/grpc++.h>
#include <unistd.h>

#include "log.h"
#include "utils.h"
#include "config.h"
#include "host_config.h"
#include "cri_helpers.h"
#include "network_plugin.h"
#include "container_inspect.h"

namespace CRIRuntimeService {
std::string Constants::namespaceModeHost { "host" };
std::string Constants::nameDelimiter { "_" };
char Constants::nameDelimiterChar { '_' };
std::string Constants::kubePrefix { "k8s" };
std::string Constants::sandboxContainerName { "POD" };
std::string Constants::kubeAPIVersion { "0.1.0" };
std::string Constants::iSulaRuntimeName { "iSulad" };
std::string Constants::RESOLV_CONF_PATH { "/etc/resolv.conf" };
} // namespace CRIRuntimeService
CRIRuntimeServiceImpl::CRIRuntimeServiceImpl()
{
    m_cb = get_service_callback();
    if (m_cb == nullptr) {
        ERROR("Get callback failed");
    }
}

void CRIRuntimeServiceImpl::VersionResponseToGRPC(container_version_response *response,
                                                  runtime::v1alpha2::VersionResponse *gResponse, Errors &error)
{
    gResponse->set_version(CRIRuntimeService::Constants::kubeAPIVersion);
    gResponse->set_runtime_name(CRIRuntimeService::Constants::iSulaRuntimeName);
    gResponse->set_runtime_version(response->version ? response->version : "");
    gResponse->set_runtime_api_version(VERSION);
}

void CRIRuntimeServiceImpl::Init(Network::NetworkPluginConf mConf, const std::string &podSandboxImage, Errors &err)
{
    if (!podSandboxImage.empty()) {
        m_podSandboxImage = podSandboxImage;
    } else {
        m_podSandboxImage = CRIHelpers::GetDefaultSandboxImage(err);
        if (err.NotEmpty()) {
            return;
        }
    }

    std::vector<std::shared_ptr<Network::NetworkPlugin>> plugins;
    Network::ProbeNetworkPlugins(mConf.GetPluginConfDir(), mConf.GetPluginBinDir(), &plugins);

    std::shared_ptr<Network::NetworkPlugin> chosen { nullptr };
    Network::InitNetworkPlugin(&plugins, mConf.GetPluginName(), this, mConf.GetHairpinMode(),
                               mConf.GetNonMasqueradeCIDR(), mConf.GetMTU(), &chosen, err);
    if (err.NotEmpty()) {
        ERROR("Init network plugin failed: %s", err.GetCMessage());
        return;
    }

    m_pluginManager = std::make_shared<Network::PluginManager>(chosen);
}

void CRIRuntimeServiceImpl::Version(const std::string &apiVersion, runtime::v1alpha2::VersionResponse *versionResponse,
                                    Errors &error)
{
    (void)apiVersion;

    if (m_cb == nullptr || m_cb->container.version == nullptr) {
        error.SetError("Unimplemented callback");
        return;
    }

    container_version_response *response { nullptr };
    if (m_cb->container.version(nullptr, &response) != 0) {
        if (response != nullptr && response->errmsg) {
            error.SetError(response->errmsg);
        } else {
            error.SetError("Failed to call version callback");
        }
        goto cleanup;
    }
    VersionResponseToGRPC(response, versionResponse, error);

cleanup:
    free_container_version_response(response);
}

void CRIRuntimeServiceImpl::UpdateRuntimeConfig(const runtime::v1alpha2::RuntimeConfig &config, Errors &error)
{
    const std::string NET_PLUGIN_EVENT_POD_CIDR_CHANGE { "pod-cidr-change" };
    const std::string NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR { "pod-cidr" };

    INFO("iSulad cri received runtime config: %s", config.network_config().pod_cidr().c_str());
    if (m_pluginManager != nullptr && config.has_network_config() && !(config.network_config().pod_cidr().empty())) {
        std::map<std::string, std::string> events;
        events[NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = config.network_config().pod_cidr();
        m_pluginManager->Event(NET_PLUGIN_EVENT_POD_CIDR_CHANGE, events);
    }
    return;
}

std::unique_ptr<runtime::v1alpha2::RuntimeStatus> CRIRuntimeServiceImpl::Status(Errors &error)
{
    std::unique_ptr<runtime::v1alpha2::RuntimeStatus> status(new (std::nothrow) runtime::v1alpha2::RuntimeStatus);
    if (status == nullptr) {
        error.SetError("Out of memory");
        return nullptr;
    }

    runtime::v1alpha2::RuntimeCondition *runtimeReady = status->add_conditions();
    runtimeReady->set_type(CRIHelpers::Constants::RUNTIME_READY);
    runtimeReady->set_status(true);
    runtime::v1alpha2::RuntimeCondition *networkReady = status->add_conditions();
    networkReady->set_type(CRIHelpers::Constants::NETWORK_READY);
    networkReady->set_status(true);

    container_version_response *response { nullptr };
    if (m_cb == nullptr || m_cb->container.version == nullptr || m_cb->container.version(nullptr, &response) != 0) {
        runtimeReady->set_status(false);
        runtimeReady->set_reason("iSuladDaemonNotReady");
        std::string msg = "iSulad: failed to get iSulad version: ";
        if (response != nullptr && response->errmsg != nullptr) {
            msg += response->errmsg;
        } else {
            msg += "Get version callback failed";
        }
        runtimeReady->set_message(msg);
    }
    free_container_version_response(response);

    // Get status of network
    m_pluginManager->Status(error);
    if (error.NotEmpty()) {
        networkReady->set_status(false);
        networkReady->set_reason("NetworkPluginNotReady");
        networkReady->set_message("iSulad: network plugin is not ready: " + error.GetMessage());
        error.Clear();
    }
    return status;
}

std::string CRIRuntimeServiceImpl::GetNetNS(const std::string &podSandboxID, Errors &err)
{
    int ret;
    char fullpath[PATH_MAX] { 0 };
    std::string result { "" };
    const std::string NetNSFmt { "/proc/%d/ns/net" };

    container_inspect *inspect_data = InspectContainer(podSandboxID, err);
    if (inspect_data == nullptr) {
        goto cleanup;
    }
    if (inspect_data->state->pid == 0) {
        err.Errorf("cannot find network namespace for the terminated container %s", podSandboxID.c_str());
        goto cleanup;
    }
    ret = snprintf(fullpath, sizeof(fullpath), NetNSFmt.c_str(), inspect_data->state->pid);
    if ((size_t)ret >= sizeof(fullpath) || ret < 0) {
        err.SetError("Sprint nspath failed");
        goto cleanup;
    }
    result = fullpath;

cleanup:
    free_container_inspect(inspect_data);
    return result;
}

