#!/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
# 
#
#

import ConfigParser, subprocess, platform
import sys, os, os.path, socket, types, tempfile, re, glob

import shutil, urllib,urllib2,urlparse
import convirt.core.utils.constants
from convirt.core.utils.NodeProxy import Node
import time, datetime
import string
import random

import traceback
import xml.dom.minidom
from xml.dom.minidom import Node
import webbrowser


try:
    import hashlib
    md5_constructor=hashlib.md5
except ImportError,e:
    import md5
    md5_constructor=md5.new


# ease of use
constants = convirt.core.utils.constants

class dynamic_map(dict):
    def __init__(self, another_dict=None):
        if another_dict:
            dict.__init__(self, another_dict)
        else:
            dict.__init__(self)

    def __getattr__(self, name):
        if self.has_key(name):
            return self[name]
        return None

    def __getitem__(self, name):
        if dict.has_key(self,name):
            return dict.__getitem__(self, name)
        return None


    def __setattr__(self,name,value):
        self[name] = value

# allows values to be functions or method and eval them 
# useful for generating multiple nic addresses in template substitution.
class fv_map(dict):
    def __init__(self):
        dict.__init__(self)

    def __getitem__(self, name):
        if dict.has_key(self,name):
            value=dict.__getitem__(self, name)
            if isinstance(value, types.MethodType) or \
               isinstance(value, types.FunctionType) :
                value = value() # evaluate it
                return value

        return dict.__getitem__(self, name)


        
