#!/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
# 
#
# author : Jd <jd_jedi@users.sourceforge.net>
#


# All classes in thse files are required Interfaces for managing
# machines/host/nodes with virtualization technology.

from convirt.core.utils.NodeProxy import Node, NodeWrapper
from convirt.core.utils.utils import XMConfig, LVMProxy, PyConfig
from convirt.core.utils.IPy import *
from convirt.core.utils.constants import *
from convirt.core.utils.phelper import HostValidationException,\
     AuthenticationException, CommunicationException

import sys,os,re,types, platform
import threading


class NodeException(Exception):
    def __init__(self):
	Exception.__init__(self)



class ManagedNode:
    """
    Interface that represents a node being managed.It defines useful APIs
    for clients to be able to do Management for a virtualized Node
    """

    def __init__(self,
                 hostname = None,
                 ssh_port = 22,
                 username=Node.DEFAULT_USER,
                 password=None,
                 isRemote=False,
                 helper = None,
                 use_keys = False,
                 address = None):

        self.hostname = hostname
        self.ssh_port = ssh_port
        self.username = username
        self.password = password
        self.isRemote = isRemote
        self.helper   = helper
        self.use_keys = use_keys
        if not address:
            self.address  = hostname
        else:
            self.address = address
        
        
        self._node_proxy_lock = threading.RLock()
        
        self._node_proxy = None    # lazy-init self.node_proxy 
        self._lvm_proxy = None     # lazy-init self.lvm_proxy 
        self._isLVMEnabled = None  # lazy-init self.isLVMEnabled 
        self._config = None        # lazy-init self.config
        self._environ = None       # lazy-init self.environ
        self._exec_path = None     # lazy init self.exec_path

        self.metrics = None
        
    # implement lazy initialization. 
    def __getattr__(self, name):
        if name == 'node_proxy':
            return self._init_node_proxy()
        if name == 'lvm_proxy': 
            return self._init_lvm_proxy()
        if name == 'config':
            return self._init_config()
        if name == 'isLVMEnabled':
            return self._init_isLVMEnabled()
        if name == 'environ':
            return self._init_environ()
        if name == 'exec_path':
            return self._init_exec_path()

    def is_in_error(self):
        if self.is_authenticated():
            # The following return does not seem to work!!
            #return  self.node_proxy.get_last_error() is not None
            return  self._node_proxy._last_error is not None
        else:
            return True
       

    def disconnect(self):
        if self._node_proxy is not None:
            self._node_proxy.disconnect()
            self._node_proxy = None

    def connect(self):
        if self._node_proxy is not None:
            self._node_proxy.connect(self.hostname,
                                     self.ssh_port,
                                     self.username,
                                     self.password,
                                     self.isRemote,
                                     self.use_keys)

        else:
            self._init_node_proxy()

    # for now simple check. Later need to go deep
    def is_authenticated(self):
        if self._node_proxy is not None:
            return self._node_proxy.n_p is not None
        return self._node_proxy
    
    # set credentials, allow credentials to be set later.
    def set_credentials(self, username, password):
        self.username = username
        self.password = password
        
    def is_remote(self):
        return self.isRemote


    def get_address(self):
        return self.address

    def set_address(self, address):
        self.address = address


    def get_metrics(self, refresh=False):
        if not refresh: return self.metrics

    def get_VM_count(self):
        pass
    
    def get_console(self):
        """
        get the console for the dom
        API migght need revision...
        """
        pass

    def get_terminal(self, username, password):
        """
        return tty terminal (telnet, ssh session ?)
        """
        pass
    
    def get_vnc(self):
        """
        get VNC session for this dom. VNC would popup username/password.
        """
        pass

    def get_os_info(self):
      return self.environ['os_info']
    
    def get_network_info(self):
        return self.environ['network_info']

    def get_nic_info(self):
        return self.environ['nic_info']

    def get_bridge_info(self):
        return self.environ['bridge_info']

    def get_default_bridge(self):
        return self.environ['default_bridge']

    def get_cpu_info(self):
      return self.environ['cpu_info']

    def get_disk_info(self):
      return self.environ['disk_info']

    def get_memory_info(self):
      return self.environ['memory_info']

    def get_os_info_display_names(self):
      display_dict = {key_os_release:display_os_release,key_os_machine:display_os_machine,key_os_distro_string:display_os_distro}
      return display_dict

    def get_network_info_display_names(self):
      display_dict = {key_network_interface_name:display_network_interface_name,key_network_ip:display_network_ip}
      return display_dict

    def get_cpu_info_display_names(self):
      display_dict = { key_cpu_count:display_cpu_count,key_cpu_vendor_id:display_cpu_vendor_id,key_cpu_model_name:display_cpu_model_name, key_cpu_mhz : display_cpu_mhz}
      return display_dict

    def get_memory_info_display_names(self):
      display_dict = {key_memory_total:display_memory_total,key_memory_free:display_memory_free}
      return display_dict

    def get_disk_info_display_names(self):
      display_dict = {key_disk_file_system:display_disk_file_system,key_disk_size:display_disk_size,key_disk_mounted:display_disk_mounted}
      return display_dict


    def _init_node_proxy(self):
        self._node_proxy_lock.acquire()
        try:
            if self._node_proxy is None:
                while True:
                    creds = None
                    try:
                        self._node_proxy = NodeWrapper( \
                            hostname = self.hostname,
                            ssh_port = self.ssh_port,
                            username = self.username,
                            password = self.password,
                            isRemote = self.isRemote,
                            use_keys = self.use_keys)
                        
                    except AuthenticationException, e:
                        creds = None
                        if self.helper and not use_keys:
                            creds = self.helper.get_credentials(self.hostname,
                                                                self.username)
                            if creds is None:
                                raise Exception("Server not Authenticated")
                            else:
                                self.username = creds.username
                                self.password = creds.password
                        else:
                            raise e
                    else:
                        break
        finally:
            self._node_proxy_lock.release()
        
        
        return self._node_proxy
        

    def _init_config(self):
        if self._config is None:
            self._config = XMConfig(self.node_proxy)
        return self._config

    def _init_lvm_proxy(self):
        if self._lvm_proxy is None and self.isLVMEnabled:            
                self._lvm_proxy = LVMProxy(self.node_proxy,
                                           self.exec_path)
        return self._lvm_proxy

    def _init_isLVMEnabled(self):
        if self._isLVMEnabled is None:
            conf_val = self.config.get(XMConfig.ENV, prop_lvm)
            if conf_val is None:
                self._isLVMEnabled = LVMProxy.isLVMEnabled(self.node_proxy,
                                                           self.exec_path)
                self.config.set(XMConfig.ENV,prop_lvm,str(self._isLVMEnabled))
            else:
                self._isLVMEnabled = eval(conf_val)                
        return self._isLVMEnabled
    
    def _init_environ(self):
        if self._environ is None:
            self._environ = NodeEnvHelper(self.node_proxy)
        return self._environ

    def _init_exec_path(self):
        if self._exec_path is None:
            self._exec_path = self.config.get(XMConfig.PATHS,
                                              prop_exec_path)
        return self._exec_path



