#!/usr/bin/env python
# vim: set expandtab ts=4 sw=4:

# Copyright (C) 2009 University of Victoria
# You may distribute under the terms of either the GNU General Public
# License or the Apache v2 License, as specified in the README file.

# cloud_status - tool to display information about cloud scheduler
# 
import xmlrpclib
import sys
import socket
from optparse import OptionParser
import logging
import platform

import cloudscheduler.utilities as utilities 
import cloudscheduler.cloud_management as cloud_management
import cloudscheduler.config as config

def main(argv=None):

    # Parse command line options
    parser = OptionParser()
    parser.add_option("-f", "--config-file", dest="config_file", metavar="FILE",
                      help="Designate a Cloud Sceduler config file")
    parser.add_option("-s", "--server-hostname", dest="server_hostname",
                      metavar="HOSTNAME",
                      help="Pick a specific machine's Cloud Scheduler"
                           "information server")
    parser.add_option("-P", "--port", dest="port", metavar="PORT",
                      help="Pick a custom port to connect to Cloud Scheduler"
                           "information server")
    parser.add_option("-a", "--all", action="store_true", 
                      dest="all", default=False, 
                      help="Apply Operation to All [VMs, Jobs, etc]")
    parser.add_option("-k", "--kill-vm", dest="killvm", action="store_true", 
                      default=False, help="Kill a VM, must specify cloud and VM ID.")
    parser.add_option("-c", "--cloud", dest="cloud_name", metavar="NAME", 
                      help="Cloud name for operation.")
    parser.add_option("-n", "--vm-id", dest="vm_id", metavar="ID", 
                      help="The VM ID of VM for operation.")
    parser.add_option("-b", "--number", dest="number", metavar="NUM", 
                      help="Specify number of virtual machine to perform operation on.")
    parser.add_option("-p", "--refresh-proxy", metavar="PROXYTYPE", action="store",
                      dest="proxy_refresh", type="choice", choices=['vm','job'], help="Refresh Proxies for user")
    parser.add_option("-u", "--user", dest="user", action="store", metavar="USER", help="User to perform operation on")
    parser.add_option("-r", "--reconfig", dest="reconfig", action="store_true", default=False, help="Reload cloud_resources.conf")
    parser.add_option("-l", "--log-level", dest="log_level", action="store", type="choice", choices=['VERBOSE', 'DEBUG', 'WARNING', 'ERROR', 'INFO', 'CRITICAL'], help="Change the logging level")
    parser.add_option("-e", "--enable", dest="enable", action="store", metavar="NAME", help="Enable cloud NAME")
    parser.add_option("-d", "--disable", dest="disable", action="store", metavar="NAME", help="Disable cloud NAME")
    parser.add_option("-m", "--remove", dest="remove", action="store_true", help="Remove a VM from Cloudscheduler management, leave VM running. Requires -c name, and -n id or -a all.")
    parser.add_option("-q", "--quick-exit", dest="quick", action="store_true", default=False, help="Toggle the quick shutdown flag of CS.")
    parser.add_option("-i", "--reload-limits", dest="limits", action="store_true", default=False, help="Reload the user limits file.")
    parser.add_option("-j", "--list-limits", dest="list_limits", action="store_true", default=False, help="List the current user limits.")
    parser.add_option("-o", "--force-retire", dest="force_retire", action="store_true", default=False, help="Force Retire a VM. Requires -c name, and -n id or -a all.")
    parser.add_option("-x", "--reset-vm-state", dest="reset_state", action="store_true", default=False, help="Reset the override state of VM. Requires -c name and -n id.")
    parser.add_option("-t", "--reload-target-alias", dest="alias", action="store_true", default=False, help="Reload the Target Cloud Alias file.")
    parser.add_option("-y", "--list-alias", dest="list_alias", action="store_true", default=False, help="List the current cloud aliases.")
    parser.add_option("-v", "--vm-allocation", dest="vm_allocation", action="store", metavar="NUM", help="Specify a new vm slot value for cloud")
    parser.add_option("-C", "--comment", dest="comment", action="store", default=None, metavar="COM", help="Comment of why command being executed. Required for all commands")

    try:
        (cli_options, _) = parser.parse_args()
    except Exception as e:
        print "Failed to parse options does some value need to be wrapped in quotes?"
        print e
        sys.exit(-1)

    # Initialize config
    if cli_options.config_file:
        config.setup(cli_options.config_file)
    else:
        config.setup()

    # Set up logging
    log = logging.getLogger("cloudadmin")
    logging._srcfile = None
    logging.logProcesses = 0
    log.setLevel(utilities.LEVELS[config.log_level])
    log_formatter = logging.Formatter(config.log_format)
    if config.log_stdout:
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(log_formatter)
        log.addHandler(stream_handler)

    if config.log_location_cloud_admin:
        file_handler = None
        if config.log_max_size:
            file_handler = logging.handlers.RotatingFileHandler(
                                            config.log_location_cloud_admin,
                                            maxBytes=config.log_max_size)
        else:
            try:
                file_handler = logging.handlers.WatchedFileHandler(
                                            config.log_location_cloud_admin,)
            except AttributeError:
                # Python 2.5 doesn't support WatchedFileHandler
                file_handler = logging.handlers.RotatingFileHandler(
                                            config.log_location_cloud_admin,)

        file_handler.setFormatter(log_formatter)
        log.addHandler(file_handler)

    if not config.log_location_cloud_admin and not config.log_stdout:
        null_handler = utilities.NullHandler()
        log.addHandler(null_handler)

    def _check_for_comment():
            if config.admin_log_comments:
                if not cli_options.comment:
                    print "Admin comments being enforced. Please use -C 'reason for command' when performing this option."
                    log.debug("Attempted to perform action without required comment.")
                    return False
                else:
                    log.info("Comment: %s" % cli_options.comment)
                    return True
            else:
                return True
    # Get port to connect to info server.
    #   Precedence: -p argument, then from config module
    if cli_options.port:
        server_port = cli_options.port
    else:
        server_port = config.admin_server_port

    if cli_options.server_hostname:
        server_hostname = cli_options.server_hostname
    else:
        server_hostname = platform.node()

    # Connect to info server
    try:
        s = xmlrpclib.ServerProxy("http://%s:%s" %
                                  (server_hostname, server_port))
        if cli_options.killvm and _check_for_comment():
            if cli_options.cloud_name:
                if cli_options.vm_id:
                    print s.shutdown_vm(cli_options.cloud_name, cli_options.vm_id)
                    log.info("Shutting down VM: %s on: %s" % (cli_options.vm_id, cli_options.cloud_name))
                elif cli_options.all:
                    print s.shutdown_cluster_all(cli_options.cloud_name)
                    log.info("Shutting down all VMs on cloud: %s" % cli_options.cloud_name)
                elif cli_options.number:
                    print s.shutdown_cluster_count(cli_options.cloud_name, cli_options.number)
                    log.info("Shutting down %s VMs on cloud: %s" % (cli_options.number, cli_options.cloud_name))
                else:
                    print "Must provide a -n [VM ID], -a to kill all VMs, or -b [count] to kill a set number on cloud."
                    log.debug("Kill VMs called without valid vm id or all flag.")
            else:
                print "Must Provide the cloud name to kill VMs on and the VM ID, count, or all flag."
                log.debug("Kill VMs called without valid cloud name.")
        elif cli_options.remove and _check_for_comment():
            if cli_options.cloud_name:
                if cli_options.vm_id:
                    print s.delete_vm_entry(cli_options.cloud_name, cli_options.vm_id)
                    log.info("Removing VM entry: %s on: %s" % (cli_options.vm_id, cli_options.cloud_name))
                elif cli_options.all:
                    print s.delete_all_vm_entry_cloud(cli_options.cloud_name)
                    log.info("Removing all VM entries from Cloud Scheduler on cloud: %s" % cli_options.cloud_name)
                else:
                    print "Must provide a -n [VM ID] or -a to remove all VMs on this cloud."
                    log.debug("Remove VMs called without vm id or all flag.")
            else:
                print "Must Provide the cloud name to remove VMs on and the VM ID or all flag."
                log.debug("Remove VMs called without cloud name.")
        elif cli_options.force_retire and _check_for_comment():
            if cli_options.cloud_name:
                if cli_options.vm_id:
                    print s.force_retire_vm(cli_options.cloud_name, cli_options.vm_id)
                    log.info("Force retiring VM: %s on cloud: %s" % (cli_options.vm_id, cli_options.cloud_name))
                elif cli_options.all:
                    print s.force_retire_all_vm(cli_options.cloud_name)
                    log.info("Force retiring all VMs on: %s" % cli_options.cloud_name)
                elif cli_options.number:
                    print s.force_retire_count_vm(cli_options.cloud_name, cli_options.number)
                    log.info("Force retiring %s VMs, on cloud: %s" %(cli_options.number, cli_options.cloud_name))
                else:
                    print "Must provide a -n [VM ID], -a (all), or -b [count] to force retire all or some VMs on this cloud."
                    log.debug("Force retire called on cloud: %s, missing valid vm or option flag." % cli_options.cloud_name)
            else:
                print "Must Provide the cloud name to force retire VMs on and the VM ID, all or count flag."
                log.debug("Force retire called without valid cloud name.")
        elif cli_options.reset_state and _check_for_comment():
            if cli_options.cloud_name:
                if cli_options.vm_id:
                    print s.reset_override_state(cli_options.cloud_name, cli_options.vm_id)
                    log.info("Reset state of vm: %s on cloud: %s" % (cli_options.vm_id, cli_options.cloud_name))
                else:
                    print "Must provide -n [VM ID] to reset state of VM."
                    log.debug("Reset state attempted without valid vm id.")
            else:
                print "Must provide the cloud name and VM ID to reset override state on."
                log.debug("reset state attempted without valid cloud name.")
        elif cli_options.enable:
            print s.enable_cloud(cli_options.enable)
            log.info("Cloud: %s enabled." % cli_options.enable)
        elif cli_options.disable:
            print s.disable_cloud(cli_options.disable)
            log.info("Cloud: %s disabled." % cli_options.disable)
        elif cli_options.reconfig:
            print "Reconfig Disabled - use 'quickrestart'"
            log.debug("Call to disabled reconfig option. service quickrestart suggested.")
            #print s.cloud_resources_reconfig()
        elif cli_options.limits and _check_for_comment():
            if s.user_limit_reload():
                print "User-Limit file reloaded."
                log.info("Reloaded the user limlt file.")
            else:
                print "No Limits set - list empty or problem parsing file."
                log.debug("Problem reloading user limits, check format, path, permissions.")
        elif cli_options.list_limits:
            print s.list_user_limits()
            log.debug("Listing the user limit file")
        elif cli_options.alias and _check_for_comment():
            if s.cloud_alias_reload():
                print "Target Cloud Alias file reloaded."
                log.info("Reloaded the cloud_alias file")
            else:
                print "Failed to load alias file - check json format, path, or permissions."
                log.debug("Failed to reload cloud alias file, check format, path, permissions.")
        elif cli_options.list_alias:
            print s.list_cloud_alias()
            log.debug("Listing cloud_alias contents")
        elif cli_options.log_level and _check_for_comment():
            print s.change_log_level(cli_options.log_level)
            log.info("Changed logging level to: %s" % cli_options.log_level)
        elif cli_options.proxy_refresh and _check_for_comment():
            if cli_options.user:
                if cli_options.proxy_refresh.startswith('v'):
                    print "Refreshing VM proxies for user %s." % cli_options.user
                    s.refresh_vm_proxy_user(cli_options.user)
                    log.info("Refreshing VM proxies for user %s." % cli_options.user)
                elif cli_options.proxy_refresh.startswith('j'):
                        print "Refreshing Job proxies for user %s." % cli_options.user
                        s.refresh_job_proxy_user(cli_options.user)
                        log.info("Refresh job proxies for user %s" % cli_options.user)
                else:
                    print "Unsupported Proxy type, enter 'job' or 'vm'."
                    log.debug("Incorrect proxy type entered: valid options are job or vm")
            else:
                print "Must Specify a user with -u or --user."
                log.debug("Attempting to refresh proxy, missing user flag.")
        elif cli_options.quick and _check_for_comment():
            s.perform_quick_shutdown()
            print "Toggled Quick shutdown flag."
            log.info("Toggled quick shutdown flag.")
        elif cli_options.vm_allocation and _check_for_comment():
            if cli_options.cloud_name:
                print 'Adjusting vm_slots on %s to %s' % (cli_options.cloud_name, cli_options.vm_allocation)
                print s.adjust_cloud_allocation(cli_options.cloud_name, cli_options.vm_allocation)
                log.info("Attempting to adjust vm allocation on %s. Target: %s" % (cli_options.cloud_name, cli_options.vm_allocation))
            else:
                print "Change vm slot of cloud need '-c cloudname' for cloud to change allocation of"
                log.debug("Attempting to adjust vm allocation but missing cloud name.")
        else:
            print "Run cloud_admin -h for help."
            log.debug("Invoked with no arguments or other error.")


    except socket.error:
        print "%s: couldn't connect to cloud scheduler at %s on port %s."\
               % (sys.argv[0], server_hostname, server_port)
        print "Is the cloud scheduler running on port %s?" % server_port
    except:
        print "Unexpected error: ", sys.exc_info()[0], sys.exc_info()[1]
        print "Is the cloud scheduler running on port %s?" % server_port



if __name__ == "__main__":
    sys.exit(main())