class XMConfig(ConfigParser.SafeConfigParser):
    """ ConVirt's configuration management class. """

    # the default list of sections in the config
    DEFAULT = 'DEFAULT'
    ENV = 'ENVIRONMENT'
    PATHS = 'PATHS'
    APP_DATA = 'APPLICATION DATA'
    CLIENT_CONFIG = 'CLIENT CONFIGURATION'
    IMAGE_STORE = 'IMAGE STORE'
    DEFAULT_REMOTE_FILE = '/etc/convirt.conf'
    
    def __init__(self, node, searchfiles = None, create_file = None):
        """Looks for convirt.conf in current, user home and /etc directories. If
        it is not found, seeds one in the local directory."""

        ConfigParser.SafeConfigParser.__init__(self)
        self.node = node
        self.std_sections = [self.ENV,self.PATHS,self.APP_DATA,
                             self.CLIENT_CONFIG]
        
        if searchfiles is None:
            # no search path give. apply heuristics
            if not self.node.isRemote:
                # localhost
                filelist = [x for x in [os.path.join(os.getcwd(),'convirt.conf'),
                                        os.path.expanduser('~/.convirt/convirt.conf'),
                                        '/etc/convirt.conf'] if self.node.file_exists(x)]
            else:
                # remote host
                if self.node.file_exists(self.DEFAULT_REMOTE_FILE):
                    filelist =  [self.DEFAULT_REMOTE_FILE]
                else:
                    filelist = []
        else:
            # search path specified
            filelist = [x for x in searchfiles if self.node.file_exists(x)]

        if len(filelist) < 1:
            base_dir = None
            print 'No Configuration File Found'
            if create_file is None:
                # no default creation file is specified. use heuristics
                if not self.node.isRemote:
                    # localhost. create in cwd
                    print 'Creating default convirt.conf in current directory'            
                    self.configFile = os.path.join(os.getcwd(), 'convirt.conf')
                    base_dir = os.getcwd()
                else:
                    # remote host. create in default remote location
                    print 'Creating default convirt.conf at %s:%s' \
                          % (self.node.hostname,self.DEFAULT_REMOTE_FILE)
                    self.configFile = self.DEFAULT_REMOTE_FILE                
            else:
                # default creation location is specified
                print 'Creating default convirt.conf at %s:%s' \
                      % (self.node.hostname,create_file)                
                self.configFile = create_file            

            # new file created, populate w/ default entries
            self.__createDefaultEntries(base_dir)
            self.__commit()
            
        else:            
            # config file(s) found. choose a writable file,
            # otherwise create a new default file in the user's
            # home directory (only for localhost)
            self.configFile = None
            for f in filelist:
                try:
                    if self.node.file_is_writable(f):
                        # file is writable
                        if self.__validateFile(f):
                            # file is valid
                            self.configFile = f
                            print 'Valid, writable configuration found, using %s' % f
                        else:
                            # file is writable but not valid
                            # back it up (a new one will get created)                            
                            self.node.rename(f,f+'.bak')
                            print 'Old configuration found. Creating backup: %s.bak' % f                            

                        break
                    else:
                        print 'Confguration File not writable, skipping: %s' % f
                except IOError:
                    print 'Confguration File accessable, skipping: %s' % f
                    continue
                    
            if self.configFile is None:
                # no writable config file found
                print "No writable configuration found ... "
                if not self.node.isRemote:
                    # localhost
                    base_dir = os.path.expanduser('~/.convirt/')
                    if not os.path.exists(base_dir):
                        os.mkdir(base_dir)
                    self.configFile = os.path.join(base_dir, "convirt.conf")
                    print "\t Creating %s" % self.configFile                    
                    self.__createDefaultEntries(base_dir)
                    self.__commit()
                else:
                    # TBD: what to do in the remote case
                    raise Exception('No writable configuration found on remote host: %s' % self.node.hostname)
                
            #self.configFile = filelist[0]
            fp = self.node.open(self.configFile)
            self.readfp(fp)
            fp.close()

        # What is this doing here ? commenting out.
        #self.__commit()


    def __createDefaultEntries(self, base_dir = None):

        # cleanup first
        for s in self.sections():
            self.remove_section(s)
            
        # add the standard sections
        for s in self.std_sections:
                self.add_section(s)                


        # seed the defaults
        self.set(self.DEFAULT,constants.prop_default_computed_options,
                 "['arch', 'arch_libdir', 'device_model']")

        # paths
        if base_dir :
            base=base_dir
            log_dir = os.path.join(base,"log")
        else:
            base='/var/cache/convirt'
            log_dir = '/var/log/convirt'

        i_store = os.path.join(base, 'image_store')
        a_store = os.path.join(base, 'appliance_store')
        updates_file = os.path.join(base, 'updates.xml')
        
        self.set(self.PATHS, constants.prop_snapshots_dir, "/var/cache/convirt/vm_snapshots")
        self.set(self.PATHS, constants.prop_snapshot_file_ext, '.snapshot.xm')
        self.set(self.PATHS, constants.prop_cache_dir, "/var/cache/convirt")
        self.set(self.PATHS, constants.prop_exec_path, '$PATH:/usr/sbin')
        self.set(self.PATHS, constants.prop_image_store, i_store)
        self.set(self.PATHS, constants.prop_appliance_store, a_store)
        self.set(self.PATHS, constants.prop_updates_file, updates_file)

        self.set(self.PATHS, constants.prop_log_dir,log_dir)

        #self.set(self.CLIENT_CONFIG, constants.prop_default_xen_protocol, 'tcp')
        self.set(self.CLIENT_CONFIG, constants.prop_default_ssh_port, '22')
        #self.set(self.CLIENT_CONFIG, constants.prop_default_xen_port, '8006')
        
        self.set(self.CLIENT_CONFIG, constants.prop_enable_log, 'True')
        self.set(self.CLIENT_CONFIG, constants.prop_log_file, 'convirt.log')
        
        self.set(self.CLIENT_CONFIG,
                 constants.prop_enable_paramiko_log, 'False')
        self.set(self.CLIENT_CONFIG,
                 constants.prop_paramiko_log_file, 'paramiko.log')
        self.set(self.CLIENT_CONFIG,
                     constants.prop_http_proxy, '')

        self.set(self.CLIENT_CONFIG,
                     constants.prop_chk_updates_on_startup,'True')

        #self.set(self.CLIENT_CONFIG,
        #             constants.prop_vncviewer_options,'-PreferredEncoding=hextile')

        
        #self.set(self.CLIENT_CONFIG, constants.prop_browser, '/usr/bin/yelp')
        #self.set(self.PATHS, constants.prop_staging_location, '')
        #self.set(self.PATHS, constants.prop_kernel, '')
        #self.set(self.PATHS, constants.prop_ramdisk, '')
        #self.set(self.ENV, constants.prop_lvm, 'True')
        #self.__commit()

        # set localhost specific properties
        if not self.node.isRemote:
            #self.add_section(constants.LOCALHOST)
            self.set(self.ENV,constants.prop_dom0_kernel,platform.release())
        
        
    def __commit(self):
        outfile = self.node.open(self.configFile,'w')
        self.write(outfile)
        outfile.close()

    def __validateFile(self, filename):
        temp = ConfigParser.ConfigParser()
        fp = self.node.open(filename)
        temp.readfp(fp)
        fp.close()
        for s in self.std_sections:
            if not temp.has_section(s):
                print "section " + s + " not found"
                return False
        return True
    

    def getDefault(self, option):
        """ retrieve a default option/key value """
        return self.get(self.DEFAULT, option)


    def get(self, section, option):

        # does the option exist? return None if not
        if option is None: return None
        if not self.has_option(section, option): return None

        # option is available in the config. get it.
        retVal = ConfigParser.SafeConfigParser.get(self, section, option)
        
        # check if the value is blank. if so, return None
        # otherwise, return the value.
        if retVal == None:
            return retVal
        
        if not retVal.strip():
            return None
        else:
            return retVal
        

    def setDefault(self, option, value):
        """set the default for option to value.
        POSTCONDITION: option, value pair has been set in the default
        configuration, and committed to disk"""
        if option is not None:
            self.set(self.DEFAULT, option, value)


    def set(self, section, option, value):
        ConfigParser.SafeConfigParser.set(self, section, option, value)
        self.__commit()

        
    def getHostProperty(self, option, hostname=constants.LOCALHOST):
        """ retrieve the value for 'option' for 'hostname',
        'None', if the option is not set"""

        # does the option exist? return None if not
        if not self.has_option(hostname, option): return None

        # option is available in the config. get it.
        retVal = self.get(hostname, option)
        
        # check if the value is blank. if so, return None
        # otherwise, return the value.
        if retVal == None:
            return retVal
        
        if not retVal.strip():
            return None
        else:
            return retVal

    def getHostProperties(self, hostname=constants.LOCALHOST):
        if not self.has_section(hostname): return None
        return dict(self.items(hostname))

    def setHostProperties(self, options, hostname=constants.LOCALHOST):
        """ set config 'option' to 'value' for 'hostname'.
        If the a config section for 'hostname' doesn't exit,
        one is created."""
        
        if not self.has_section(hostname): self.add_section(hostname)
        for option in options:
            self.set(hostname, option, options[option])
        self.__commit()


    def setHostProperty(self, option, value, hostname=constants.LOCALHOST):
        """ set config 'option' to 'value' for 'hostname'.
        If the a config section for 'hostname' doesn't exit,
        one is created."""
        
        if not self.has_section(hostname): self.add_section(hostname)
        self.set(hostname, option, value)
        self.__commit()

    def removeHost(self, hostname):
        """ remove 'hostname' from the list of configured hosts.
        The configuration is deleted from both memory and filesystem"""

        if self.has_section(hostname):
            self.remove_section(hostname)
            self.__commit()

    def getHosts(self):
        """ return the list configured hosts"""
        hosts=[]
        for sec in self.sections():
            if sec in self.std_sections or sec == self.IMAGE_STORE:
                continue
            else:
                hosts.append(sec)
        #hosts = set(self.sections())-set(self.std_sections)
        #hosts = set(hosts) - set(self.IMAGE_STORE)
        return hosts


    def getGroups(self):
        groups = self.get(self.APP_DATA, constants.prop_groups)
        if groups is not None:
            return eval(groups)
        return {}

    def saveGroups(self, group_list):
        g_list_map = {}


        for g in group_list:
            g_map = {} # initialize it for each group hence within the loop.
            g_map["name"] = group_list[g].name
            g_map["node_list"] = group_list[g].getNodeNames()
            g_map["settings"] = group_list[g].getGroupVars()
            g_list_map[group_list[g].name] = g_map       
                 
        self.set(self.APP_DATA, constants.prop_groups, str(g_list_map))
        self.__commit()
        
    
