#!/usr/bin/env python
# -*- coding: utf-8 -*-

from optparse import OptionParser
import sys
import os
from uuid import uuid4

from opennode.cli.actions.utils import execute
from opennode.cli.actions.quota import rename_quota_file, copy_quota_file
from opennode.cli.actions.openvz_utils import (get_openvz_running_ctids,
                                               get_openvz_stopped_ctids)

valid_extensions = ['.mount', '.umount', '.premount', '.postumount',
                    '.start', '.stop', '.conf']


def move_running_vm(ct):
    # XXX: VE_PRIVATE and VE_ROOT must be present in ctid.conf
    # as vzctl & co utilities rely on them.
    ct['private'] = os.path.dirname(execute(
        'source /etc/vz/conf/%(ctid)s.conf && echo $VE_PRIVATE' % ct))
    ct['root'] = os.path.dirname(execute(
        'source /etc/vz/conf/%(ctid)s.conf && echo $VE_ROOT' % ct))
    if 'dest' in ct and not ct['dest'] == ct['private']:
        print ("Can't change private path on running container %(ctid)s, "
               "skipping.") % ct
        return
    ex = execute('vzctl chkpnt %(ctid)s --dumpfile '
                 '/tmp/Dump.%(ctid)s' % ct)
    print ex
    # XXX: We can't call rename with only one file extension inside {}
    # So we list all files belonging to ctid, filter them with known list
    # and put result to comma delimited string inside ct structure.
    ls_conf = execute('ls /etc/vz/conf/%(ctid)s.*' % ct)
    extensions = []
    for path in ls_conf.split('\n'):
        ext = os.path.splitext(path)[1]
        if ext in valid_extensions:
            extensions.append(ext)
    ct['extensions'] = ','.join(extensions)
    if ',' in ct['extensions']:
        execute('rename %(ctid)s %(new_ctid)s /etc/vz/conf/%(ctid)s'
                '{%(extensions)s}' % ct)
    else:
        execute('rename %(ctid)s %(new_ctid)s /etc/vz/conf/%(ctid)s'
                '%(extensions)s' % ct)
    execute('mv %(private)s/%(ctid)s %(private)s/%(new_ctid)s' % ct)
    execute('mv %(root)s/%(ctid)s %(root)s/%(new_ctid)s' % ct)
    ex = execute('vzctl restore %(new_ctid)s --dumpfile '
                 '/tmp/Dump.%(ctid)s' % ct)
    print ex
    # XXX: vzctl restore manages new quota file, remove old one by hand.
    execute('rm /var/vzquota/quota.%(ctid)s' % ct)


def copy_stopped(ct):
    ct['private'] = os.path.dirname(execute(
        'source /etc/vz/conf/%(ctid)s.conf && echo $VE_PRIVATE' % ct))
    ct['root'] = os.path.dirname(execute(
        'source /etc/vz/conf/%(ctid)s.conf && echo $VE_ROOT' % ct))
    if not 'dest' in ct or not ct['dest']:
        ct['dest'] = ct['private']
    ls_conf = execute('ls /etc/vz/conf/%(ctid)s.*' % ct)
    extensions = []
    for path in ls_conf.split('\n'):
        ext = os.path.splitext(path)[1]
        if ext in valid_extensions:
            extensions.append(ext)
    # XXX: There is no copy utility like rename(1) so
    # we have to call cp in a loop
    for ext in extensions:
        execute('cp /etc/vz/conf/%s%s /etc/vz/conf/%s%s' %
                (ct['ctid'], ext, ct['new_ctid'], ext))
    # XXX: libvirt does not like different VMs with same uuid.
    execute("sed -i -e's/#UUID: .*/#UUID: %s/' /etc/vz/conf/%s.conf" %
            (str(uuid4()), ct['new_ctid']))
    execute('mkdir -p %(dest)s/%(new_ctid)s' % ct)
    execute('rsync -av --numeric-ids %(private)s/%(ctid)s/ '
            '%(dest)s/%(new_ctid)s/' % ct)
    execute('mkdir -p %(root)s/%(new_ctid)s' % ct)
    execute('rsync -av --numeric-ids %(root)s/%(ctid)s/ '
            '%(root)s/%(new_ctid)s/' % ct)
    copy_quota_file(ct['ctid'], ct['new_ctid'])
    ex = execute(("vzctl set %(new_ctid)s "
                  "--private '%(dest)s/$VEID' --save") % ct)
    print ex