class NodeEnvHelper:
    """A helper for ManagedNode for retrieving and storing
    Environment/Config information for a host."""

    def __init__(self, node_proxy):
        self.node_proxy = node_proxy
        self.__dict = None
        self.__populateDictionary()

    def __iter__(self):
        return self.__dict.iterkeys()

    def keys(self):
        return self.__dict.keys()
    
    def __getitem__(self, item):
        if not item: return None
        if self.__dict.has_key(item):
            return self.__dict[item]
        else:
            return None

    def __setitem__(self, item, value):
        self.__dict[item] = value

    def refresh(self):
        self.__dict = None
        self.__populateDictionary()

    # try to get the network definition from ip and netmask
    def populate_network(self, n):
        if n.get("ip") and n.get("netmask"):
            try:
                ip = IP(n.get("ip"))
                mask = IP(n.get("netmask"))
                net_int = ip.int() & mask.int()
                net_ip = IP(net_int)
                net_ip_mask = IP(net_ip.strNormal() + "/" + mask.strNormal())
                net_str = net_ip_mask.strNormal()
                n["network"] = net_str
            except Exception,ex:
                pass


    def __populateDictionary(self):
        """ retrieve environment information for the
        node and store it in the local dictionary. """

        if self.__dict is not None:
            self.__dict = None



        cpu_attributes = [key_cpu_count,key_cpu_vendor_id,key_cpu_model_name, key_cpu_mhz]
        memory_attributes = [key_memory_total,key_memory_free]
        disk_attributes = [key_disk_file_system,key_disk_size,key_disk_mounted]

        cpu_values = self.node_proxy.exec_cmd( \
                'cat /proc/cpuinfo | grep "processor" | wc -l;' +
                'cat /proc/cpuinfo | grep "vendor*" | head -1 | cut -d\':\' -f2;' +
                'cat /proc/cpuinfo | grep "model na*" | head -1 | cut -d\':\' -f2;' +
                'cat /proc/cpuinfo | grep "cpu MHz*" | head -1 | cut -d\':\' -f2;'\
                )[0].split('\n')[:-1]

        memory_values = self.node_proxy.exec_cmd( \
                'cat /proc/meminfo | grep "Mem*" | cut -d\':\' -f2'\
                )[0].split('\n')[:-1]

        memory_values = [ int(re.search('(\d+)(\s+)(\S+)', v.strip()).group(1))/ 1000 \
                          for v in memory_values ]

        network_values = self.node_proxy.exec_cmd( \
               'ifconfig | awk \'{print $1, $2, $3, $4}\' ;'
               )[0].split("\n   \n")

        # Process networks # TODO : rewrite this.
        raw_network_list = network_values
        network_list = []
        if raw_network_list[0].find('not found') == -1:
            for i in range(len(raw_network_list)):
                split_item = raw_network_list[i].split("UP")
                if cmp('',split_item[0]) !=0 :
                    split_item = split_item[0].split('\n')
                    interface_name = split_item[0].split()[0]
                    ip_addr = ''
                    if cmp('',split_item[1]) !=0:
                        for i in range(1, len(split_item)):
                            ip_addr += split_item[i] + '\n'
                    network_list.append(dict([(key_network_interface_name, interface_name), (key_network_ip, ip_addr)]))


        disk_values = self.node_proxy.exec_cmd( \
                'df -kh -P | grep ^/dev | awk \'{print $1, $2, $6}\''\
                )[0].split('\n')[:-1]

        cpu_dict = dict((cpu_attributes[x],cpu_values[x]) \
                           for x in range(len(cpu_attributes)))
        cpu_dict[key_cpu_count] = int(cpu_dict[key_cpu_count])
        
        memory_dict = dict((memory_attributes[x],memory_values[x]) \
                           for x in range(len(memory_attributes)))


        disk_list = []
        for i in range(len(disk_values)):
          disk_values_split =disk_values[i].split(" ")
          disk_list.append(dict((disk_attributes[x],disk_values_split[x]) \
            for x in range(len(disk_values_split))))

        # os details
        os_values = self.node_proxy.exec_cmd( \
            'uname -r;uname -s; uname -m;'\
                )[0].split('\n')[:-1]
        i = 0
        os_dict = {}
        for name in [key_os_release,key_os_system,key_os_machine]:
            try:
                os_dict[name] = os_values[i]
            except ValueError:
                pass
            i = i+1

        # Augment the information gathered from setup script.
        discovery_file = '/var/cache/convirt/server_info'
        alt_discovery_file = '/var/cache/convirt/server_info'
        if not self.node_proxy.file_exists(discovery_file):
            if self.node_proxy.file_exists(alt_discovery_file):
                discovery_file = alt_discovery_file

        discovered_info = None
        if self.node_proxy.file_exists(discovery_file):
            discovered_info = PyConfig(self, discovery_file)
        
        def_bridge = ""
        if discovered_info:
            os_dict[key_os_distro] = discovered_info["DISTRO"]
            os_dict[key_os_distro_ver] = discovered_info["VER"]
            os_dict[key_os_distro_string] = discovered_info["DISTRO"] + " " + \
                discovered_info["VER"]
            def_bridge = discovered_info["DEFAULT_BRIDGE"]


        # Add some more network specific info.
        cmd = "ls -ld /sys/class/net/*/device | sed -e 's/.*\/sys\/class\/net\///' -e 's/\/device.*//'"
    
        nics = {}
        (output,exit_code) = self.node_proxy.exec_cmd( cmd)
        if output and not exit_code:
            nic_list = output.split('\n')
            for nic in nic_list:
                if nic:
                    nics[nic] = { "name":nic }

        bridges = {}
        cmd = "ls -ld /sys/class/net/*/{bridge,brif/*} | sed -e 's/.*\/sys\/class\/net\///' -e 's/\/bridge.*//' -e 's/\->.*//'"
        (output,exit_code) = self.node_proxy.exec_cmd( cmd)
        if output and not exit_code:
            base_entries = output.split('\n')
            entries=[]
            for e in base_entries:
                if e and e.find('cannot access') < 0:
                    entries.append(e)
            # Populate bridges
            for e in entries:
                if e and e.find("/brif/") == -1:
                    bridges[e] = { "name":e }
            # Populate interfaces for bridges.
            for e in entries:
                if e.find("/brif/") > -1:
                    (br_name,brif,ifname) = e.split("/")
                    if ifname:
                        ifname = ifname.strip()
                    if ifname and (ifname.find("vif") == 0 or \
                            ifname.find("tap")==0):
                        # ignore virtual interface conventions.
                        continue
                    bridge = bridges[br_name]
                    if bridge :
                        interfaces = bridge.get("interfaces")
                        if interfaces is not None:
                            inteterfaces.append(ifname)
                        else:
                            bridge["interfaces"] = [ifname,]

        # from the network info, get ip address and netmask and 
        # construct net details from it.
        # NOTE : There must be a better way of doing this.
        # Look in to ioctl 
        ipv4_re = re.compile("(.*):(.*) (.*):(.*)")
        ipv4_re1 = re.compile("(.*):(.*) (.*):(.*) (.*):(.*)")
        for n_info in network_list:
            ifname = n_info[key_network_interface_name]
            b = bridges.get(ifname)
            n = nics.get(ifname)
            if b or n:
                # we need to process
                ip_info = n_info[key_network_ip]
                if ip_info:
                    ip_list = ip_info.split('\n')
                    for ip_entry in ip_list:
                        m = ipv4_re.match(ip_entry)
                        if not m:
                            ipv4_re1.match(ip_entry)
                        if m :
                            l = len(m.groups())
                            if l > 0 and  m.group(1).find("inet addr") == 0:
                                if b: b["ip"] = m.group(2)
                                if n: n["ip"] = m.group(2)
                            if l > 2 and m.group(3).find("Mask") == 0:
                                if b: b["netmask"] = m.group(4)
                                if n: n["netmask"] = m.group(4)
                            if l > 4 and m.group(5).find("Mask") == 0:
                                if b: b["netmask"] = m.group(6)
                                if n: n["netmask"] = m.group(6)
                                
                                
        # find the network by using ip and netmask
        for n in nics.itervalues():
            self.populate_network(n)
        for b in bridges.itervalues():
            self.populate_network(b)

        #import pprint; pprint.pprint(bridges)
        self.__dict = dict([('cpu_info',cpu_dict),
                            ('memory_info', memory_dict),
                            ('disk_info', disk_list),
                            ('network_info',network_list),
                            ('nic_info',nics),
                            ('bridge_info',bridges),
                            ('os_info', os_dict),
                            ('default_bridge', def_bridge) ])