class LVMProxy:
    """A thin, (os-dependent) wrapper around the shell's LVM
    (Logical Volume Management) verbs"""
    # TODO: move this class to an OSD module
    
    @classmethod
    def isLVMEnabled(cls, node_proxy, exec_path=''):
        retVal = True
        if node_proxy.exec_cmd('vgs 2> /dev/null',exec_path)[1]:
            retVal = False
        return retVal
    
        
    def __init__(self, node_proxy, exec_path=''):
        """ The constructor simply checks if LVM services are available
        for use via the shell at the specified 'node'.
        RAISES: OSError"""
        self.node = node_proxy
        self.exec_path = exec_path

        if node_proxy.exec_cmd('vgs 2> /dev/null',exec_path)[1]:
            raise OSError("LVM facilities not found")


    def listVolumeGroups(self):
        """ Returns the list of existing Volume Groups"""
        try:
            vglist = self.node.exec_cmd('vgs -o vg_name --noheadings', self.exec_path)[0]
            return [s.strip() for s in vglist.splitlines()]
        except OSError, err:
            print err
            return None

    def listLogicalVolumes(self, vgname):
        """ Returns the list of Logical Volumes in a Volume Group"""
        try:            
            lvlist = self.node.exec_cmd('lvs -o lv_name --noheadings '+ vgname,
                                        self.exec_path)[0]
            return [s.strip() for s in lvlist.splitlines()]
        except OSError, err:
            print err
            return None

    def createLogicalVolume(self, lvname, lvsize, vgname):
        """ Create a new LV with in the specified Volume Group.
        'lvsize' denotes size in number of megabytes.
        RETURNS: True on sucees
        RAISES: OSError"""
        error,retcode = self.node.exec_cmd('lvcreate %s -L %sM -n %s'%(vgname,lvsize,lvname),
                                           self.exec_path)
        if retcode:
            raise OSError(error.strip('\n'))
        else:
            return True
        
                
    def removeLogicalVolume(self, lvname, vgname=None):
        """ Remove the logical volume 'lvname' from the
        Volume Group 'vgname'. If the latter is not specified,
        'lvname' is treated as a fully specified path
        RETURNS: True on success
        RAISES: OSError"""
        if (vgname):
            lvpath = vgname + '/' + lvname
        else:
            lvpath = lvname
            
        error,retcode = self.node.exec_cmd('lvremove -f %s'% lvpath, self.exec_path)
        if retcode:
            raise OSError(error.strip('\n'))
        else:
            return True




from threading import Thread

class Poller(Thread):
    """ A simple poller class representing a thread that wakes
    up at a specified interval and invokes a callback function"""

    def __init__(self, freq, callback, args=[], kwargs={}, max_polls = None):
        Thread.__init__(self)
        self.setDaemon(True)
        self.frequency = freq
        self.callback = callback
        self.args = args
        self.kwargs = kwargs
        self.done = False
        self.remainder = max_polls

    def run(self):
        while not self.done:
            self.callback(*self.args,**self.kwargs)
            time.sleep(self.frequency)
            if self.remainder is not None:
                self.remainder -= 1
                if self.remainder < 0:
                    self.done = True

    def stop(self):
        self.done = True


### TODO : Remove dependence on managed_node
class PyConfig:
    """
        Class represents python based config file.
        Also, supports instantiating a templatized config file
    """
    default_computed_options = []
    COMPUTED_OPTIONS = "computed_options"
    CUSTOMIZABLE_OPTIONS = "customizable_options"
    def __init__(self,
                 managed_node = None,   # if none, assume localnode
                 filename = None,       # config file
                 signature = None):     # signature to be written as first line

        if managed_node is not None and isinstance(managed_node, str):
            raise Exception("Wrong param to PyConfig.")
        
        """ filename and file open function to be used """
        self.filename = filename
        self.managed_node = managed_node
        self.lines = []
        self.options = {}
        self.signature = signature
        

        if self.filename is not None:
            (self.lines, self.options) = self.read_conf()

    @classmethod        
    def set_computed_options(cls, computed):
        cls.default_computed_options = computed

    def get_computed_options(self):
        c = []
        if self.default_computed_options is not None:
            c = self.default_computed_options
            
        if self.options.has_key(self.COMPUTED_OPTIONS):
            specific_computed_options = self[self.COMPUTED_OPTIONS]
        else:
            specific_computed_options = None
            
        if specific_computed_options is not None and \
               type(specific_computed_options) == types.ListType:
            for o in specific_computed_options:
                c.append(o)

        if self.COMPUTED_OPTIONS not in c :
            c.append(self.COMPUTED_OPTIONS)
        return c

    def get_customizable_options(self):
        customizable_options = None
        if self.options.has_key(self.CUSTOMIZABLE_OPTIONS):
            customizable_options = self[self.CUSTOMIZABLE_OPTIONS]
        else:
            customizable_options = self.options.keys()

        if customizable_options is not None and \
               self.CUSTOMIZABLE_OPTIONS in customizable_options :
            customizable_options.remove(self.CUSTOMIZABLE_OPTIONS)
            
        return customizable_options

    # set the name of the file associated with the config.
    def set_filename(self, filename):
        """
        set the filename associated with this config.
        subsequent write would use this file name.
        """
        self.filename = filename

    # Allow for changing the managed node.
    # useful in reading a template and then writing modified, instantiated
    # file on a managed node.
    def set_managed_node(self, node):
        self.managed_node = node
        
    def read_conf(self, init_glob=None, init_locs=None):

        if init_glob is not None:
            globs = init_glob
        else:
            globs = {}

        if init_locs is not None:
            locs = init_locs
        else:
            locs  = {}
            
        lines = []
        options = {}
        try:
            if self.managed_node is None:
                f = open(self.filename)
            else:
                f = self.managed_node.node_proxy.open(self.filename)
            lines = f.readlines()
            f.close()
            if len(lines) > 0:
                cmd = "\n".join(lines)
                exec cmd in globs, locs
        except:
            raise
        # Extract the values set by the script and set the corresponding
        # options, if not set on the command line.
        vtypes = [ types.StringType,
                   types.ListType,
                   types.IntType,
                   types.FloatType,
                   types.DictType
                   ]
        for (k, v) in locs.items():
            if not(type(v) in vtypes): continue
            options[k]=v

        return (lines,options)
    
    def write(self, full_edit=False):
        """Writes the settings out to the filename specified during
        initialization"""

        dir = os.path.dirname(self.filename)
        if self.managed_node is None:
            if not os.path.exists(dir):
                os.makedirs(dir)
            outfile = open(self.filename, 'w')
        else:
            if not self.managed_node.node_proxy.file_exists(dir):
                mkdir2(self.managed_node, dir)
            outfile = self.managed_node.node_proxy.open(self.filename, 'w')
            
        if self.signature is not None:
            outfile.write(self.signature)
        
        # Simple write
        if self.lines is None or len(self.lines) == 0:
            for name, value in self.options.iteritems():
                outfile.write("%s = %s\n" % (name, repr(value)))
        else:
            # drive the writing through lines read.
            updated = []
            for line in self.lines:
                if self.signature is not None and \
                       line.find(self.signature) >= 0:
                    continue
                if line[0] == '#' or line[0] == '\n' or \
                        line[0].isspace() or line.strip().endswith(':'):
                    outfile.write(line)
                else:
                    ndx = line.find("=")
                    if ndx > -1:
                        token = line[0:ndx]
                        token = token.strip()
                        if self.options.has_key(token):
                            if token not in self.get_computed_options() and \
                               (token != self.CUSTOMIZABLE_OPTIONS or full_edit) :
                                value = self.options[token]
                                outfile.write("%s=%s\n" % (token, repr(value)))
                                updated.append(token)
                            else:
                                #print "writing computed Token X:" , line
                                if token != self.COMPUTED_OPTIONS:
                                    outfile.write(line)
                        else:
                            if token in self.get_computed_options():
                                outfile.write(line)
                            else:
                                #print "Valid token but removed" , line
                                pass
                    else:
                        #print "writing default Y:" , line
                        outfile.write(line)

            # Add new tokens added
            for name, value in self.options.iteritems():
                if name not in updated and \
                       name not in self.get_computed_options():
                    outfile.write("%s=%s\n" % (name, repr(value)))

        outfile.close()

    def instantiate_config(self, value_map):
        
        # do this so that substitution happens properly
        # we may have to revisit, map interface of PyConfig
        if isinstance(value_map, PyConfig):
            value_map = value_map.options

        # instantiate the filename
        fname = string.Template(self.filename)
        new_val = fname.safe_substitute(value_map)
        self.set_filename(new_val)
        
        for key in self.options.keys():
            value = self.options[key]
            if value is not None:
                if type(value) is types.StringType:
                    template_str = string.Template(value)
                    self.options[key] = template_str.safe_substitute(value_map)
                elif type(value) is types.ListType:
                    new_list = []
                    for v in value:
                        if type(v) is types.StringType:
                            template_str = string.Template(v)
                            new_list.append(template_str.safe_substitute(value_map))
                                                
                        else:
                            new_list.append(v)
                    self.options[key] = new_list
                    #print "old %s, new %s", (value, self.options[key])
                            
                    

    def save(self, filename):
        """ save the current state to a file"""
        self.filename = filename
        self.write()

    def get(self, name):
        return self[name]

    #access config as hastable 
    def __getitem__(self, name):
        if self.options.has_key(name):
            return self.options[name]
        else:
            attrib = getattr(self,name, None)
            if attrib is not None:
                return attrib
            else:
                return None

    def __setitem__(self, name, item):
        self.options[name] = item

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

    def iteritems(self):
        return self.options.iteritems()

    def has_key(self, key):
        return self.options.has_key(key)
    
    def keys(self):
        return self.options.keys()
    # debugging dump
    def dump(self):
        if self.filename is not None:
            print self.filename
        for name, value in self.options.iteritems():
            print "%s = %s" % (name, repr(value))

    def __delitem__(self, key):
        if self.has_key(key):
            del self.options[key]