def move_stopped(ct):
    ct['private'] = os.path.dirname(execute(
        'source /etc/vz/conf/%(ctid)s.conf && echo $VE_PRIVATE' % ct))
    ct['root'] = os.path.dirname(execute(
        'source /etc/vz/conf/%(ctid)s.conf && echo $VE_ROOT' % ct))
    if not 'dest' in ct or not ct['dest']:
        ct['dest'] = ct['private']
    ls_conf = execute('ls /etc/vz/conf/%(ctid)s.*' % ct)
    extensions = []
    for path in ls_conf.split('\n'):
        ext = os.path.splitext(path)[1]
        if ext in valid_extensions:
            extensions.append(ext)
    ct['extensions'] = ','.join(extensions)
    if ',' in ct['extensions']:
        execute('rename %(ctid)s %(new_ctid)s /etc/vz/conf/%(ctid)s'
                '{%(extensions)s}' % ct)
    else:
        execute('rename %(ctid)s %(new_ctid)s /etc/vz/conf/%(ctid)s'
                '%(extensions)s' % ct)
    execute('mv %(private)s/%(ctid)s %(dest)s/%(new_ctid)s' % ct)
    execute('mv %(root)s/%(ctid)s %(root)s/%(new_ctid)s' % ct)
    rename_quota_file(ct['ctid'], ct['new_ctid'])
    ex = execute(("vzctl set %(new_ctid)s "
                  "--private '%(dest)s/$VEID' --save") % ct)
    print ex


def process_ct_list(options, ct_list):
    running = map(str, get_openvz_running_ctids())
    stopped = map(str, get_openvz_stopped_ctids())
    for ct in ct_list:
        if ct['ctid'] not in running + stopped:
            # XXX: No VM with such ctid
            print "Source VM not found"
            continue
        if ct['new_ctid'] in running + stopped:
            # XXX: VM Exists with target ctid. Bail out!
            print ("Can't migrate %s to %s. "
                   "Target already exists. Skipping." %
                   (ct['ctid'], ct['new_ctid']))
            continue
        if ct['ctid'] in running:
            if options.copy:
                # XXX: Running copntainer can't user copy option
                print ("Can't use copy option with running container "
                       "(ctid %s), skipping." % ct['ctid'])
                continue
            if 'dest' in ct and ct['dest']:
                if not os.path.exists(ct['dest']):
                    print ("Destnation path %s for %s:%s dosen't exist, "
                           "skipping." % (ct['dest'],
                                          ct['ctid'],
                                          ct['new_ctid']))
                    continue
            move_running_vm(ct)
        else:
            copy_stopped(ct) if options.copy else move_stopped(ct)


def main():
    usage = """
%prog <CT List>
<CT List> = <source_CTID>:<dest_CTID>[:<dest_private>] [...]
%prog -C <CT List>
<CT List> = <source_CTID>:<dest_CTID>[:<dest_private>] [...]
%prog --help"""
    parser = OptionParser(usage=usage)
    parser.add_option('-C', '--copy', action='store_true', dest='copy',
                      default=False, help=('Clones the source Container '
                      'instead of moving it.'))
    parser.add_option('-s', '--fast-sid', action='store_true', dest='fast_sid',
                      default=False, help=('Allows you to speed up the '
                      'process of cloning the Container.'))
    parser.add_option('-d', '--destroy-source', action='store_true',
                      dest='destroy_source', default=False,
                      help='Destroys the source Container after its cloning.')
    parser.add_option('-n', '--disable-network', action='store_true',
                      dest='no_network', default=False,
                      help=('Disables offline management '
                      'for the source Container after its cloning.'))
    parser.add_option('-l', '--skiplock', action='store_true', dest='skiplock',
                      default=False, help=('Allows you to clone '
                      'locked Containers.'))
    parser.add_option('--verbose', action='store_true', dest='verbose',
                      default=False, help=('Sets log level to maximum possible'
                      ' value for this vzmlocal session.'))
    parser.add_option('--quiet', action='store_true', dest='quiet',
                      default=False, help=('Disables logging to screen and'
                      ' to the log file.'))
    (options, args) = parser.parse_args()
    if not args:
        parser.print_usage()
        sys.exit(1)
    ct_list = []
    arg_params = ['ctid', 'new_ctid', 'dest']
    for arg in args:
        ct = dict(zip(arg_params, arg.split(':')))
        if 'ctid' not in ct or not ct['ctid'] or \
                'new_ctid' not in ct or not ct['new_ctid']:
            parser.print_usage()
            sys.exit(2)
        if not ct['ctid'].isdigit() or not ct['new_ctid'].isdigit():
            print ('Source and destination CTID must be numeric values, '
                   'got %(ctid)s:%(new_ctid)s') % ct
            sys.exit(3)
        ct_list.append(ct)
    process_ct_list(options, ct_list)


if __name__ == '__main__':
    main()