#########################
# SELF TEST
#########################

if __name__ == '__main__':

    REMOTE_HOST = '192.168.123.155'
    REMOTE_USER = 'root'
    REMOTE_PASSWD = ''

    REMOTE = False 
    
    local_node = ManagedNode(hostname=LOCALHOST)
    if not REMOTE:
        remote_node = local_node  # for local-only testing
    else:        
        remote_node = ManagedNode(hostname=REMOTE_HOST,
                           username=REMOTE_USER,
                           password = REMOTE_PASSWD,
                           isRemote = True)    

    #
    # lvm_proxy  tests
    #

    print '\nManagedHost.LVMProxy interface test STARTING'

    print 'Local Node LVM enabled?:' , local_node.isLVMEnabled
    print 'Remote Node LVM enabled?:' , remote_node.isLVMEnabled
    
    for lvm in (local_node.lvm_proxy,remote_node.lvm_proxy):
        if lvm:
            vgs =  lvm.listVolumeGroups()
            for g in vgs:
                print g
                print lvm.listLogicalVolumes(g)
                print '\t Creating test LV'
                lvm.createLogicalVolume('selfTest',0.1,g)
                print '\t Deleting test LV'
                lvm.removeLogicalVolume('selfTest',g)            
    print 'ManagedHost.LVMPRoxy interface test COMPLETED\n'
   

    #
    # environment tests
    #
    print '\nManagedHost.Environment access  test STARTING'

    for nd in (local_node, remote_node):
        print "\n%s's environment:" % nd.hostname
        print 'Available attributes:', nd.environ.keys()
        for attr in nd.environ:
            print attr, ':',nd.environ[attr]

    print 'ManagedHost.Environment access  test COMPLETED\n'

    network_values = remote_node.node_proxy.exec_cmd( \
               'ifconfig | awk \'{print $1, $2, $3}\' ;'
               )[0].split("\n  \n")


    print network_values
    for item in network_values:
      print item.split("UP")


    disk_values = remote_node.node_proxy.exec_cmd( \
               'df  -kh -P | grep ^/dev')[0].split('\n')
    for item in disk_values:
      print item

    disk_values = ('Filesystem            Size  Used Avail Use% Mounted on\n'
                  '/dev/mapper/VG_Fedora-LV2\n29G   19G  8.2G  70% /\n'
                  '/dev/sda1             3.8G  100M  3.5G   3% /boot\n'
                  'tmpfs                 1.9G     0  1.9G   0% \n'
                  'dev/shm\n/dev/mapper/VG_PL1-home\n'
                  '20G   16G  3.1G  84% /mnt/home\n'
                  'dev/mapper/VG_PL1-vm_disks\n'
                  '40G   23G   15G  61% /vm_disks\n'
                  'dev/mapper/VG_PL1-images\n'
                  '79G   68G  7.1G  91% /mnt/images')

    disk_values = disk_values.splitlines()
    print disk_values
    sys.exit(0)
    




    
    
    