# generic worker to be used to communicate with main thread
# using idle_add
import threading
import gobject
from threading import Thread
class Worker(Thread):
    def __init__(self, fn, succ, fail,progress=None):
        Thread.__init__(self)
        self.setDaemon(True)
        self.fn = fn
        self.succ = succ
        self.progress = progress
        self.fail = fail
        
    def run(self):
        try:
            ret = self.fn()
        except Exception, ex:
            traceback.print_exc()
            if self.fail:
                gobject.idle_add(self.fail,ex)
        else:
            if self.succ:
                gobject.idle_add(self.succ, ret)


# class to download the updates.
# can be used by UI to display the updates.
class UpdatesMgr:
    update_url = "http://www.convirture.com/updates/updates.xml"
    updates_file = "/var/cache/convirt/updates.xml"
    
    def __init__(self, config):
        self.config = config
        self.url = self.config.get(XMConfig.PATHS, constants.prop_updates_url)
        if not self.url:
            self.url = UpdatesMgr.update_url
            
            
        self.updates_loc = self.config.get(XMConfig.PATHS,
                                       constants.prop_updates_file)
        if not self.updates_file:
            self.updates_file = UpdatesMgr.updates_file


    def fetch_updates(self):
        update_items = []

        try:
            # file is not writable..lets create a tmp file
            if not os.access(self.updates_file,os.W_OK):
                (t_handle, t_name) = tempfile.mkstemp(prefix="updates.xml")
                self.updates_file = t_name
                os.close(t_handle) # Use the name, close the handle.
                
            fetch_isp(self.url, self.updates_file, "/xml")
        except Exception, ex:
            print "Error fetching updates ", ex
            try:
                if os.path.exists(self.updates_file):
                    os.remove(self.updates_file)
            except:
                pass
            return update_items


        if os.path.exists(self.updates_file):
            updates_dom = xml.dom.minidom.parse(self.updates_file)
            for entry in updates_dom.getElementsByTagName("entry"):
                info = {}
                for text in ("title","link","description", "pubDate",
                             "product_id", "product_version","platform"):
                    info[text] = getText(entry, text)
                populate_node(info,entry,"link",
                          { "link" : "href"})

                update_items.append(info)

        # cleanup the file
        try:
            if os.path.exists(self.updates_file):
                os.remove(self.updates_file)
        except:
            pass
        
        return update_items


    # every time it is called it gets new updates from last time
    # it was called.
    def get_new_updates(self):
        new_updates = []
        updates = self.fetch_updates()
        str_r_date = self.config.get(XMConfig.APP_DATA,
                                   constants.prop_ref_update_time)
        if str_r_date:
            p_r_date = time.strptime(str_r_date, "%Y-%m-%d %H:%M:%S")
            r_date = datetime.datetime(*p_r_date[0:5])
        else:
            r_date = datetime.datetime(2000, 1, 1)

        max_dt = r_date
        for update in updates:
            str_p_dt = str(update["pubDate"])
            if str_p_dt:
                p_dt = time.strptime(str_p_dt, "%Y-%m-%d %H:%M:%S")
                dt = datetime.datetime(*p_dt[0:5])
                if dt > r_date :
                    new_updates.append(update)
                    if dt > max_dt:
                        max_dt = dt
                        

        if max_dt > r_date:
            str_max_dt = max_dt.strftime("%Y-%m-%d %H:%M:%S")
            self.config.set(XMConfig.APP_DATA,
                            constants.prop_ref_update_time,
                            str_max_dt)
        return new_updates
    
