#!/usr/bin/env python
#
#   ConVirt   -  Copyright (c) 2008 Convirture Corp.
#   ======
#
# ConVirt is a Virtualization management tool with a graphical user
# interface that allows for performing the standard set of VM operations
# (start, stop, pause, kill, shutdown, reboot, snapshot, etc...). It
# also attempts to simplify various aspects of VM lifecycle management.
#
#
# This software is subject to the GNU General Public License, Version 2 (GPLv2)
# and for details, please consult it at:
#
#    http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
# 
#
#


from convirt.core.model.ManagedNode import ManagedNode
from convirt.core.model.Groups import ServerGroup
from convirt.core.utils.constants import *
import convirt.core.utils.utils
from pprint import pprint

utils = convirt.core.utils.utils
    
import os, socket, tempfile, traceback, stat

class GridManager:

    # creds_helper will eventually become part of XenNodeFactory
    def __init__(self, store, config, registry,creds_helper):
        self.store = store   # persistent store
        self.config = config # config store
        self.registry = registry
        self.group_list = {}
        self.node_list = {} # nodes that are not categorized

        # initialize stat from the store
        host_names  = store.getHosts()

        group_list_map = store.getGroups()   
        group_names = group_list_map.keys()


        for name in group_names:
            g_map = group_list_map[name]
            if isinstance(g_map, list):
                g_map = {"name":name, 'node_list':g_map, "settings":{}}
                group_list_map[name] = g_map
            
            self.group_list[name] = ServerGroup(name, None, g_map["settings"])            

        group_map = group_list_map
        #pprint.pprint(group_map)

        if len(self.group_list) is 0:
            self._create_default_groups()
            
        for host in host_names:

            _remote = store.getHostProperty(prop_isRemote, host)
            if not _remote:
                remote = utils.is_host_remote(host)
                self.store.setHostProperty(prop_isRemote,str(remote),host)
            else:
                remote = eval(_remote)

            props = store.getHostProperties(host)
            if props.has_key("platform"):
                platform = props["platform"]
            else:
                platform = "xen" # help in upgrade
                props["platform"] = platform
                self.store.setHostProperty(prop_platform, platform, host)

            if not props.has_key("hostname"):
                props["hostname"] = host
                self.store.setHostProperty(prop_hostname, host, host)

            try:
                factory = self.getFactory(platform)
            except Exception, ex:
                print "ERROR : Skipping server %s. No factory found for %s server type" % (host, platform)
                print ex
                continue

            node = factory.create_node_from_repos(**props)

            grps = self._find_groups(host, group_map)
            
            if len(grps) == 0:
                self.node_list[node.hostname] = node
            else:
                for g in grps:
                    group = self.group_list.get(g)
                    if group is not None:
                        group._addNode(node)


    def getFactory(self, platform):
        factory =  self.registry.get_node_factory(platform)
        if factory :
            return factory
        
        raise Exception("No factory for %s platform." % platform)

    def _save_groups(self):
        self.store.saveGroups(self.group_list)

    def _save_node(self, node):
        factory = self.getFactory(node.platform)
        props = factory.get_props_for_repos(node)
        self.store.setHostProperties(props, node.hostname)

    def _create_default_groups(self):
        for name in ["Desktops", "QA Lab", "Servers"]:
            default_group = ServerGroup(name)
            self.group_list[default_group.name] = default_group
        self._save_groups()


    def _find_groups(self, node_name, group_list_map = None):
        # given a node name find all the group names to which it belongs
        grp = []

        if group_list_map is None:
            group_list_map = {}
            for g in self.group_list:
#                group_map[g] = self.group_list[g].getNodeNames()
                group_list_map[g] = {"node_list" : self.group_list[g].getNodeNames()}
            
        for group_name in group_list_map.keys():
            g = group_list_map[group_name]
            node_list = g["node_list"]
            if node_name in node_list:
                grp.append(group_name)
                
        return grp


    def discoverNodes(self,ip_list):
        pass


    def getNodeNames(self, group_name = None):
        if group_name is None:
            return self.node_list.keys()
        else:
            group = self.group_list.get(group_name)
            if group is not None:
                return group.getNodeNames()


    def getNodeList(self, group_name = None):
        if group_name is None:
            return self.node_list
        else:
            group = self.group_list.get(group_name)
            if group is not None:
                return group.getNodeList()

    def getNode(self,name, group_name = None):
        if group_name is None:
            return self.node_list[name]
        else:
            group = self.group_list.get(group_name)
            if group is not None:
                return group.getNode(name)

    
    def addNode(self,node, group_name = None):
        if group_name is None:
            if self.node_list.get(node.hostname) is None:
                self._save_node(node)
                self.node_list[node.hostname] = node
            else:
                raise Exception("Server %s already exists" % node.hostname)
        else:
            group = self.group_list.get(group_name)
            if group is not None:
                group._addNode(node)
                self._save_node(node)
                self._save_groups()


    def removeNode(self,name,group_name = None):
        
        if group_name is None:
            if self.node_list.get(name) is not None:
                del self.node_list[name]
        else:
            group = self.group_list.get(group_name)
            if group is not None:
                group._removeNode(name)
                self._save_groups()

        # remove the node, if not part of any other groups
        groups = self._find_groups(name)
        if len(groups) == 0 and name not in self.node_list.keys():
            self.store.removeHost(name)

    def list_nodes(self):
        print "## DUMP =="
        for name in self.getNodeNames():
            print "Node name" ,  name
        for g in self.group_list:
            print "group ", g
        print "## END DUMP =="


    
    def cloneNode(self,source_node, dest):
        pass


    def migrateDomains(self, source_node, vm_list, dest, live, force=False):
        ex_list = []
        
        try:
            try:
                if len(vm_list) > 0 :
                    if not force:
                        (err_list, warn_list) = \
                                   source_node.migration_checks(vm_list,
                                                                dest, live)

                for vm in vm_list:
                    try:
                        #source_node.migrate_dom(vm.name, dest, live)
                        self.migrateDomain(vm.name,source_node,dest,live,
                                           force = True) # checks done 
                    except Exception, ex1:
                        traceback.print_exc()
                        ex_list.append("Error migrating " + vm.name + " : " + str(ex1))

            except Exception, ex:
                traceback.print_exc()
                raise ex
        finally: 
            if len(ex_list) > 0:
                msg = "Errors in migrate all operations \n"
                for m in ex_list:
                    msg = msg + m + "\n"
                raise Exception(msg)

        

    def migrateNode(self,source_node, dest, live, force = False):
        """ Migrate all vms on this node to a dest node."""
        vm_list = []
        for vm in source_node.get_doms():
            if not vm.isDom0():
                vm_list.append(vm)
                
        self.migrateDomains(source_node, vm_list, dest, live, force)

    def cloneDomain(self,source_dom_name,
                    source_node,
                    dest_node=None):
        pass
    
    def migrateDomain(self,source_dom_name,
                      source_node,
                      dest_node, live, force = False):
        dom = source_node.get_dom(source_dom_name)
        running = dom.is_resident()
        
        if not force and running:
            (err_list, warn_list) = source_node.migration_checks([dom],
                                                                 dest_node,
                                                                 live)

        ## No good mechanism for sucess or failue till we cutover to
        # task / XenAPI
        try:
            if running:
                source_node.migrate_dom(source_dom_name, dest_node, live)
        except socket.timeout:
            print "ignoring timeout on migration "
            pass

        # move config files if necessary.
        self.move_config_file(source_dom_name, source_node, dest_node)

    def move_config_file(self, dom_name, source_node, dest_node):
        dom = source_node.get_dom(dom_name)
        if dom and dom.get_config():
            config = dom.get_config()
            target_filename = config.filename
            isLink = False
            mode = source_node.node_proxy.lstat(target_filename).st_mode
            if stat.S_ISLNK(mode) is True:
                isLink = True
                print "CONFIG NAME  = ", config.filename
                target = source_node.node_proxy.readlink(config.filename)
                # transform the relative links to absolute
                print "ORIG TARGET  = ", target
                target = os.path.join(os.path.dirname(config.filename), target)
                print "TARGET  = ", target
                target_filename = os.path.abspath(target)
                print "TARGET FILENAME = ", target_filename
                
                          
            if target_filename is not None:
                if dest_node.node_proxy.file_exists(target_filename):
                    # we are done:
                    pass
                else:
                    # create a temp file on the client node.
                    # and move it to the dest node.
                    (t_handle, t_name) = tempfile.mkstemp(prefix=dom_name)
                    try:
                        source_node.node_proxy.get(target_filename,
                                                   t_name)
                        utils.mkdir2(dest_node, 
                                     os.path.dirname(target_filename))
                        dest_node.node_proxy.put(t_name,
                                                 target_filename)
                        source_node.node_proxy.remove(target_filename)
                        
                    finally:
                        os.close(t_handle)
                        os.remove(t_name)
                    
                if isLink:
                    dest_node.node_proxy.symlink(target_filename,
                                                 config.filename)
                    source_node.node_proxy.remove(config.filename)

            # now lets reassociate the config with the new node.
            dest_node.add_dom_config(config.filename)
            source_node.remove_dom_config(config.filename)
            
                

    # server pool related functions.
    def getGroupNames(self):
        return self.group_list.keys()

    def getGroupList(self):
        return self.group_list

    def getGroup(self,name):
        return self.group_list[name]
       
    def setGroupVars(self, name):
        self._save_groups()
    
    def addGroup(self,grp):
        if grp.name in self.group_list:
            raise Exception("Group already exists.")
        self.group_list[grp.name] = grp
        self._save_groups()

    def removeGroup(self,name, deep=False):
        if deep:
            for node_name in self.group_list[name].getNodeNames():
                self.removeNode(node_name, name)
        del self.group_list[name]
        self._save_groups()


    def shutdown(self):
        # iterate through each node and disconnect it
        groups = self.getGroupList()
        for group in groups.itervalues():
            nodes = self.getNodeList(group.name)
            for n in nodes.itervalues():
                try:
                    n.disconnect()
                except Exception, ex:
                    print ex
        ungrouped_nodes = self.getNodeList()
        for n in ungrouped_nodes.itervalues():
            try:
                n.disconnect()
            except Exception, ex:
                print ex
        

    
        
        
