#
# CORE
# Copyright (c)2011-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
xenconfig.py: Implementation of the XenConfigManager class for managing
configurable items for XenNodes.

Configuration for a XenNode is available at these three levels:
Global config:         XenConfigManager.configs[0] = (type='xen', values)
   Nodes of this machine type have this config. These are the default values.
   XenConfigManager.default_config comes from defaults + xen.conf
Node type config:      XenConfigManager.configs[0] = (type='mytype', values)
   All nodes of this type have this config.
Node-specific config:  XenConfigManager.configs[nodenumber] = (type, values)
   The node having this specific number has this config.
'''

import sys, os, threading, subprocess, time, string
import ConfigParser
from xml.dom.minidom import parseString, Document
from core.constants import *
from core.api import coreapi
from core.conf import ConfigurableManager, Configurable


class XenConfigManager(ConfigurableManager):
    ''' Xen controller object. Lives in a Session instance and is used for
        building Xen profiles.
    '''
    _name = "xen"
    _type = coreapi.CORE_TLV_REG_EMULSRV
    
    def __init__(self, session):
        ConfigurableManager.__init__(self, session)
        self.verbose = self.session.getcfgitembool('verbose', False)
        self.default_config = XenDefaultConfig(session, objid=None)
        self.loadconfigfile()

    def setconfig(self, nodenum, conftype, values):
        ''' add configuration values for a node to a dictionary; values are
            usually received from a Configuration Message, and may refer to a
            node for which no object exists yet
        '''
        if nodenum is None: 
            nodenum = 0 # used for storing the global default config
        return ConfigurableManager.setconfig(self, nodenum, conftype, values)

    def getconfig(self, nodenum, conftype, defaultvalues):
        ''' get configuration values for a node; if the values don't exist in
            our dictionary then return the default values supplied; if conftype
            is None then we return a match on any conftype.
        '''
        if nodenum is None: 
            nodenum = 0 # used for storing the global default config
        return ConfigurableManager.getconfig(self, nodenum, conftype,
                                             defaultvalues)

    def clearconfig(self, nodenum):
        ''' remove configuration values for a node
        '''
        ConfigurableManager.clearconfig(self, nodenum)
        if 0 in self.configs:
            self.configs.pop(0)

    def configure(self, session, msg):
        ''' Handle configuration messages for global Xen config.
        '''
        return self.default_config.configure(self, msg)

    def loadconfigfile(self, filename=None):
        ''' Load defaults from the /etc/core/xen.conf file into dict object.
        '''
        if filename is None:
            filename = os.path.join(CORE_CONF_DIR, 'xen.conf')
        cfg = ConfigParser.SafeConfigParser()
        if filename not in cfg.read(filename):
            self.session.warn("unable to read Xen config file: %s" % filename)
            return
        section = "xen"
        if not cfg.has_section(section):
            self.session.warn("%s is missing a xen section!" % filename)
            return
        self.configfile = dict(cfg.items(section))
        # populate default config items from config file entries
        vals = list(self.default_config.getdefaultvalues())
        names = self.default_config.getnames()
        for i in range(len(names)):
            if names[i] in self.configfile:
                vals[i] = self.configfile[names[i]]
        # this sets XenConfigManager.configs[0] = (type='xen', vals)
        self.setconfig(None, self.default_config._name, vals)

    def getconfigitem(self, name, model=None, node=None, value=None):
        ''' Get a config item of the given name, first looking for node-specific
            configuration, then model specific, and finally global defaults.
            If a value is supplied, it will override any stored config.
        '''
        if value is not None:
            return value
        n = None
        if node:
            n = node.objid
        (t, v) = self.getconfig(nodenum=n, conftype=model, defaultvalues=None)
        if n is not None and v is None:
            # get item from default config for the node type
            (t, v) = self.getconfig(nodenum=None, conftype=model,
                                    defaultvalues=None)
        if v is None:
            # get item from default config for the machine type
            (t, v) = self.getconfig(nodenum=None, 
                                    conftype=self.default_config._name,
                                    defaultvalues=None)

        confignames = self.default_config.getnames()
        if v and name in confignames:
            i = confignames.index(name)
            return v[i]
        else:
            # name may only exist in config file
            if name in self.configfile:
                return self.configfile[name]
            else:
                #self.warn("missing config item '%s'" % name)
                return None


class XenConfig(Configurable):
    ''' Manage Xen configuration profiles.
    '''
    
    @classmethod
    def configure(cls, xen, msg):
        ''' Handle configuration messages for setting up a model.
            Similar to Configurable.configure(), but considers opaque data
            for indicating node types.
        '''
        reply = None
        nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE)
        objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ)
        conftype = msg.gettlv(coreapi.CORE_TLV_CONF_TYPE)
        opaque = msg.gettlv(coreapi.CORE_TLV_CONF_OPAQUE)

        nodetype = objname
        if opaque is not None:
            opaque_items = opaque.split(':')
            if len(opaque_items) != 2:
                xen.warn("xen config: invalid opaque data in conf message")
                return None
            nodetype = opaque_items[1]

        if xen.verbose:
            xen.info("received configure message for %s" % nodetype)
        if conftype == coreapi.CONF_TYPE_FLAGS_REQUEST:
            if xen.verbose:
                xen.info("replying to configure request for %s " % nodetype)
            # when object name is "all", the reply to this request may be None
            # if this node has not been configured for this model; otherwise we
            # reply with the defaults for this model
            if objname == "all":
                typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE
            else:
                typeflags = coreapi.CONF_TYPE_FLAGS_NONE
            values = xen.getconfig(nodenum, nodetype, defaultvalues=None)[1]
            if values is None:
                # get defaults from default "xen" config which includes
                # settings from both cls._confdefaultvalues and  xen.conf 
                defaults = cls.getdefaultvalues()
                values = xen.getconfig(nodenum, cls._name, defaults)[1]
            if values is None:
                return None
            # reply with config options
            if nodenum is None:
                nodenum = 0
            reply = cls.toconfmsg(0, nodenum, typeflags, nodetype, values)
        elif conftype == coreapi.CONF_TYPE_FLAGS_RESET:
            if objname == "all":
                xen.clearconfig(nodenum)
        #elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
        else:
            # store the configuration values for later use, when the XenNode
            # object has been created
            if objname is None:
                xen.info("no configuration object for node %s" % nodenum)
                return None
            values_str = msg.gettlv(coreapi.CORE_TLV_CONF_VALUES)
            if values_str is None:
                # use default or preconfigured values
                defaults = cls.getdefaultvalues()
                values = xen.getconfig(nodenum, cls._name, defaults)[1]
            else:
                # use new values supplied from the conf message
                values = values_str.split('|')
            xen.setconfig(nodenum, nodetype, values)
        return reply

    @classmethod
    def toconfmsg(cls, flags, nodenum, typeflags, nodetype, values):
        ''' Convert this class to a Config API message. Some TLVs are defined
            by the class, but node number, conf type flags, and values must
            be passed in.
        '''
        values_str = string.join(values, '|')
        tlvdata = ""
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_NODE, nodenum)
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OBJ,
                                            cls._name)
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_TYPE,
                                            typeflags) 
        datatypes = tuple( map(lambda x: x[1], cls._confmatrix) )
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES,
                                            datatypes) 
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES,
                                            values_str)
        captions = reduce( lambda a,b: a + '|' + b, \
                           map(lambda x: x[4], cls._confmatrix))
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_CAPTIONS,
                                            captions)
        possiblevals = reduce( lambda a,b: a + '|' + b, \
                               map(lambda x: x[3], cls._confmatrix))
        tlvdata += coreapi.CoreConfTlv.pack(
            coreapi.CORE_TLV_CONF_POSSIBLE_VALUES, possiblevals) 
        if cls._bitmap is not None:
            tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_BITMAP,
                                                cls._bitmap)
        if cls._confgroups is not None:
            tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_GROUPS,
                                                cls._confgroups)
        opaque = "%s:%s" % (cls._name, nodetype)
        tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OPAQUE,
                                            opaque)
        msg = coreapi.CoreConfMessage.pack(flags, tlvdata)
        return msg


class XenDefaultConfig(XenConfig):
    ''' Global default Xen configuration options.
    '''
    _name = "xen"
    # Configuration items:
    #   ('name', 'type', 'default', 'possible-value-list', 'caption')
    _confmatrix = [
        ('ram_size', coreapi.CONF_DATA_TYPE_STRING, '256', '', 
         'ram size (MB)'),
        ('disk_size', coreapi.CONF_DATA_TYPE_STRING, '256M', '',
         'disk size (use K/M/G suffix)'),
        ('iso_file', coreapi.CONF_DATA_TYPE_STRING, '', '',
         'iso file'),
        ('mount_path', coreapi.CONF_DATA_TYPE_STRING, '', '',
         'mount path'),
        ('etc_path', coreapi.CONF_DATA_TYPE_STRING, '', '',
         'etc path'),
        ('persist_tar_iso', coreapi.CONF_DATA_TYPE_STRING, '', '',
         'iso persist tar file'),
        ('persist_tar', coreapi.CONF_DATA_TYPE_STRING, '', '',
         'persist tar file'),
        ('root_password', coreapi.CONF_DATA_TYPE_STRING, 'password', '',
         'root password'),
        ]

    _confgroups = "domU properties:1-%d" % len(_confmatrix)