#
# copy directory from local filesystem to remote machine, dest.
# src : source file or directory
# dest_node :node on which the files/directory needs to be copied
# dest_dir : destination directory under which src file/dir would be
#            copied
# dest_name : name of file/directory on the destination node.
# hashexcludes: list of the files to be excluded from generating hash.
#
def copyToRemote(src,dest_node,dest_dir, dest_name = None, hashexcludes=[]):
    srcFileName = os.path.basename(src)
    if srcFileName and srcFileName[0] == "." :
        print "skipping hidden file ", src
        return

    if not os.path.exists(src):
        raise "%s does not exist." % src

    
    hashFile = src + ".hash"
    # If the selected file is already a hashfile, skip it.
    if os.path.isfile(src) and not src.endswith(".hash") :        
        mkdir2(dest_node,dest_dir)
        dest_hashFile = os.path.join(dest_dir, os.path.basename(hashFile))
        if dest_name is not None:
            dest = os.path.join(dest_dir, dest_name)
        else:
            dest = os.path.join(dest_dir, os.path.basename(src))

        copyFile = False
        # Check for hashfile
        if srcFileName not in hashexcludes:
            if not os.path.exists(hashFile) : 
                generateHashFile(src)
            else:
                updateHashFile(src) # update the hash if required.

            localhashVal  = None
            remotehashVal = None
            if os.path.exists(hashFile):
                # read local hash
                try :
                    lhf = open(hashFile)
                    localhashVal =  lhf.read()         
                finally:
                    lhf.close()
            
                #only if local hashfile exists, check for remote hashfile.
                if dest_node.node_proxy.file_exists(dest_hashFile):
                    try :
                        rhf = dest_node.node_proxy.open(dest_hashFile)
                        remotehashVal = rhf.read()
                    finally:
                        rhf.close()

            else:
                raise Exception("Hash file not found." + hashFile)

            if not compareHash(remotehashVal, localhashVal):
                copyFile = True

        else:
            copyFile = True # file is in exclude hash
    
        if copyFile:  # either in exclude hash or hash mismatch
            print "copying ", src
            mode = os.lstat(src).st_mode
            dest_node.node_proxy.put(src, dest)
            dest_node.node_proxy.chmod(dest, mode)

            if srcFileName not in hashexcludes: # file needs a hash
                # copy the hash file too.
                print "copying hash too", hashFile
                mode = os.lstat(hashFile).st_mode
                dest_node.node_proxy.put(hashFile, dest_hashFile)
                dest_node.node_proxy.chmod(dest_hashFile, mode)
            
    elif os.path.isdir(src): # directory handling
        mkdir2(dest_node,dest_dir)
        if dest_name is not None:
            dest = os.path.join(dest_dir, dest_name)
        else:
            (dirname, basename) = os.path.split(src)
            dest = os.path.join(dest_dir,basename)
            mkdir2(dest_node, dest)
            
        for entry in os.listdir(src):
            s = os.path.join(src, entry)
            copyToRemote(s, dest_node, dest, hashexcludes=hashexcludes)


#Generates hashfile for a given filename.
#The extension of the hashfilename is ".hash".
#Hashvalue = Last modification time + "|" + hexvalue.
def generateHashFile(filename):
	f = file(filename,'rb')
	fw = file(filename + ".hash", 'wb')
	m = md5_constructor()
 
	readBytes = 1024 # read 1024 bytes per time
	try:
	    while (readBytes):
		    readString = f.read(readBytes)
		    m.update(readString)
		    readBytes = len(readString)
	finally:
	    f.close()
	
	try:
	    fw.write(str(os.stat(filename).st_mtime))
	    fw.write('|')
	    fw.write(m.hexdigest())
	
	finally:
	    fw.close()

# The method updates the hasfile if it exists.
# Check if the modification time is same in the hashfile.
# If not look for hash value if it is the same.
# If not generate a new hash value and add modification time.
def updateHashFile(filename):
    fhash = file(filename + ".hash", 'rb')
    m = md5_constructor()

    try:
        readHash = fhash.read()
    finally:
        fhash.close()    
    
    hashline = readHash.split("|")

    hashVal = m.hexdigest()
    hashTime = os.stat(filename).st_mtime
    generate = False
    
    # Check for the modification time 
    if hashline[0] == str(hashTime) and  hashline[1] == hashVal:   
        return 
    else:
        f = file(filename,'rb')
        readBytes = 1024; # read 1024 bytes per time
        try:
            while (readBytes):
                readString = f.read(readBytes)
                m.update(readString)
                readBytes = len(readString)
        finally:
            f.close()

        try:
            fhash = file(filename + ".hash", 'wb')
            fhash.write(str(hashTime))
            fhash.write('|')
            fhash.write(hashVal)
        finally:
            fhash.close()


#Compares 2 hashvalues.
#First part of the hashvalue is the last modification time of the file.
#Second part is the hexvalue.
# for comparison, we can simply use the string compare.
def compareHash(remoteHash, localHash):
    return remoteHash == localHash


# make n level directory.
def mkdir2(dest_node, dir):
    root = dir
    list = []
    while not dest_node.node_proxy.file_exists(root) and root is not '/':
        list.insert(0,root)
        (root, subdir) = os.path.split(root)

    for d in list:
        dest_node.node_proxy.mkdir(d)
    
    

   
def fetchImage(src, dest):
    """ Copies 'src' to 'dest'. 'src' can be an http or ftp URL
    or a filesystem location. dest must be a fully qualified
    filename."""
    
    print "Fetching: "+src
    if src.startswith("http://") or src.startswith("ftp://"):
        # newtwork fetch
        urllib.urlretrieve(src,dest)
    else:
        # filesystem fetch
        shutil.copyfile(src, dest)



## New Code
def search_tree(tree, key):
    """Retrieve a value from a tree"""

    if tree == None or key == None or len(tree) < 1:
        return None

    # if list has a name/ctx
    if type(tree[0]) is str:
        if key == tree[0]:
            if len(tree) > 2:
                return tree[1:]
            else:
                return tree[1]
        l = tree[1:]
    else:
        l = tree
        
    for elem in l:
        #print "processing ..", elem[0], key
        if type(elem) is list:
            if len(elem) > 0 and elem[0] == key:
                if len(elem) > 2:
                    #print "returning [[v1],[v2],..] from NV"
                    return elem[1:]
                else:
                    #print "returning V from NV"
                    return elem[1]
            elif len(elem) >=2 and type(elem[1]) is list:
                if len(elem) == 2:
                    #print "recursing with [V] " # , elem[1]
                    v = search_tree(elem[1],key)
                    if v is not None:
                        return v
                else:
                    #print "recursing with [[v1],[v2],...]" #, elem[1:]
                    v = search_tree(elem[1:],key)
                    if v is not None:
                        return v
    return None

def is_host_remote(host):
    host_names = []
    try:
        (host_name, host_aliases,host_addrs) = socket.gethostbyaddr(host)
        host_names.append(host_name)
        host_names = host_aliases + host_addrs
    except:
        host_names.append(host)

    return len(set(l_names).intersection(set(host_names))) == 0


# we will be using this at few places. better to keep it in a separate
# function
def read_python_conf(conf_file):
    """ reads a conf file in python format and returns conf as hash table"""
    glob = {}
    loc  = {}
    if not os.path.exists(conf_file):
        print "conf file not found :" + conf_file
        return None
    execfile(conf_file, glob, loc)
    
    # filter out everything other than simple values and lists
    vtypes = [ types.StringType,
               types.ListType,
               types.IntType,
               types.FloatType,
               types.DictType
               ]

    for (k, v) in loc.items():
        if type(v) not in vtypes:
            del loc[k]

    return loc


# need to go in common place

