# Copyright (C) 2010-2014 GRNET S.A.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import string
from optparse import make_option
from django.db import transaction
 
from synnefo.lib.ordereddict import OrderedDict
from synnefo.quotas import util
from synnefo.quotas import enforce
from synnefo.quotas import errors
from snf_django.management.commands import SynnefoCommand, CommandError
from snf_django.management.utils import pprint_table
 
 
DEFAULT_RESOURCES = ["cyclades.cpu",
                     "cyclades.ram",
                     "cyclades.floating_ip",
                     ]
 
 
class Command(SynnefoCommand):
    help = """Check and fix quota violations for Cyclades resources.
    """
 
    command_option_list = (
        make_option("--max-operations",
                    help="Limit operations per backend."),
        make_option("--users", dest="users",
                    help=("Enforce resources only for the specified list "
                          "of users, e.g uuid1,uuid2")),
        make_option("--exclude-users",
                    help=("Exclude list of users from resource enforcement")),
        make_option("--projects",
                    help=("Enforce resources only for the specified list "
                          "of projects, e.g uuid1,uuid2")),
        make_option("--exclude-projects",
                    help=("Exclude list of projects from resource enforcement")
                    ),
        make_option("--resources",
                    help="Specify resources to check, default: %s" %
                    ",".join(DEFAULT_RESOURCES)),
        make_option("--fix",
                    default=False,
                    action="store_true",
                    help="Fix violations"),
        make_option("--force",
                    default=False,
                    action="store_true",
                    help=("Confirm actions that may permanently "
                          "remove a vm")),
        make_option("--shutdown-timeout",
                    help="Force vm shutdown after given seconds."),
    )
 
    def confirm(self):
        self.stdout.write("Confirm? [y/N] ")
        try:
            response = raw_input()
        except EOFError:
            response = "ABORT"
        if string.lower(response) not in ['y', 'yes']:
            self.stderr.write("Aborted.\n")
            exit()
 
    def get_handlers(self, resources):
        def rem(v):
            try:
                resources.remove(v)
                return True
            except ValueError:
                return False
 
        if resources is None:
            resources = list(DEFAULT_RESOURCES)
        else:
            resources = resources.split(",")
 
        handlers = [h for h in enforce.RESOURCE_HANDLING if rem(h[0])]
        if resources:
            m = "No such resource '%s'" % resources[0]
            raise CommandError(m)
        return handlers
 
    @transaction.commit_on_success
    def handle(self, *args, **options):
        write = self.stderr.write
        fix = options["fix"]
        force = options["force"]
        handlers = self.get_handlers(options["resources"])
        maxops = options["max_operations"]
        if maxops is not None:
            try:
                maxops = int(maxops)
            except ValueError:
                m = "Expected integer max operations."
                raise CommandError(m)
 
        shutdown_timeout = options["shutdown_timeout"]
        if shutdown_timeout is not None:
            try:
                shutdown_timeout = int(shutdown_timeout)
            except ValueError:
                m = "Expected integer shutdown timeout."
                raise CommandError(m)
 
        excluded_users = options['exclude_users']
        excluded_users = set(excluded_users.split(',')
                             if excluded_users is not None else [])
 
        users_to_check = options['users']
        if users_to_check is not None:
            users_to_check = list(set(users_to_check.split(',')) -
                                  excluded_users)
 
        try:
            qh_holdings = util.get_qh_users_holdings(users_to_check)
        except errors.AstakosClientException as e:
            raise CommandError(e)
 
        excluded_projects = options["exclude_projects"]
        excluded_projects = set(excluded_projects.split(',')
                                if excluded_projects is not None else [])
 
        projects_to_check = options["projects"]
        if projects_to_check is not None:
            projects_to_check = list(set(projects_to_check.split(',')) -
                                     excluded_projects)
 
        try:
            qh_project_holdings = util.get_qh_project_holdings(
                projects_to_check)
        except errors.AstakosClientException as e:
            raise CommandError(e)
 
        qh_project_holdings = sorted(qh_project_holdings.items())
        qh_holdings = sorted(qh_holdings.items())
        resources = set(h[0] for h in handlers)
        dangerous = bool(resources.difference(DEFAULT_RESOURCES))
 
        opts = {"shutdown_timeout": shutdown_timeout}
        actions = {}
        overlimit = []
        viol_id = 0
 
        if users_to_check is None:
            for resource, handle_resource, resource_type in handlers:
                if resource_type not in actions:
                    actions[resource_type] = OrderedDict()
                actual_resources = enforce.get_actual_resources(
                    resource_type, projects=projects_to_check)
                for project, project_quota in qh_project_holdings:
                    if enforce.skip_check(project, projects_to_check,
                                          excluded_projects):
                        continue
                    try:
                        qh = util.transform_project_quotas(project_quota)
                        qh_value, qh_limit, qh_pending = qh[resource]
                    except KeyError:
                        write("Resource '%s' does not exist in Quotaholder"
                              " for project '%s'!\n" %
                              (resource, project))
                        continue
                    if qh_pending:
                        write("Pending commission for project '%s', "
                              "resource '%s'. Skipping\n" %
                              (project, resource))
                        continue
                    diff = qh_value - qh_limit
                    if diff > 0:
                        viol_id += 1
                        overlimit.append((viol_id, "project", project, "",
                                          resource, qh_limit, qh_value))
                        relevant_resources = enforce.pick_project_resources(
                            actual_resources[project], users=users_to_check,
                            excluded_users=excluded_users)
                        handle_resource(viol_id, resource, relevant_resources,
                                        diff, actions)
 
        for resource, handle_resource, resource_type in handlers:
            if resource_type not in actions:
                actions[resource_type] = OrderedDict()
            actual_resources = enforce.get_actual_resources(resource_type,
                                                            users_to_check)
            for user, user_quota in qh_holdings:
                if enforce.skip_check(user, users_to_check, excluded_users):
                    continue
                for source, source_quota in user_quota.iteritems():
                    if enforce.skip_check(source, projects_to_check,
                                          excluded_projects):
                        continue
                    try:
                        qh = util.transform_quotas(source_quota)
                        qh_value, qh_limit, qh_pending = qh[resource]
                    except KeyError:
                        write("Resource '%s' does not exist in Quotaholder"
                              " for user '%s' and source '%s'!\n" %
                              (resource, user, source))
                        continue
                    if qh_pending:
                        write("Pending commission for user '%s', source '%s', "
                              "resource '%s'. Skipping\n" %
                              (user, source, resource))
                        continue
                    diff = qh_value - qh_limit
                    if diff > 0:
                        viol_id += 1
                        overlimit.append((viol_id, "user", user, source,
                                          resource, qh_limit, qh_value))
                        relevant_resources = actual_resources[source][user]
                        handle_resource(viol_id, resource, relevant_resources,
                                        diff, actions)
 
        if not overlimit:
            write("No violations.\n")
            return
 
        headers = ("#", "Type", "Holder", "Source", "Resource", "Limit",
                   "Usage")
        pprint_table(self.stdout, overlimit, headers,
                     options["output_format"], title="Violations")
 
        if any(actions.values()):
            self.stdout.write("\n")
            if fix:
                if dangerous and not force:
                    write("You are enforcing resources that may permanently "
                          "remove a vm.\n")
                    self.confirm()
                write("Applying actions. Please wait...\n")
            title = "Applied Actions" if fix else "Suggested Actions"
            log = enforce.perform_actions(actions, maxops=maxops, fix=fix,
                                          options=opts)
            headers = ("Type", "ID", "State", "Backend", "Action", "Violation")
            if fix:
                headers += ("Result",)
            pprint_table(self.stdout, log, headers,
                         options["output_format"], title=title)