#!/usr/bin/python
# -*- coding: utf-8 -*-
# @brief   : 2018/10/10 16:56
# @File    : app ctrl
#  @Software: Software management infrastructure

import os
import re
import subprocess
import sys
import shutil
import platform
import functools
import logging as log

from grub import GrubBios, Grub2Bios, Grub2Uefi, \
    verify_dir_same, verify_file_same
from patch import PatchMgr
from custom_upg import parse_euler_upg_xml

MY_DIR = os.path.dirname(os.path.realpath(__file__))
MSWIN = (platform.system() == "Windows")
PATH_FIX = '.' if MSWIN else ''

DISK_ROOT = os.getenv('DISK_ROOT', '/startup_disk/image')
BOOT_LINK = os.path.join(DISK_ROOT, 'boot')
BOOT_OSP = os.path.join(DISK_ROOT, 'boot_osp')
BOOT_OSP_BAK = BOOT_OSP + '_bak'
OSP_BSP = PATH_FIX + '/OSM/script/proc_osp_bsp.info'
PATCH_CONF = os.path.join(BOOT_OSP, 'patch', 'patch.conf')
BOARD_TYPE = os.getenv('BOARD_TYPE', None)
SPARE_FLAG = os.getenv('SPARE_FLAG', None)
DATA_DIR = "/OSM/coffer_data"
SCRIPT_PATH = "/OSM/script"
DATA_NODE_FLAG = True if os.path.exists(
    '/startup_disk/conf/conf_local/data_node') else False
RAID_CFG = PATH_FIX + '/proc/mdstat'
# the data_node is in tmp dir when setup system
if os.path.exists('/tmp/conf_local/data_node'):
    DATA_NODE_FLAG = True
GRUB_PATH = "/startup_disk/image/boot/conf/grub.cfg"
RETURN_EXEC_SUCC = 0
RETURN_EXEC_FAILED = 1

RETURN_NO_NEED_UPGRADE = 1
RETURN_HOT_UPGRADE = 2
RETURN_COLD_UPGRADE = 3
PYTHON_2 = '2.7' in sys.version


def remount_image(run_func):
    """

    :param run_func:
    :return:
    """

    @functools.wraps(run_func)
    def wrapper(*args, **kwargs):
        if os.system('mount |grep %s >/dev/null' % DISK_ROOT):
            log.error("The disk (%s) is not mounted.", DISK_ROOT)
            return False
        remount = False
        if os.system(
                'mount |grep %s |grep rw|grep -v sync >/dev/null' % DISK_ROOT):
            remount = True
            if os.system('mount -o remount,rw,async %s' % DISK_ROOT):
                log.error('Remount disk(%s) for write failed.', DISK_ROOT)
                return False
        ret = run_func(*args, **kwargs)
        if remount:
            os.system('mount -o remount,ro,sync %s' % DISK_ROOT)
        return ret

    return wrapper


def decode_str(value):
    if not PYTHON_2 and isinstance(value, bytes):
        return value.decode(errors='ignore')
    return value