def guess_value(value):
    # check if it is a number
    if value is None or value == '':
        return value

    if value.isdigit():
        return int(value)

    # check if float
    parts = value.split(".")
    if len(parts) == 2:
        if parts[0].isdigit() and parts[1].isdigit():
            return float(value)

    # check if it is a list
    if value[0] in  ['[' ,'{']:
        g = {}
        l = {}
        cmd = "x = " + value
        exec cmd in g, l
        return l['x']

    # assume it is a string
    return value

## fetch stuff uses urllib2. throws exception on 404 etc.
## this same routine is in the common/functions used for provisioning
## Any enhancement/fixes should be made over there as well.
def fetch_url(url, dest, proxies=None,
              reporthook=None,data=None,chunk=2048):
    if reporthook:
        raise Exception("reporthook not supported yet")
    
    resp = None
    df = None
    ret = (None, None)
    try:
        if proxies:
            proxy_support = urllib2.ProxyHandler(proxies)
            opener = urllib2.build_opener(proxy_support)
        else:
            opener = urllib2.build_opener()

        req = urllib2.Request(url)
        req.add_header("User-Agent", constants.fox_header)
                       
        if data:
            resp = opener.open(req, data)
        else:
            resp = opener.open(req)

        ret = resp.geturl(),resp.info()
        df = open(dest, "wb")
        data = resp.read(chunk)
        while data:
            try:
                df.write(data)
                data = resp.read(chunk)
            except socket.error, e:
                if (type(e.args) is tuple) and (len(e.args) > 0) and \
                       ((e.args[0] == errno.EAGAIN or e.args[0] == 4)):
                    continue
                else:
                    raise
    finally:
        if df:
            df.close()
        if resp:
            resp.close()

    return ret

# ISP seems to cache and return a http redirect which we can not
# parse. So just retry.
def fetch_isp(url, dest, content_type):
    retries = 2
    while retries > 0:
        (u, headers) = fetch_url(url, dest)
        retries = retries - 1;
        type =  headers.get("Content-Type")
        if type and type.lower().find(content_type) < 0  :
            print "Retrying ..", type
            continue
        else:
            break

    if type is None:
        raise Exception("Could not fetch %s. Content-Type is None", u)

    if type.lower().find(content_type) < 0 :
        raise Exception("Could not fetch %s. Wrong content type: "+ type )


# Go through firefox setup and try to guess the proxy values.
def guess_proxy_setup():
    moz_pref_path = os.path.expanduser("~/.mozilla/firefox")
    files = glob.glob(moz_pref_path  + "/*/prefs.js")
    if len(files) > 0:
        pref_file = files[0]
        print pref_file
    else:
        return (None, None)

    prefs = open(pref_file, "r").read().split("\n")

    # get all user_pref lines
    #prefs = re.findall('user_pref("network\.proxy\.(.*)",(.*));', prefs )
    proxy_prefs = {}
    for pref_line in prefs:
        pref = re.findall('user_pref\("network.proxy.(.*)", ?(.*)\);', pref_line )
        if len(pref) > 0 and len(pref[0]) > 1:
            k = pref[0][0]
            v = pref[0][1]
            if v[0] == '"':
                v = v[1:]
            if v[-1] == '"':
                v = v[0:-1]
            proxy_prefs[k] = v

    if proxy_prefs.has_key("type"):
        if proxy_prefs["type"] != "1": # 1 means manual setup of of proxy
            print "type is ", type , " other than manual proxy. None, None"
            return (None, None)
    else:
        print "type is missing. Direct connection. None, None"
        return (None, None)


    http_proxy = None
    if proxy_prefs.has_key("http") and proxy_prefs["http"]:
        http_proxy = "http://"+proxy_prefs["http"]
        if proxy_prefs.has_key("http_port") and proxy_prefs["http_port"]:
            http_proxy += ":" + proxy_prefs["http_port"]
        else:
            http_proxy += ":" + '80'

    ftp_proxy =None
    if proxy_prefs.has_key("ftp") and proxy_prefs["ftp"]:
        ftp_proxy = "http://"+proxy_prefs["ftp"]
        if proxy_prefs.has_key("ftp_port") and proxy_prefs["ftp_port"]:
            ftp_proxy += ":" + proxy_prefs["ftp_port"]
        else:
            ftp_proxy += ":" + '80'

    return http_proxy, ftp_proxy

def show_url(url):
    if webbrowser.__dict__.has_key("open_new_tab"):
        webbrowser.open_new_tab(url)
    else:
        webbrowser.open_new(url)
       

#
# Module initialization
#

l_names = []
try:
    (local_name, local_aliases,local_addrs) = \
                 socket.gethostbyaddr(constants.LOCALHOST)

    l_names.append(local_name)
    l_names = local_aliases + local_addrs
except socket.herror:
    print "ERROR : can not resolve localhost"
    pass
    

# directly from xend/server/netif.py (LGPL)
# Copyright 2004, 2005 Mike Wray <mike.wray@hp.com>
# Copyright 2005 XenSource Ltd
def randomMAC():
    """Generate a random MAC address.

    Uses OUI (Organizationally Unique Identifier) 00-16-3E, allocated to
    Xensource, Inc. The OUI list is available at
    http://standards.ieee.org/regauth/oui/oui.txt.

    The remaining 3 fields are random, with the first bit of the first
    random field set 0.

    @return: MAC address string
    """
    mac = [ 0x00, 0x16, 0x3e,
            random.randint(0x00, 0x7f),
            random.randint(0x00, 0xff),
            random.randint(0x00, 0xff) ]
    return ':'.join(map(lambda x: "%02x" % x, mac))


# directly from xend/uuid.py (LGPL)
# Copyright 2005 Mike Wray <mike.wray@hp.com>
# Copyright 2005 XenSource Ltd
def randomUUID():
    """Generate a random UUID."""

    return [ random.randint(0, 255) for _ in range(0, 16) ]


# directly from xend/uuid.py (LGPL)
# Copyright 2005 Mike Wray <mike.wray@hp.com>
# Copyright 2005 XenSource Ltd
def uuidToString(u):
    return "-".join(["%02x" * 4, "%02x" * 2, "%02x" * 2, "%02x" * 2,
                     "%02x" * 6]) % tuple(u)


# directly from xend/uuid.py (LGPL)
# Copyright 2005 Mike Wray <mike.wray@hp.com>
# Copyright 2005 XenSource Ltd
def uuidFromString(s):
    s = s.replace('-', '')
    return [ int(s[i : i + 2], 16) for i in range(0, 32, 2) ]


