#!/opt/alt/python27/bin/python
# -*- coding: utf-8 -*-

# cloudlinux-license Utility to erase package limits in /etc/container/ve.cfg
# for ISPManager5 panels
#
# Copyright CloudLinux Zug GmbH 2010-2016 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT

import sys
import subprocess
import simplejson as json
import cldetectlib as detect
from copy import deepcopy
from clcommon import ClPwd
from collections import defaultdict
from cllimits.lib import exec_utility
from clcontrollib import ISPManagerGetPackagesException
from lvectllib import get_XML_cfg, get_globals, check_value
import lvectllib
import xml.dom.minidom as xml
import time

UTILITY_PATH = "/usr/sbin/lvectl"


def check_if_isp5_master():
    if detect.getCP() and detect.is_ispmanager():
        return detect.ispmanager5_is_master()
    print("Either this server hasn't ControlPanel or that ControlPanel isn't ISPManager5")
    sys.exit(1)


def get_user_packages_dict():
    """
    Is copy-pasted from clcontrollib.py source code before it was replaced with LU-256.
    Returns dict of {'uid': 'package_name'}
    """
    clpwd = ClPwd()
    dict_uid_package = {}
    if check_if_isp5_master():
        # ISP5 master
        try:
            # Get users list
            # Call mgrctl: /usr/local/mgr5/sbin/mgrctl -m ispmgr user -o json
            p = subprocess.Popen(['/usr/local/mgr5/sbin/mgrctl', '-m', 'ispmgr', 'user', '-o', 'json'],
                                 stdout=subprocess.PIPE)
            out, _ = p.communicate()
        except OSError as e:
            raise ISPManagerGetPackagesException("ERROR: Can't execute /usr/local/mgr5/sbin/mgrctl: %s" % str(e))
        try:
            mgrctl_out = json.loads(out)
        except ValueError:
            raise ISPManagerGetPackagesException("ERROR: mgrctl invalid output:\n%s" % out)
        if 'doc' not in mgrctl_out or 'elem' not in mgrctl_out['doc']:
            print("ControlPanel has no users, maybe current license is outdated")
            sys.exit(1)
        users_data = mgrctl_out['doc']['elem']
        for user_data in users_data:
            try:
                # determine user location
                user_location = user_data['loc']['$']
                # user_location example: 'localhost (95.164.68.74)'
                # We don't support non-local users
                if not user_location.startswith('localhost'):
                    continue
                # determine uid
                user_name = user_data['name']['$']
                uid = clpwd.get_uid(user_name)
                # determine package
                package_data = user_data['preset']
                if '$orig' in package_data:
                    # non-package user
                    package_name = package_data['$orig'].replace('#', '')
                else:
                    # Package user
                    package_name = package_data['$']
                # Add uid-package to dictionary
                dict_uid_package[uid] = package_name
            except IndexError:
                # Ignore record, if any index absent
                continue
    else:
        # ISP5 node
        try:
            # Get users list
            # Call mgrctl: /usr/local/mgr5/sbin/mgrctl -m ispmgrnode user
            # JSON output not supported on node, use TEXT output
            p = subprocess.Popen(['/usr/local/mgr5/sbin/mgrctl', '-m', 'ispmgrnode', 'user'],
                                 stdout=subprocess.PIPE)
            out, _ = p.communicate()
        except (OSError, ), e:
            raise ISPManagerGetPackagesException("ERROR: Can't execute /usr/local/mgr5/sbin/mgrctl: %s" % str(e))
        # Parse output
        users_data = out.split('\n')
        for user_data in users_data:
            if not user_data:
                continue
            user_name = None
            try:
                # user parametres
                # user_params_list example:
                # ['name=bogdan1', 'fullname=', 'active=on', 'quota_total=0', 'quota_used=0',
                #  'limit_cpu=', 'limit_db=', 'limit_db_users=', 'limit_dirindex=index.html', 'index.php',
                #  'limit_emaildomains=', 'limit_emails=', 'limit_ftp_users=', 'limit_mailrate=',
                #  'limit_maxclientsvhost=', 'limit_memory=', 'limit_mysql_maxconn=',
                #  'limit_mysql_maxuserconn=', 'limit_mysql_query=', 'limit_mysql_update=',
                #  'limit_nginxlimitconn=', 'limit_php=', 'limit_process=', 'limit_webdomains=']
                user_params_list = user_data.split(' ')
                # print user_params_list
                for user_param in user_params_list:
                    if user_param.startswith('name'):
                        # username found
                        user_name = user_param.split('=')[1]
                        break
            except IndexError:
                # Ignore record, if any index absent
                pass
            # determine uid of user
            uid = clpwd.get_uid(user_name)
            # Add uid to dictionary.
            # Package name unavailable on ISP5 node, so create it manually by agreement with ISP Manager support
            dict_uid_package[uid] = "package_%s" % str(uid)
    return dict_uid_package


