-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- 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 v2 for more details.

-- Description: AC

local log = require 'mc.logging'
local class = require 'mc.class'
local skynet = require 'skynet'
local context = require 'mc.context'
local utils = require 'types.utils'
local singleton = require 'mc.singleton'
local client = require 'fructrl.client'
local worker = require 'worker.core'

local PERSISTENCE_PATH = '/bmc/kepler/persistence/MicroComponent'
local OPEN_STR = 'open'
local CLOSE_STR = 'close'
local MAX_RETRY_TIMES <const> = 3
local acdown_retry_times = 0

local c_accycle = class()

function c_accycle:ctor(db)
    self.db = db
    self.objs = {}
    log:notice_easy("create ac_cycle class successfully, uptime: %s.", utils.uptime())
end

local function get_reboot_object()
    local ret = nil
    for _, v in pairs(client:GetRebootObjects()) do
        if v.extra_params.Path == PERSISTENCE_PATH then
            ret = v
            break
        end
    end
    if not ret then
        log:error("Failed to get reboot object, but accycle will continue to executed")
        -- 对象为nil时,调方法的报错无法被捕获,所以必须不为nil
        return {}
    end
    return ret
end

local function persistence_api(cb, re_obj, ctx, type)
    local ok, rsp = pcall(cb, re_obj, ctx)
    if not ok then
        log:error('Failed to invoke the %s method of persistence, err: %s', type, rsp)
    else
        log:notice('Succeeded in invoking the %s method of persistence', type)
    end
end

function c_accycle:insert_accycle_object(object)
    table.insert(self.objs, object)
    log:notice_easy("insert ac_cycle object successfully, uptime: %s.", utils.uptime())
end

function c_accycle:do_system_cmd(cmd_buf)
    local w = worker.new(0)
    w:start([[
                local worker = ...
                local vos = require 'utils.vos'
                local log = require 'mc.logging'
                local cmd_buf = worker:recv()
                local ok = vos.system_s('/bin/sh', '-c', cmd_buf)
                if ok ~= 0 then
                    log:error('execute %s failed', cmd_buf)
                end
            ]])
    w:send(cmd_buf, true)
    while not w:is_complete() do
        skynet.sleep(100)
    end
    w:stop()
end

function c_accycle:ac_down()
    -- 避免反复AC场景下持久化数据库关闭造成的持久化失败
    if not self.objs or not next(self.objs) then
        log:info('ACcycle obj not exist, call AC_down failed')
        return
    end

    -- 限制线程次数，最大三次
    if acdown_retry_times >= MAX_RETRY_TIMES then
        log:notice('Too many threads are running, and ACCycle will not be executed.')
        return
    end

    skynet.fork_once(function()
        acdown_retry_times = acdown_retry_times + 1
        local ctx = context.get_context_or_default()
        ctx.fru_id = nil
        ctx.Timeout = '10'
        local reboot_object = {}
        context.with_context(ctx, function()
            reboot_object = get_reboot_object()
        end)
        ctx.Requestor = 'bmc.kepler.fructrl'
        -- 调关闭持久化
        persistence_api(reboot_object.Action, reboot_object, ctx, CLOSE_STR)
        log:notice_easy('refresh the cache, and then execute ac down')
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Five seconds later, execute ac down operation')
        log:operation(ctx:get_initiator(), 'fructrl', 'Set FRU0 to ACCycle successfully')
        log:notice_easy('AC register written value: %s' .. 'ACWp register written value: %s',
            self.objs[1].ACValue, self.objs[1].ACWpValue)
        self:do_system_cmd('sync') -- 将所有未写的系统缓冲区写到磁盘中，包含已修改的 i-node、已延迟的块 I/O 和读写映射文件
        self:do_system_cmd('echo 3 > /proc/sys/vm/drop_caches') -- 释放slab和页缓存
        skynet.sleep(500) -- 为保证刷盘内容的完整性，5s后再重复执行一次
        self:do_system_cmd('sync')
        self:do_system_cmd('echo 3 > /proc/sys/vm/drop_caches')

        local ok, err = pcall(function ()
            self.objs[1].ACWp = self.objs[1].ACWpValue
            self.objs[1].AC = self.objs[1].ACValue
        end)

        if not ok then
            log:error('execute ac down failed, err:%s', err)
            -- ac失败,恢复持久化服务
            persistence_api(reboot_object.Cancel, reboot_object, ctx, OPEN_STR)
        end
        acdown_retry_times = acdown_retry_times - 1
    end)
end

return singleton(c_accycle)