# the following function quotes from python2.5/uuid.py
#def get_host_network_devices():
#    device = []
#    for dir in ['', '/sbin/', '/usr/sbin']:
#        executable = os.path.join(dir, "ifconfig")
#        if not os.path.exists(executable):
#            continue
#        try:
#            cmd = 'LC_ALL=C %s -a 2>/dev/null' % (executable)
#            pipe = os.popen(cmd)
#        except IOError:
#            continue
#        for line in pipe:
#            words = line.lower().split()
#            for i in range(len(words)):
#                if words[i] == "hwaddr":
#                    device.append(words)
#    return device


        
# Util functions

# generic function which looks for a tag under the node, and populates
# info with attribute values using attribute map
def populate_node(info, parent_node, tag, attributes):
    nodes = parent_node.getElementsByTagName(tag)
    if nodes:
        node = nodes[0]
        populate_attrs(info, node, attributes)

def populate_attrs(info, node, attributes):
    for key, attr in attributes.iteritems():
        info[key] = node.getAttribute(attr)

def getText(parent_node, tag_name):
    nodelist = []
    elems =  parent_node.getElementsByTagName(tag_name)
    if elems.length > 0:
        first = elems[0]
        nodelist = first.childNodes
    else:
        #print "#####tag %s not found for %s", (tag_name, parent_node)
        pass
    
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE or node.nodeType == node.CDATA_SECTION_NODE:
            rc = rc + node.data
    return rc        


# search a file with context in the tarball or installation and return path
# precedence to the current directory/tarball
def get_path(filename, name_spaces=None):
    for path in (os.path.dirname(sys.argv[0]),'.', '/usr/share/convirt'):
        if name_spaces:
            for ns in name_spaces:
                p = os.path.join(path, ns)
                f = os.path.join(p, filename)
                if os.path.exists(f):
                    return (p, f)
        else:
            f = os.path.join(path, filename)
            if os.path.exists(f):
                return (path,f)
    return (None,None)

## initialize platform defaults
def get_platform_defaults(location):
    dir_name = os.path.dirname(location)
    file_name = os.path.join(dir_name, "defaults")
    return read_python_conf(file_name)
    
def get_prop(map, key, default=None):
    if map.has_key(key):
        return map[key]
    else:
        return default



def cleanupQCDomain(managed_node, name, vbdlocation=''):
    """ Delete the xen configuration file and associated
    disk devices created during a quickcreate"""

    if name in managed_node.get_dom_names():
        dom = managed_node.get_dom(name)
        dom_config = None
        if dom:
            dom_config = dom.get_config()

        if dom_config:
            domfilename = dom_config.filename
            if domfilename and managed_node.node_proxy.file_exists(domfilename):
                managed_node.node_proxy.remove(domfilename)


            for file in dom_config.getDisks():
                # skip read only volumes, they are likely to be shared
                # or cdroms.
                # THIS IS NOT COMPLETE SOLN, NEED to prompt user for each
                # disk is the right soln
                if file.mode.find("w") == -1:
                    continue
                # dont mess with /dev/disk/ area. (Lun, AOE disks)
                if file.filename and (file.filename.strip().find("/dev/disk/")==0 or file.filename.strip().find("/dev/etherd/") == 0):
                    continue
                if file.type == 'phy':
                    try:
                        managed_node.lvm_proxy.removeLogicalVolume(file.filename)
                        print 'deleting: ' + file.filename
                    except OSError, err:
                        print "error deleting " + file.filename,err
                elif file.type in ['file','tap:aio','tap:qcow','tap:vmdk']:
                    if managed_node.node_proxy.file_exists(file.filename):
                        print 'deleting: ' + file.filename
                        managed_node.node_proxy.remove(file.filename)
    else:
        print "Couldn't find the domain %s. Skipping deletion" % name

# Provisioning through cli
def provision_vm(image_store,image_name,eGroup,managed_node,storage_manager,vname,memory,vcpus):


    ctx = dynamic_map()
    ctx.image_store = image_store
    image_id=""
    images=image_store.get_images()
    for id in images.keys():
        img=images[id]
        if image_name==img.name:
            image_id=img.id
            image=img
            break

    if image_id=="":
        raise e

    ctx.image_id=image_id
    ctx.managed_node= managed_node
    ctx.vm = None
    ctx.server_pool= eGroup
    ctx.platform = managed_node.get_platform()
    ctx.storage_manager = storage_manager
    vm_config,image_config=image.get_configs()

    if ctx.server_pool is not None:
            grp_settings = ctx.server_pool.getGroupVars()
            merge_pool_settings(vm_config,
                                      image_config,
                                      grp_settings, True)


    vm_config['name']=vname
    if memory is not None:
        vm_config['memory']=memory
    if vcpus is not None:
       vm_config['vcpus']=vcpus

    vm_config_path=os.path.join('$VM_CONF_DIR',vname)
    vm_config.set_filename(vm_config_path)

    ctx.vm_config=vm_config
    ctx.image_config=image_config

    kick_off_cli_provisioning(ctx,image_name)

# Provisioning process
def kick_off_cli_provisioning(context,img_name):

    # instantiate the configs
    v_config = context.vm_config
    i_config = context.image_config
    # add/set a uui to the config
    v_config["uuid"] = uuidToString(randomUUID())

    # create is_remote map by position.
    image_id=context.image_id
    try:

        store,managed_node,vm_config_file=vm_config_write(context,image_id,v_config,
                                            i_config,img_name)
         # execute the provisioning script
        (out, exit_code, log_filename)=store.execute_provisioning_script(
                                     managed_node,
                                     image_id,
                                     v_config,
                                     i_config)

#        print "out==",out, "exit_code=",exit_code,"log", log_filename
        if exit_code == 0:
            try:
                managed_node.add_dom_config(vm_config_file)
            except Exception, str:
                raise Exception(str)


    except Exception, str:
        raise Exception(str)


# Config file writing
def vm_config_write(context,image_id,v_config,i_config,img_name):
            # update is_remote and change protocol

        try:
            is_remote_list = []
            s_stats = v_config.get_storage_stats()
            for de in v_config.getDisks():
                r = s_stats.get_remote(de.filename)
                is_remote_list.append(r)

            img_location = context.image_store.get_image(image_id).location
            if img_location:
                img_location = os.path.basename(img_location)

            instantiate_configs(context.managed_node,
                                     context.image_store,
                                     img_name,
                                     img_location,
                                     v_config,
                                     i_config)


            store = context.image_store
            managed_node = context.managed_node

            vm_config_file  = v_config.filename


            v_config["image_name"] = img_name
            v_config["image_id"] = image_id
            v_config["platform"] = managed_node.get_platform()
            # see if the following can be moved to execute_prov script
            v_config.set_managed_node(managed_node)
            change_file_2_tap = False
            is_hvm_image = context.image_store.get_image(image_id).is_hvm()
            if v_config["platform"] == 'xen' and not is_hvm_image:
                change_file_2_tap = True


            changed = False
            changed_disks = []

            ndx = 0
            ll = len(is_remote_list)
            s_stats = v_config.get_storage_stats()
            for de in v_config.getDisks():
                if de and de.type == "file" and change_file_2_tap:
                    de.type = "tap:aio"
                    changed = True
                changed_disks.append(repr(de))

                r = None
                if ndx < ll:
                    r = is_remote_list[ndx]
                s_stats.set_remote(de.filename,r)
                ndx += 1


            if changed: # update the disk entry
                print "Updating disk to new value : for tap:aio", changed_disks
                v_config["disk"] = changed_disks

            v_config.write()

            return store,managed_node,vm_config_file
        except Exception, e:
            raise e