def construct_args(package_data, user_data):
    """
    Generates list of lvectl arguments to set limits

    :param dict package_data: Limits for given package as "limit_name": "limit_value"
    :param set user_data: Limits that user already has and we don't want to change
    :return: List of strings
    """
    res = []
    for key, value in package_data.iteritems():
        if key not in user_data:
            res.append("--%s=%s" % (key, value))
    return res

# Save XML config ve.cfg.timestamp
def save_ve_config_backup(XML):
    XML = XML.toprettyxml(encoding='utf-8',indent='', newl='')
    XML = XML.replace("\n",'').replace("\t",'')
    new_xml = xml.parseString(XML)
    ve_cfg_backup_name = '/etc/container/ve.cfg.' + str(int(time.time()))
    f = open(ve_cfg_backup_name, "w")
    f.write(new_xml.toprettyxml(encoding='utf-8'))
    f.close()


def main():
    lvectllib.get_global_lock(True)
    get_XML_cfg()
    # lvectllib uses global variables, so we have to get their values
    global_vars = get_globals()
    ve_defaults = global_vars['ve_defaults']
    # If we have no packages' limits in /etc/container/ve.cfg
    if len(global_vars['ve_package']) == 0:
        print("Here isn't any package. No changes needed")
        return
    # Create backup for ve.cfg
    save_ve_config_backup(global_vars['ve_cfg'])
    # Get list of users and their packages from mgrctl utility
    dict_uid_package = get_user_packages_dict()
    # Group uids by package names
    packages_uids = defaultdict(list)
    for uid, package in dict_uid_package.iteritems():
        packages_uids[package].append(uid)
    packages = {}
    to_delete = {}

    # For every package, get it's limits and put that to packages dict
    # store packages' names that exist in /etc/container/ve.cfg into to_delete dict
    for el in global_vars['ve_package']:
        pack_id = el.getAttribute('id')
        setup_data = {}
        if el.getAttribute('reseller'):
            to_delete[pack_id] = "--reseller=%s" % el.getAttribute('reseller')
        else:
            to_delete[pack_id] = ''
        if pack_id in packages_uids.keys():
            check_value('ncpu', el, ve_defaults, setup_data)
            try:
                cpu = el.getElementsByTagName('cpu')[0].getAttribute('limit')
                if cpu.endswith("%") or cpu.endswith("mhz") or cpu.endswith("ghz"):
                    setup_data['speed'] = cpu
                else:
                    setup_data['cpu'] = cpu
            except (ValueError, IndexError, TypeError):
                pass
            check_value('io', el, ve_defaults, setup_data)
            check_value('mem', el, ve_defaults, setup_data)
            # mem limit is in 4K bloks, convert it to M
            if setup_data.get('mem', False):
                setup_data['mem'] = str(int(setup_data['mem'])*4/1024) + 'M'
            try:
                ep = int(el.getElementsByTagName('other')[0].getAttribute('maxentryprocs'))
                setup_data['ep'] = ep
            except (ValueError, IndexError, TypeError):
                pass
            check_value('nproc', el, ve_defaults, setup_data)
            check_value('pmem', el, ve_defaults, setup_data)
            # pmem limit is in 4K bloks, convert it to M
            if setup_data.get('pmem', False):
                setup_data['pmem'] = str(int(setup_data['pmem'])*4/1024) + 'M'
            check_value('iops', el, ve_defaults, setup_data)
            packages[pack_id] = deepcopy(setup_data)

    users = defaultdict(set)
    for el in global_vars['ve_lve']:
        user_set = set(['vmem',])
        for child in el.childNodes:
            if child.nodeType == child.ELEMENT_NODE:
                if child.nodeName == 'other':
                    user_set.update(['ep', 'maxEntryProcs'])
                elif child.nodeName == 'cpu':
                    user_set.update(['cpu', 'speed'])
                elif child.nodeName == 'mem':
                    user_set.add('mem')
                else:
                    user_set.add(child.nodeName)
        users[str(el.getAttribute('id'))].update(user_set)

    for uid, package in dict_uid_package.iteritems():
        package_data = packages.get(package)
        if package_data is not None:
            # Call lvectl set uid --limit=value --limit=value to set limits from package to lve user
            limits = construct_args(package_data, users[str(uid)])
            if len(limits) > 0:
                exec_utility(UTILITY_PATH, ["set", str(uid)] + limits)

    for package, reseller in to_delete.iteritems():
        # Call lvectl package-delete package_name to delete package limits from /etc/container/ve.cfg
        exec_utility(UTILITY_PATH, ["package-delete", package, reseller])


if __name__ == "__main__":
    main()
