#!/usr/bin/env python3

import os, sys, json, re
import logging
import argparse
import fcntl
from rpc.client import print_json, JSONRPCException
import rpc
import ctblock
from ctblock import log

# global definitions
blk_cmd_lock = '/tmp/lava_blk_lock'
dbpath = '/usr/etc/vfiouser-disk/lava_blk_database'

# refer to blk_errno.h
errno_info = {
    1:  'Lava engine error',
    2:  'Memory not enough',
    3:  'Operation timeout',
    4:  'Invalid value',
    5:  'Illeagal disk name',
    6:  'Disk not exist',
    7:  'Disk status abnormal',
    8:  'Capacity error',
    9:  'Disk already exist',
    10: 'Disk name exist',
    11: 'Minimum unit size error',
    -19:'Disk not attach',
    -32602: 'Invalid parameters'
}


def cmd_response(status, add_info):
    ''' Print ctb command result
        Args:
            status: command result, 0 represent successful, 1 represent failed
            add_info: other output, decided by specified command
    '''
    result = {}
    result['status'] = status
    if status != 0:
        # try to get error code
        err_str = re.sub('.*response:\n', '', add_info, flags=re.DOTALL)
        try:
            err_code = json.loads(err_str)['code']
        except json.decoder.JSONDecodeError as err:
            err_code = None
        if err_code:
            if err_code in errno_info.keys():
                result['add_info'] = errno_info[err_code]
            else:
                result['add_info'] = json.loads(err_str)['message']
        else:
            result['add_info'] = err_str
    else:
        result['add_info'] = add_info
    print_json(result)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Lava Engine Block Strorage Client Commands', usage='%(prog)s [options]')
    parser.add_argument('-s', dest='server_socket', help='RPC domain socket path', default='/var/tmp/spdk.sock')
    parser.add_argument('-t', dest='timeout',
                        help='Timeout as a floating point number expressed in seconds waiting for response. Default: 60.0',
                        default=60.0, type=float)
    parser.add_argument('-r', dest='conn_retries',
                        help='Retry connecting to the RPC server N times with 0.2s interval. Default: 0',
                        default=0, type=int)
    parser.add_argument('-v', dest='verbose', action='store_const', const="INFO",
                        help='Set verbose mode to INFO', default="ERROR")
    subparser = parser.add_subparsers(dest='feature', metavar='')
    # manipulate disk
    diskparser = subparser.add_parser('disk', help='vfiouser disk command')
    disksubparser = diskparser.add_subparsers(title='Available Subcommand', dest='disk_cmd', metavar='')
    # manipulate host
    hostparser = subparser.add_parser('host', help='host command')
    hostsubparser = hostparser.add_subparsers(title='Available Subcommand', dest='host_cmd', metavar='')
    # manipulate diskgroup
    diskgroupparser = subparser.add_parser('diskgroup', help='diskgroup command')
    diskgroupsubparser = diskgroupparser.add_subparsers(title='Available Subcommand', dest='diskgroup_cmd', metavar='')
    # manipulate system command
    sysparser = subparser.add_parser('system', help='system command')
    syssubparser = sysparser.add_subparsers(title='Available Subcommand', dest='system_cmd', metavar='')

    # app
    def app_start(args):
        ctblock.app.app_start(args.db_handler, args.server_socket, args.timeout,
                              args.verbose, args.conn_retries)

    p = syssubparser.add_parser('start', help='Start vfiouser-disk service')
    p.set_defaults(func=app_start)

    def app_stop(args):
        ctblock.app.app_stop(args.server_socket, args.timeout,
                             args.verbose, args.conn_retries)

    p = syssubparser.add_parser('stop', help='Stop vfiouser-disk service')
    p.set_defaults(func=app_stop)

    def app_dump_db(args):
        info = ctblock.app.app_dump_db(args.db_handler)
        return info

    p = syssubparser.add_parser('db_dump', help='Dump all vfiouser disk info from database')
    p.set_defaults(func=app_dump_db)

    def app_del_diskdb(args):
        ctblock.app.app_del_diskdb(args.db_handler, args.bdev_name)

    p = syssubparser.add_parser('db_del_disk', help='Delete single disk info from database')
    p.add_argument('bdev_name', help='lava disk name')
    p.set_defaults(func=app_del_diskdb)

    def app_del_diskgroupdb(args):
        ctblock.app.app_del_diskgroupdb(args.db_handler, args.vmuuid)

    p = syssubparser.add_parser('db_del_dg', help='Delete single disk group info from database')
    p.add_argument('vmuuid', help='disk group vmuuid')
    p.set_defaults(func=app_del_diskgroupdb)

    def app_stripe_cal(args):
        info = ctblock.app.app_stripe_cal(args.offset, args.datalen, args.sector_size,
                                          args.stripe_space, args.stripe_size, args.stripe_num)
        return info

    p = syssubparser.add_parser('stripe_cal', help='calculate stripe')
    p.add_argument('offset', help='disk offset, Byte', type=int)
    p.add_argument('datalen', help='disk datalen, Byte', type=int)
    p.add_argument('sector_size', help='disk sector_size, Byte', type=int)
    p.add_argument('stripe_space', help='space for stripe, Byte', type=int)
    p.add_argument('stripe_size', help='stripe unit size, Byte', type=int)
    p.add_argument('stripe_num', help='stripe number, Byte', type=int)
    p.set_defaults(func=app_stripe_cal)

    def clear_db(args):
        ctblock.app.app_clear_db(args.db_handler)

    p = syssubparser.add_parser('clear_db', help='Delete all disk and disk group info from database and nvmf')
    p.set_defaults(func=clear_db)

    def clear_vmuuid(args):
        ctblock.app.app_clear_vmuuid(args.db_handler, args.vmuuid)

    p = syssubparser.add_parser('clear', help='Delete all disk and disk group info from database and nvmf')
    p.add_argument('vmuuid', help='disk group vmuuid')
    p.set_defaults(func=clear_vmuuid)

    # disk_util
    def disk_attach_bdev(args):
        info = ctblock.disk_util.disk_attach_bdev(args.client, args.db_handler,
                                                  args.bdev_name,
                                                  vmuuid=args.vmuuid,
                                                  sn=args.sn,
                                                  blocktype=args.blocktype)
        return info

    p = disksubparser.add_parser('attach', help='Attach lava bdev, default 512 sector')
    p.add_argument('bdev_name', help='lava disk name')
    p.add_argument('--sn', help='disk serial number', required=True)
    p.add_argument('--vmuuid', help='virtual machine uuid', required=True)
    p.add_argument('--blocktype', help='0 is sysdisk, 1 is datadisk', required=False, default=1, type=int)
    p.set_defaults(func=disk_attach_bdev)

    def disk_refresh_bdev(args):
        ctblock.disk_util.disk_refresh_bdev(args.client,
                                            bdev_name=args.bdev_name)

    p = disksubparser.add_parser('refresh', help='Refresh lava bdev')
    p.add_argument('bdev_name', help='lava bdev name')
    p.set_defaults(func=disk_refresh_bdev)

    def disk_detach_bdev(args):
        ctblock.disk_util.disk_detach_bdev(args.client, args.db_handler,
                                           bdev_name=args.bdev_name,
                                           vmuuid=args.vmuuid)

    p = disksubparser.add_parser('detach', help='Detach lava bdev')
    p.add_argument('bdev_name', help='lava disk name')
    p.add_argument('--vmuuid', help='virtual machine uuid', required=True)
    p.set_defaults(func=disk_detach_bdev)

    # diskgroup
    # 该接口提供给虚拟化调用，作用和set_qos相同，可能会重复调用，仅在diskgroup不存在时创建
    def diskgroup_set_qos(args):
        ctblock.disk_group.diskgroup_set_qos(args.client, args.db_handler,
                                             args.vmuuid, args.iops, args.bw)

    p = diskgroupsubparser.add_parser('create', help='Create diskgroup')
    p.add_argument('vmuuid', help='diskgroup uuid')
    p.add_argument('--iops', help='set diskgroup io per second', required=False, default=0, type=int)
    p.add_argument('--bw', help='set diskgroup bw per second(Gbit)', required=False, default=0, type=float)
    p.set_defaults(func=diskgroup_set_qos)

    p = diskgroupsubparser.add_parser('set_qos', help='set diskgroup Qos')
    p.add_argument('vmuuid', help='diskgroup uuid')
    p.add_argument('--iops', help='set diskgroup io per second', required=False, default=0, type=int)
    p.add_argument('--bw', help='set diskgroup bw per second(Gbit)', required=False, default=0, type=float)
    p.set_defaults(func=diskgroup_set_qos)

    def diskgroup_delete(args):
        ctblock.disk_group.diskgroup_delete(args.client, args.db_handler,
                                            args.vmuuid)

    p = diskgroupsubparser.add_parser('delete', help='Delete diskgroup')
    p.add_argument('vmuuid', help='diskgroup uuid')
    p.set_defaults(func=diskgroup_delete)

    def diskgroup_get_qos(args):
        return ctblock.disk_group.diskgroup_get_qos(args.client, args.vmuuid)

    p = diskgroupsubparser.add_parser('get_qos', help='get diskgroup Qos')
    p.add_argument('vmuuid', help='diskgroup uuid')
    p.set_defaults(func=diskgroup_get_qos)

    # parse arguments
    args = parser.parse_args()
    args.db_handler = ctblock.db.DBHandler(dbpath)

    # if user didn't input function name, print help info
    if sys.stdin.isatty() and not hasattr(args, 'func'):
        if args.feature:
            if args.feature == 'disk':
                diskparser.print_help()
            elif args.feature == 'host':
                hostparser.print_help()
            elif args.feature == 'system':
                sysparser.print_help()
        else:
            # No arguments and no data piped through stdin
            parser.print_help()
        exit(1)

    # make a json-rpc client
    if args.feature != 'system':
        args.client = ctblock.app.create_rpc_client(args.server_socket,
                                                    args.timeout,
                                                    log_level=args.verbose,
                                                    conn_retries=args.conn_retries)

    ctblock.log.log_init()
    # lock configure file to guarantee command would be executed serially
    with open(blk_cmd_lock, 'a') as f:
        # acquire lock
        fcntl.flock(f, fcntl.LOCK_EX)
        # call function
        try:
            output = args.func(args)
        except Exception as err:
            cmd_response(1, str(err))
            exit(1)
        if output:
            cmd_response(0, output)
        else:
            cmd_response(0, 'null')
        # release lock
        fcntl.flock(f, fcntl.LOCK_UN)

    exit(0)