def instantiate_configs(managed_node,
                            image_store, image_name, image_location,
                            vm_config, image_config):

    # get prepare template map
    try:
        template_map = fv_map()
        store_location = image_store.get_remote_location(managed_node)

        template_map["IMAGE_STORE"] = store_location
        template_map["IMAGE_NAME"] = image_name
        template_map["IMAGE_LOCATION"] = image_location
        template_map["VM_NAME"] = vm_config["name"]

        # default conventions
        def_bridge="xenbr0"
        if managed_node.platform == 'kvm':
            def_bridge='br0'

        # see if the managed_node has the default bridge
        bridge = managed_node.get_default_bridge()
        if bridge:
            print "setting default bridge from discoverd information", bridge
            def_bridge = bridge
        elif image_config["DEFAULT_BRIDGE"] : #cheating
            def_bridge = image_config["DEFAULT_BRIDGE"]


        template_map["DEFAULT_BRIDGE"] = def_bridge

        # Autogenerated MAC address
        # TODO: keep track of generated MAC's
        template_map["AUTOGEN_MAC"] = randomMAC # NOTE : passing function reference

        image_config.instantiate_config(template_map)

        # instantiate vm_config with image config
        vm_config.instantiate_config(template_map)

        if image_config is not None:
            vm_config.instantiate_config(image_config)
    except Exception, e:
        raise e



def merge_pool_settings(vm_config,
                            image_config,
                            pool_settings,
                            append_missing=False):
        gconfig = {}

        for key in pool_settings:
            gconfig[key] = pool_settings[key]

        for key in vm_config:
            if key in gconfig:
                vm_config[key] = gconfig[key]
                del gconfig[key]

        for key in image_config:
            if key in gconfig:
                image_config[key] = gconfig[key]
                del gconfig[key]

        if append_missing is True:
            for key in gconfig:
                image_config[key] = gconfig[key]



class dummyStream:
	''' dummyStream behaves like a stream but does nothing. '''
	def __init__(self): pass
	def write(self,data): pass
	def read(self,data): pass
	def flush(self): pass
	def close(self): pass
#########################
# SELF TEST
#########################

if __name__ == '__main__':

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

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


    #
    # LVMProxy tests
    #
    lvm_local = LVMProxy(local_node)
    lvm_remote = LVMProxy(remote_node)
    lvm_remote = lvm_local

    print '\nLVMProxy interface test STARTING'
    for lvm in (lvm_local, lvm_remote):
        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 'LVMPRoxy interface test COMPLETED\n'


    #
    # XMConfig tests
    #    

    TEST_CONFIGFILE = '/foo/convirt.conf'
    
    print "\nXMConfig interface test STARTING\n"
    
    print 'LOCALHOST ...'
    config_local = XMConfig(local_node, searchfiles = [TEST_CONFIGFILE],
                            create_file=TEST_CONFIGFILE)    
    config_local.set(XMConfig.DEFAULT,'TEST_PROP','TEST_VAL')
    print "Default Property TEST_PROP:",config_local.getDefault('TEST_PROP')
    print "Default Sections:",config_local.sections()
    print "Known Hosts", config_local.getHosts()
    config_local2 = XMConfig(local_node, searchfiles = [TEST_CONFIGFILE])
    print "Default Property TEST_PROP:",config_local2.getDefault('test_prop')    
    local_node.remove(TEST_CONFIGFILE)

    print '\nREMOTE HOST ...'
    config_remote = XMConfig(remote_node, searchfiles = [TEST_CONFIGFILE],
                            create_file=TEST_CONFIGFILE)
    config_remote.setDefault('TEST_PROP','TEST_VAL')
    print "Default Property TEST_PROP:",config_remote.get(XMConfig.DEFAULT,'TEST_PROP')
    print "Default Sections:",config_remote.sections()
    print "Known Hosts", config_remote.getHosts()
    config_remote2 = XMConfig(remote_node, searchfiles = [TEST_CONFIGFILE])
    print "Default Property TEST_PROP:",config_remote2.getDefault('test_prop')
    remote_node.remove(TEST_CONFIGFILE)

    print "\nXMConfig interface test COMPLETED"


    #
    # ImageStore tests
    #

##    print "\nImageStore interface test STARTING\n"
##    config_local = XMConfig(local_node, searchfiles = [TEST_CONFIGFILE],
##                            create_file=TEST_CONFIGFILE)
##    store = ImageStore(config_local)
##    print store.list()
    

##     print "\nImageStore interface test STARTING\n"
##     config_local = XMConfig(local_node, searchfiles = [TEST_CONFIGFILE],
##                             create_file=TEST_CONFIGFILE)
##     store = ImageStore(config_local)
##     print store.list
    
##     store.addImage('test_image','/var/cache/convirt/vmlinuz.default','/var/cache/convirt/initrd.img.default')
##     print store.list
##     print store.getImage('test_image')
##     print store.getFilenames('test_image')

##     #store.addImage('test_image2',
##     #               'http://linux.nssl.noaa.gov/fedora/core/5/i386/os/images/xen/vmlinuz',
##     #               'http://linux.nssl.noaa.gov/fedora/core/5/i386/os/images/xen/initrd.img',
##     #               )
##     #print store.list
##     #print store.getImage('test_image2')
##     #print store.getFilenames('test_image2')

##     store.addImage('test_image2',
##                    'http://localhost/fedora/images/xen/vmlinuz',
##                    'http://localhost/fedora/images/xen/initrd.img',
##                    )
##     print store.list
##     print store.getImage('test_image2')
##     print "First access, should fetch ...\n",store.getFilenames('test_image2')
##     print "Second access, should get from cache ... "
##     kernel, ramdisk = store.getFilenames('test_image2')
##     print (kernel, ramdisk)
##     local_node.remove(kernel)
##     local_node.remove(ramdisk)
##     local_node.rmdir('/var/cache/convirt/test_image2')
    
##     local_node.remove(TEST_CONFIGFILE)
    
    print "\nImageStore interface test COMPLETED"
    sys.exit(0)
    
    
    
        