def call_system_cmd(shell_cmd):
    """
    执行shell命令，并返回回显信息
    """
    process = subprocess.Popen(shell_cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
    output, _ = process.communicate()
    ret_code = process.poll()
    output = decode_str(output)
    return ret_code, output


def keep_dim(run_func):
    """

    :param run_func:
    :return:
    """
    @functools.wraps(run_func)
    def wrapper(*args, **kwargs):
        dim_config = False
        res, _ = call_system_cmd("cat /startup_disk/image/boot/conf/grub.cfg | grep dim=on")
        if res == 0:
            log.info("DimMgr: DIM is open.")
            dim_config = True
        result = run_func(*args, **kwargs)
        if dim_config:
            _, line_num = call_system_cmd("cat {0} | grep -n os_boot_osp | grep bzImage |awk {1}"
                                          .format(GRUB_PATH, "{'print $1'}"))
            line_num = line_num.split(':')[0].strip()
            call_system_cmd("sed -i '{0} s/$/& dim=on/g' {1}".format(line_num, GRUB_PATH))
        return result
    return wrapper


def resume_ibc_ssh(process_dir):
    cur_dir = os.path.dirname(__file__)
    script_path = os.path.join(cur_dir, 'ibc_keys_resume.sh')
    if not os.path.exists(script_path):
        log.error('Script path(%s) is not exist.', script_path)
        return False
    cmd = 'sh {} {} {}'.format(script_path,'upgrade', process_dir)
    ret_code, _ = call_system_cmd(cmd)

    if ret_code:
        log.error("Resume ibc keys failed, cmd: %s.", cmd)
        return False
    log.info("Resume ibc keys success, cmd: %s.", cmd)
    return True


def backup_resume_ibc_ssh(run_func):
    """
    euler安装、升级前备份ibc ssh密钥
    euler安装、升级后恢复ibc ssh密钥
    :param run_func:
    :return:
    """
    @functools.wraps(run_func)
    def wrapper(*args, **kwargs):
        # 检查脚本是否存在
        resume_type = 'install' if run_func.__name__ == 'install' else 'upgrade'
        cur_dir = os.path.dirname(__file__)
        backup_script = os.path.join(cur_dir, 'ibc_keys_backup.sh')
        resume_script = os.path.join(cur_dir, 'ibc_keys_resume.sh')

        if not os.path.exists(backup_script) or \
                not os.path.exists(resume_script):
            log.error('Backup(%s) or Resume(%s) script is not exist.', backup_script, resume_script)
            return RETURN_EXEC_FAILED

        # 备份ibc ssh
        if os.system('sh %s' % backup_script):
            log.error("Backup ibc keys failed.")
            return RETURN_EXEC_FAILED

        # 执行euler升级、安装
        ret = run_func(*args, **kwargs)

        # 恢复ibc ssh
        if os.system('sh %s %s' % (resume_script, resume_type)):
            log.error("Resume ibc keys failed.")
            return RETURN_EXEC_FAILED
        return ret

    return wrapper


class Manifest(object):
    """
    Manifest config
    """

    def __init__(self, cfg_path):
        """

        :param cfg_path:
        """
        try:
            import yaml
            with open(cfg_path, 'r') as fp:
                self.cfg = yaml.safe_load(fp)
            self.version = self.cfg.get('Version', '')
        except Exception as e:
            self.cfg = None
            self.version = ''
            log.exception("Load manifest excepted, path(%s).", cfg_path)
        return


class MainConfig(object):
    """
    Main config
    """

    def __init__(self, cfg_path):
        """

        :param cfg_path:
        """
        try:
            import configparser as configparser
        except Exception:
            import ConfigParser as configparser
        with open(cfg_path, 'r') as fp:
            self.cfg = configparser.ConfigParser()
            self.cfg.readfp(fp)
        self.grub_board = self.cfg.get('Global', 'grub_board')
        self.log_path = self.cfg.get('Global', 'log_path')
        return


class EulerAPP(object):
    """
    App control
    """

    def __init__(self):
        self.root_dir = os.path.dirname(MY_DIR)
        self.repo_dir = os.path.join(self.root_dir, 'repo')
        self.manifest = os.path.join(self.root_dir, 'manifest.yml')
        self.main_cfg = MainConfig(
            os.path.join(self.root_dir, 'config', 'main.conf'))
        self.board_type = self.get_board_type()
        self.grub_type = 'grub' if self.board_type in self.main_cfg.grub_board else 'grub2'
        self.grub_dir = os.path.join(DISK_ROOT, self.grub_type)
        self.boot_cfg = 'menu.lst' if 'grub' == self.grub_type else 'grub.cfg'
        self.sys_disk_list = os.getenv('SYS_DISK_LIST', '').split(
            ',')  # 升级场景不安装grub
        self.mount_point = '/home/temp_mount_point'
        self.setup_type = self._get_setup_type()
        return

    def choose_grub_for_es(self):
        cmd = "cat /proc/osp/bsp | grep 'CPU Version : ES' >/dev/null"
        if os.system(cmd):
            os.system("rm -r {0}/conf/es".format(self.repo_dir))
            return
        if not os.path.exists(os.path.join(self.repo_dir, "conf/es")):
            return
        cmd = "mv -f {0}/conf/es/* {0}/conf/; rm -r {0}/conf/es".format(
            self.repo_dir)
        if os.system(cmd):
            log.error("Euler: exec cmd(%s) failed.", cmd)
        else:
            log.info("Euler: exec cmd(%s) successfully.", cmd)
        return

    @staticmethod
    def get_board_type():
        """
        Give priority to env value rather than value from file to support factory install.
        :return:
        """
        # if board_type value is data_node, it will lead failure to upgrade euler cmpt.
        if BOARD_TYPE and BOARD_TYPE != 'DATA_NODE':
            return BOARD_TYPE
        log.warning(
            "Euler: Cannot get board_type from environment or board_type(%s) is DATA_NODE.",
            BOARD_TYPE)
        cmd = "cat /OSM/script/proc_osp_bsp.info |grep PANGEA |awk '{print $4}'"
        return os.popen(cmd).read().rstrip('\r\n')

    def get_ori_grub(self, install_dir):
        """

        :param install_dir:
        :return:
        """
        default_cfg = os.path.join(install_dir, 'conf', self.boot_cfg)
        data_node_flag = '_DATANODE_' if (
                    DATA_NODE_FLAG and platform.machine() == 'x86_64') else '_'
        if SPARE_FLAG == 'True':
            board_cfg = os.path.join(install_dir, 'conf', 'grub_spare_part.cfg')
            log.info("Spare part scene, need use grub_spare_part.cfg.")
        else:
            board_cfg = default_cfg.replace('.', data_node_flag + self.board_type + '.')
        if os.path.isfile(board_cfg):
            return board_cfg
        log.warning("Failed to get grub file(%s), return default_cfg", board_cfg)
        return default_cfg

    def _install_grub(self, install_dir):
        """
        Install grub to install_dir
        :param install_dir:
        :return:
        """
        log.info("Begin to install grub(type: %s).", self.grub_type)
        if not self.sys_disk_list:
            log.error("Environment var(SYS_DISK_LIST) not set.")
            return False

        if os.path.isdir(self.grub_dir):
            shutil.rmtree(self.grub_dir)
        os.makedirs(self.grub_dir)

        # Install conf, rename grub_xxxxx.cfg to grub.cfg
        conf_dir = os.path.join(install_dir, 'conf')
        dst_cfg_path = os.path.join(conf_dir, os.path.basename(self.boot_cfg))
        src_grub_file = self.get_ori_grub(install_dir)
        if os.path.realpath(src_grub_file) != os.path.realpath(dst_cfg_path):
            shutil.copy(src_grub_file, dst_cfg_path)
        os.chmod(dst_cfg_path, 0o640)

        grub_cls = None
        if 'grub' == self.grub_type:
            grub_cls = GrubBios
        elif 'grub2' == self.grub_type:
            grub_cls = Grub2Uefi if os.path.isdir(
                '/sys/firmware/efi') else Grub2Bios

        grub = grub_cls(self.grub_dir, self.sys_disk_list, conf_dir)
        if not grub.install():
            log.error("Install grub failed.")
            return False
        return True

    def _install_pkg(self, install_dir):
        """
        Install euler package
        :param install_dir:
        :return:
        """
        if os.path.isdir(install_dir):
            shutil.rmtree(install_dir)
        shutil.copytree(self.repo_dir, install_dir, symlinks=True)
        shutil.rmtree(os.path.join(install_dir, "upd_script"),
                      ignore_errors=True)
        shutil.rmtree(os.path.join(install_dir, "recover_file"),
                      ignore_errors=True)
        shutil.copy(os.path.join(self.root_dir, 'manifest.yml'), install_dir)
        os.chmod(install_dir, 0o755)
        log.info("Install package to dir(%s) successfully.", install_dir)
        return True

    def _install_rescue(self, install_dir):
        """
        Install rescue
        :return:
        """
        log.info("Begin to install boot rescue.")
        rescue_dir = os.path.join(DISK_ROOT, "boot_rescue")
        if os.path.isdir(rescue_dir):
            shutil.rmtree(rescue_dir)
        os.makedirs(rescue_dir)

        # 此处写死链接路径，避免osp_bak 和 osp切换时再次修改
        os.symlink('../boot_osp/initrd', os.path.join(rescue_dir, 'initrd'))
        os.symlink('../boot_osp/bzImage', os.path.join(rescue_dir, 'bzImage'))

        # Install conf
        shutil.copytree(os.path.join(install_dir, 'conf'),
                        os.path.join(rescue_dir, 'conf'))
        src_grub_file = self.get_ori_grub(install_dir)
        with open(src_grub_file, 'r') as f:
            content = f.read()
        rescue_content = content.replace('os_boot_osp', 'os_rescue').replace(
            'os_only', 'os_rescue')
        rescue_cfg_path = os.path.join(rescue_dir, 'conf',
                                       os.path.basename(self.boot_cfg))
        with open(rescue_cfg_path, 'w+') as f:
            f.write(rescue_content)
        os.chmod(rescue_cfg_path, 0o640)

        # Create grub link
        os.symlink('../%s' % self.grub_type,
                   os.path.join(rescue_dir, self.grub_type))
        log.info("Install boot rescue successfully.")
        return True

    def _install_boot(self, install_dir):
        """

        :return:
        """
        log.info("Begin to install boot to dir(%s).", install_dir)
        # Create boot dir
        if os.path.islink(BOOT_LINK):
            os.unlink(BOOT_LINK)
        os.symlink(os.path.basename(install_dir), BOOT_LINK)
        os.system('fsync {}'.format(BOOT_LINK))
        # Create grub link
        os.symlink('../%s' % self.grub_type,
                   os.path.join(install_dir, self.grub_type))
        log.info("Install boot osp successfully.")
        return True

    def _upgrade_rescue_grub(self, install_dir):
        try:
            rescue_conf_dir = os.path.join(DISK_ROOT, "boot_rescue", 'conf')
            if os.path.isdir(rescue_conf_dir):
                shutil.rmtree(rescue_conf_dir)
            shutil.copytree(os.path.join(install_dir, 'conf'), rescue_conf_dir)
            src_grub_file = self.get_ori_grub(install_dir)
            with open(src_grub_file, 'r') as f:
                content = f.read()
            rescue_content = content.replace('os_boot_osp', 'os_rescue').replace('os_only', 'os_rescue')
            rescue_cfg_path = os.path.join(rescue_conf_dir, os.path.basename(self.boot_cfg))
            with open(rescue_cfg_path, 'w+') as f:
                f.write(rescue_content)
            os.chmod(rescue_cfg_path, 0o640)
            log.info("Upgrade rescue grub file success, src(%s).", src_grub_file)
            return True
        except Exception:
            log.exception("Upgrade rescue grub fail.")
            return False

    def _upgrade_grub(self, install_dir):
        """

        :param install_dir:
        :return:
        """
        src_grub_file = self.get_ori_grub(install_dir)
        dst_grub = os.path.join(install_dir, 'conf', self.boot_cfg)
        log.info("src_grub_file is %s, dst_grub is %s.", src_grub_file,
                 dst_grub)
        if os.path.realpath(src_grub_file) != os.path.realpath(dst_grub):
            shutil.copy(src_grub_file, dst_grub)
            os.system('fsync {}'.format(dst_grub))
        log.info("Upgrade grub successfully.")
        return

    def _swap_boot_dir(self, src, dst):
        """
        Swap boot dir to dst dir
        :param src:
        :param dst:
        :return:
        """
        # Copy pxe config first
        os.system('cp -arf %s/*pxe* %s' % (dst, src))
        os.system('cp -arf %s/grub.cfg %s &>/dev/null' % (dst, src))
        # Create grub link
        os.symlink('../%s' % self.grub_type, os.path.join(src, self.grub_type))

        shutil.rmtree(dst, ignore_errors=True)
        os.rename(src, dst)
        os.system('fsync {}'.format(dst))
        return

    @staticmethod
    def get_cmpt_ver(file_path):
        cmd = "grep \"Version:\" {0} | cut -d' ' -f3".format(file_path)
        version = os.popen(cmd).read().strip()
        return version

    def _backup_old_file(self, euler_file_list):
        """
        back up old euler file for rollback upgrade.
        :param euler_file_list:
        :return:
        """
        backup_dir = os.path.join(DATA_DIR, "euler_back_up")
        if not os.path.exists(backup_dir):
            os.mkdir(backup_dir)
        bak_ver_path = os.path.join(backup_dir, "manifest.yml")
        cur_version = self.get_cmpt_ver(os.path.join(DATA_DIR,
                                                     'update/pkg_upgrade_old_extract/euler-*/manifest.yml'))
        last_bak_verion = self.get_cmpt_ver(bak_ver_path)
        if last_bak_verion == cur_version:
            log.info("Back up euler version is same as cur version(%s).",
                     cur_version)
            return
        log.info("Back up upd file, last bak ver(%s), cur bak ver(%s).",
                 last_bak_verion, cur_version)
        shutil.rmtree(backup_dir, ignore_errors=True)
        for per_file in euler_file_list:
            if not os.path.exists(per_file.path):
                continue
            dst_path = backup_dir + os.path.dirname(per_file.path)
            if not os.path.isdir(dst_path):
                os.system("mkdir -p {0}".format(dst_path))
            shutil.copy(per_file.path, backup_dir + per_file.path)
        src_ver_path = os.path.join(DATA_DIR,
                                    'update/pkg_upgrade_old_extract/euler-*/manifest.yml')
        os.system("cp {0} {1}".format(src_ver_path, bak_ver_path))
        return

    def _fast_upg_euler_file(self, run_type):
        """
        upgrade euler script.
        1. replace old script
        2. exec script to make file work
        :param command:
        :return:
        """
        log.info("Upg handle run type(%s)", run_type)
        ret, euler_file_list = parse_euler_upg_xml(
            os.path.join(self.root_dir, "action/upd_info.xml"))
        if not ret:
            return False
        if run_type == 'upgrade':
            self._backup_old_file(euler_file_list)
        for per_file in euler_file_list:
            if run_type == 'upgrade' or run_type == 'rollback':
                src_path = os.path.join(self.root_dir,
                                        'repo/recover_file' + per_file.path)
            else:
                src_path = os.path.join(DATA_DIR,
                                        "euler_back_up" + per_file.path)
            log.info("Copy src(%s) to dst(%s).", src_path, per_file.path)
            if not os.path.exists(os.path.dirname(per_file.path)):
                os.system("mkdir -p {0}".format(os.path.dirname(per_file.path)))
            if os.path.exists(src_path):
                shutil.copy(src_path, per_file.path)
            if per_file.script == '':
                continue
            script = os.path.join(self.root_dir,
                                  'repo/upd_script/' + per_file.script)
            cmd = "sh {0} {1}".format(script,
                                      'upgrade' if run_type == 'upgrade' else 'rollback')
            ret = os.system(cmd)
            if ret:
                log.error("Exec %s failed.", cmd)
                return False
            else:
                log.info("Exec %s success.", cmd)
        return True

    @keep_dim
    def _fast_upgrade(self, install_dir=BOOT_OSP_BAK, run_type='upgrade'):
        """

        :param install_dir:
        :return:
        """
        if not self._install_pkg(install_dir):
            log.error("Install new pkg to bak dir failed.")
            shutil.rmtree(install_dir, ignore_errors=True)
            return False

        if os.path.exists('/OSM/log/cur_debug/audit.log'):
            os.chmod("/OSM/log/cur_debug/audit.log", 0o640)
        if os.path.exists('/OSM/coffer_log/log/cur_debug/audit.log'):
            os.chmod("/OSM/coffer_log/log/cur_debug/audit.log", 0o640)
            
        if not PatchMgr.upgrade(PATCH_CONF, os.path.join(install_dir, 'patch',
                                                         'patch.conf')):
            log.error("Upgrade patch failed.")
            shutil.rmtree(install_dir, ignore_errors=True)
            return False

        self._upgrade_grub(install_dir)

        if self.check_if_upg(run_type) and not self._fast_upg_euler_file(
                run_type):
            return False
        ret = os.system("(find {0} -type f|xargs -l -r fsync) && (find {0} -type l|xargs -l -r fsync)"
                        .format(DISK_ROOT))
        log.info("Cmd result %s.", ret)
        self._swap_boot_dir(install_dir, BOOT_OSP)
        if not self.copy_boot_file():
            log.error("Copy secure file failed.")
            return False
        log.info("Run fast upgrade successfully.")
        return True

    @staticmethod
    def _prepare_install_dir():
        """

        :return:
        """
        if os.path.realpath(BOOT_LINK) != BOOT_OSP_BAK:
            return
        shutil.rmtree(BOOT_OSP, ignore_errors=True)
        shutil.move(BOOT_OSP_BAK, BOOT_OSP)
        if os.path.islink(BOOT_LINK):
            os.unlink(BOOT_LINK)
        os.symlink(os.path.basename(BOOT_OSP), BOOT_LINK)
        return

    @property
    def partition_conf(self):
        """

        :return:
        """
        attr_name = '_part_conf'
        if not hasattr(self, attr_name):
            cfg = os.path.join(BOOT_LINK, 'conf/partition_%s.conf' %
                               self.setup_type)
            cfg_board = os.path.join(BOOT_LINK, 'conf/partition_%s.conf' %
                                     self.board_type)
            if os.path.exists(cfg):
                setattr(self, attr_name, cfg)
            else:
                setattr(self, attr_name, cfg_board)
        return getattr(self, attr_name)

    @staticmethod
    def _get_dev_in_raid(dev_list):
        sys_list = []
        for dev in dev_list:
            check_sys_disk_cmd = "cat %s | grep %s" % (RAID_CFG,
                                                       dev.split('/')[2])
            ret = os.popen(check_sys_disk_cmd).read().strip()
            if ret is "":
                log.warning("Check sys disk failed: %s is not exist, "
                            "or it is not sys disk.", dev)
            else:
                sys_list.append(dev)
        return sys_list

    @property
    def system_disk_list(self):
        """

        :return:
        """
        with open(self.partition_conf, "r") as f:
            content = f.read()
            pat = r"clearpart.*drivers=(\w+),?(\w+)?"
            ret = re.search(pat, content)
            if not ret:
                log.error(
                    "The disk partition config has no 'clearpart' section.")
                return []
            dev_list = [disk for disk in ret.groups() if disk is not None]
            dev_list = [('/dev/' + disk) for disk in dev_list if
                        os.path.exists('/dev/' + disk)]
            sys_list = self._get_dev_in_raid(dev_list)
            if not sys_list:
                log.warning("Check raid info of sys disk failed, "
                            "set default sys disk list.")
                return dev_list
            return sys_list

    def copy_file_to_part(self, partition):
        cmd = 'df -l|grep -w "{0}" |grep -w "{1}"'.format(partition, self.mount_point)
        if not os.system(cmd):
            log.warning('{0} has been mounted. Try to umount.'.format(partition))
            os.system('umount -l {0}'.format(partition))

        cmd = "mkdir -p {1} && mount {0} {1}".format(
            partition, self.mount_point)
        if os.system(cmd):
            log.warning("Exec cmd(%s) failed.", cmd)
            return False
        source_path = os.path.join(BOOT_LINK, "EFI/BOOT")
        target_path = os.path.join(self.mount_point, 'EFI')
        grub_path = os.path.join(target_path, "grub2", "grub.cfg")
        boot_path = os.path.join(target_path, "BOOT", "grub.cfg")
        cmd = "cp -r {0} {1}".format(source_path, target_path)
        ret = os.system(cmd)
        if ret or not verify_dir_same(source_path,
                                      os.path.join(target_path, 'BOOT')):
            log.error("Exec cmd(%s) failed, ret(%s).", cmd, ret)
            return False

        cmd = "cp {0} {1} && fsync {1}".format(grub_path, boot_path)
        ret = os.system(cmd)

        if ret or not verify_file_same(grub_path, boot_path):
            log.error("Exec cmd(%s) failed, ret(%s).", cmd, ret)
            return False

        os.system("umount -l {0} && rm -d {1}".format(
            partition, self.mount_point))
        log.info("Install grub success.")
        return True

    def copy_boot_file(self):
        if platform.machine() != 'aarch64':
            return True
        pxe_script = os.path.join(SCRIPT_PATH, 'pxe_server.sh')
        os.system(pxe_script)
        os.system('mount -o remount,rw,async /startup_disk/image')
        disk_list = self.system_disk_list
        copy_success_cnt = 0
        for dev in disk_list:
            log.info("Copy file to %s.", dev + '1')
            if not self.copy_file_to_part(dev + '1'):  # 1 is hidden partition
                self._clear_fail_dev(dev + '1')
                continue
            copy_success_cnt += 1
        if disk_list and not copy_success_cnt:
            return False
        return True

    def _clear_fail_dev(self, dev):
        ret = os.system('umount -l {0} && rm -rf {1}'.format(dev, self.mount_point))
        log.info("Clear fail dev(%s), ret(%s).", dev, ret)
        return

    @keep_dim
    def _roll_upgrade(self, install_dir=BOOT_OSP_BAK):
        """

        :param install_dir:
        :return:
        """
        self._prepare_install_dir()

        if not self._install_pkg(install_dir):
            log.error("Install new pkg to sys disk failed.")
            shutil.rmtree(install_dir, ignore_errors=True)
            return False

        self._upgrade_grub(install_dir)

        self._upgrade_rescue_grub(install_dir)

        ret = os.system("(find {0} -type f|xargs -l -r fsync) && (find {0} -type l|xargs -l -r fsync)"
                        .format(DISK_ROOT))
        log.info("Cmd result %s.", ret)
        if not resume_ibc_ssh(process_dir=BOOT_OSP_BAK):
            return False

        if not self._install_boot(install_dir):
            log.error("Roll upgrade boot failed.")
            shutil.rmtree(install_dir, ignore_errors=True)
            return False

        if not self.copy_boot_file():
            log.error("Copy secure file failed.")
            return False
        log.info("Roll upgrade successfully.")

        # 非快速升级中，调用eam脚本修改高低可靠内存值
        if DATA_NODE_FLAG:
            return True

        eam_update_mh_file = "/OSM/script/eam_hsp_update_mh_in_upgrade.py"
        product_ini_dir = os.path.join(self.repo_dir, 'conf', 'product_ini')
        if os.path.exists("/OSM/coffer_data/swm/upgrade_flag") and os.path.exists(eam_update_mh_file) \
                and os.path.isdir(product_ini_dir):
            cmd = ' '.join([eam_update_mh_file, '-p', product_ini_dir])
            ret, _ = call_system_cmd(cmd)
            if ret:
                log.error("Exec cmd(%s) to update more high reliable memory failed, ret(%s).", cmd, ret)
                return False
            log.info("Exec cmd(%s) to update more high reliable memory success.", cmd)

        return True

    @backup_resume_ibc_ssh
    def install(self, install_dir=BOOT_OSP):
        """

        :return:
        """
        if not self._install_pkg(install_dir):
            log.error("Install pkg to sys disk failed.")
            return RETURN_EXEC_FAILED

        if not self._install_rescue(install_dir):
            log.error("Install resume system failed.")
            return RETURN_EXEC_FAILED

        if not self._install_grub(install_dir):
            log.error("Install grub failed.")
            return RETURN_EXEC_FAILED

        if not self._install_boot(install_dir):
            log.error("Install boot failed.")
            return RETURN_EXEC_FAILED

        log.info("Install successfully.")
        return RETURN_EXEC_SUCC

    @staticmethod
    def start():
        """

        :return:
        """
        log.info("Start euler component successfully.")
        return RETURN_EXEC_SUCC

    def check_upgrade(self):
        """
        Euler component version format is X.Y.Z.P
        If X/Y/Z was changed, euler component can be only cold upgrade
        If only P was changed, euler component can be hot upgrade
        If it is same, no need upgrade
        :return: 0 -- Can not upgrade; 1 -- No need upgrade; 2 -- Hot upgrade; 3 -- Cold upgrade
        """
        pkg_ver = Manifest(self.manifest).version
        run_ver = Manifest(os.path.join(BOOT_LINK, 'manifest.yml')).version

        if not run_ver or not pkg_ver:
            log.warning(
                "Get pkg and running version failed(pkg: %s, run: %s), just return cold upgrade.",
                pkg_ver, run_ver)
            return RETURN_COLD_UPGRADE
        log.info("The running version is %s, and pkg version is %s.", run_ver,
                 pkg_ver)
        if run_ver == pkg_ver:
            return RETURN_NO_NEED_UPGRADE
        if run_ver.split('.')[:-1] == pkg_ver.split('.')[:-1]:
            return RETURN_HOT_UPGRADE
        return RETURN_COLD_UPGRADE

    def pre_upgrade(self):
        """

        :return:
        """
        if self.check_if_upg('pre_upgrade') and not self._fast_upg_euler_file(
                "pre_upgrade"):
            return RETURN_EXEC_FAILED
        log.info("Prepare for upgrade successfully.")
        return RETURN_EXEC_SUCC

    @remount_image
    @backup_resume_ibc_ssh
    def upgrade(self):
        """

        :return:
        """
        upg_type = os.getenv('UPG_TYPE', 'fast')
        if 'fast' == upg_type:
            return RETURN_EXEC_SUCC if self._fast_upgrade(
                run_type='upgrade') else RETURN_EXEC_FAILED
        return RETURN_EXEC_SUCC if self._roll_upgrade() else RETURN_EXEC_FAILED

    @staticmethod
    def check_status():
        """

        :return:
        """
        log.info("Check status successfully.")
        return RETURN_EXEC_SUCC

    @staticmethod
    def online():
        log.info("Online successfully.")
        return RETURN_EXEC_SUCC

    def pre_rollback(self):
        """

        :return:
        """
        if self.check_if_upg('pre_rollback') and not self._fast_upg_euler_file(
                "pre_rollback"):
            log.info("Prepare for rollback failed.")
            return RETURN_EXEC_FAILED
        log.info("Prepare for rollback successfully.")
        return RETURN_EXEC_SUCC

    def check_if_upg(self, run_type):
        if platform.machine() != 'aarch64':
            return False
        src_ver = self.get_cmpt_ver(os.path.join(DATA_DIR,
                                                 'update/pkg_upgrade_old_extract/euler-*/manifest.yml'))
        dst_ver = self.get_cmpt_ver(os.path.join(DATA_DIR,
                                                 'update/pkg_upgrade_extract/euler-*/manifest.yml'))
        ret = False
        if run_type == 'pre_upgrade' or run_type == 'pre_rollback':
            if src_ver > dst_ver:
                ret = True
        elif run_type == 'upgrade' or run_type == 'rollback':
            if dst_ver > src_ver:
                ret = True
        else:
            log.error("Unknown run type(%s)", run_type)
            ret = False
        log.info(
            "upg_chk: run type(%s), src_ver(%s), dst ver(%s), check ret(%s).",
            run_type, src_ver, dst_ver, ret)
        return ret

    @remount_image
    @backup_resume_ibc_ssh
    def rollback(self):
        """

        :return:
        """
        upg_type = os.getenv('UPG_TYPE', 'fast')
        if 'fast' == upg_type:
            return RETURN_EXEC_SUCC if self._fast_upgrade(
                run_type='rollback') else RETURN_EXEC_FAILED
        return RETURN_EXEC_SUCC if self._roll_upgrade() else RETURN_EXEC_FAILED

    def _get_setup_type(self):
        if not os.path.exists(OSP_BSP) and self.board_type:
            return self.board_type

        with open(OSP_BSP, "rb") as f:
            content = f.read()
            if PYTHON_2:
                ret = re.search(r'(Sys disk type|Disk State) is (\w+)',
                                content)
            else:
                ret = re.search(b'(Sys disk type|Disk State) is (\w+)',
                                content)
            if not ret:
                log.info("Get disk state failed, set setup type(%s).",
                         self.board_type)
                return self.board_type
            if PYTHON_2:
                disk_state = ret.groups()[1]
            else:
                disk_state = ret.groups()[1].decode()
            return self.board_type + '_' + disk_state


_USAGE = '''
Usage: {0} option
  Management euler component.
  Options are:
  install         Install the euler component
  start           Start the euler component, actually do nothing
  check_upgrade   Check the euler component is need upgrade
  pre_upgrade     Prepare for upgrade
  upgrade         Upgrade the euler component
  check_status    Check the euler component running status
  online          Online the euler component
  pre_rollback    Prepare for rollback
  rollback        Rollback the euler component
'''.format(os.path.basename(__file__))


def prepare_log(log_path):
    """

    :param log_path:
    :return:
    """
    fmt = log.Formatter(
        fmt='[%(asctime)s][%(levelname)s][CO_Euler: %(message)s][%(filename)s, %(lineno)d]',
        datefmt='%Y-%m-%d %H:%M:%S')
    hdlr = log.StreamHandler()
    hdlr.setFormatter(fmt)
    log.root.setLevel(log.INFO)
    log.root.addHandler(hdlr)

    if not log_path:
        return
    log_dir = os.path.dirname(log_path)
    if not os.path.isdir(log_dir):
        os.makedirs(log_dir, 0o750)
    hdlr = log.FileHandler(log_path)
    hdlr.setFormatter(fmt)
    log.root.addHandler(hdlr)
    os.chmod(log_path, 0o640)
    return


def exec_cmd():
    """

    :return:
    """
    args = sys.argv
    if len(args) != 2 or args[1] == '-h':
        print(_USAGE)
        return 0
    app = EulerAPP()
    prepare_log(app.main_cfg.log_path)
    app.choose_grub_for_es()
    if not hasattr(app, args[1]):
        log.error("Euler app not support command(%s).", args[1])
        return 1
    try:
        return getattr(app, args[1])()
    except:
        log.exception("Exec cmd(%s) failed.", args[1])
        return 2


if __name__ == '__main__':
    sys.exit(exec_cmd())

