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

# Manages network definitions
# Each network definition represents a network to which a VM can connect to.

#
# In future this would be enhanced to
# a. support VDE like networks (Private networks spanning machines)
# b. Centralized DHCP modifications
#




from datetime import datetime
from convirt.core.utils.utils import copyToRemote, get_path, XMConfig 
from convirt.core.utils.utils import uuidToString, randomUUID, mkdir2
from convirt.core.utils.utils import dynamic_map
from convirt.core.utils.constants import *
import os
import pprint, traceback

def ct_time():
    return datetime.now().ctime()

UNKNOWN = "UNKNOWN"
IN_SYNC = "IN_SYNC"
OUT_OF_SYNC = "OUT_OF_SYNC"

# Bridge info
#  -- name : Bridge Name
#  -- phy_list : List of interfaces to be added.
#  -- dhcp : True or False
#  -- static_address = Map containing static ipv4 information for the bridge

# VLAN info  (furture)
#  -- id : vland id 

# Bond info (future)
#  - name : Bond name
#  - params : Bond settings (QOS, scheme etc)
#  - slaves : List of interfaces

# IPV4 information : (furture ? : Used for NAT forwarding)
#  - ip_network : network definition : 192.168.12.0/24

# DHCP information
#   dhcp_start: start address
#   dhcp_end: : end address
#   dhcp_server : server hosting dhcp. None for host private n/w meaning on the 
#                 managed network 
#   dhcp_server_creds : credentials to update dhcp server.
# 
 
# nat_info : NAT information
#  interface : interfaces to forward to.


class NwDef:
    PUBLIC_NW = "PUBLIC_NW"
    HOST_PRIVATE_NW = "HOST_PRIVATE_NW"

    def __init__(self,id, type, name, description, is_deleted = False,
                 bridge_info=dynamic_map(),
                 vlan_info=dynamic_map(), 
                 bond_info=dynamic_map(),
                 ipv4_info=dynamic_map(),
                 dhcp_info=dynamic_map(),
                 nat_info=dynamic_map()
                 ):
                 
        self.id = id
        if self.id is None:
            self.id = uuidToString(randomUUID())
        
        self.type = type
        self.name = name
        self.description = description

        self.bridge_info = bridge_info
        self.vlan_info   = vlan_info
        self.bond_info   = bond_info
        self.ipv4_info   = ipv4_info
        self.dhcp_info   = dhcp_info
        self.nat_info    = nat_info
        
        self.deleted = is_deleted


    def set_deleted(self, value=True):
        self.deleted = value

    def is_deleted(self):
        return self.deleted

    def is_nated(self):
        if self.nat_info and self.nat_info.interface:
            return True
        else:
            return False

    def get_definition(self):
        desc = ""
        if self.type == self.HOST_PRIVATE_NW:
            desc += self.ipv4_info.ip_network
            #if self.dhcp_info:
            #    desc += " (%s-%s)" % (self.dhcp_info.dhcp_start, 
            #                         self.dhcp_info.dhcp_end)
            if self.is_nated():
                desc += ", NAT to "
                desc += self.nat_info.interface
        elif self.type == self.PUBLIC_NW:
            if self.bridge_info and self.bridge_info.phy_list:
                if self.ipv4_info and self.ipv4_info.ip_network:
                    desc = "%s (%s) connected to %s" % (self.bridge_info.name, 
                                                        self.ipv4_info.ip_network,
                                                        self.bridge_info.phy_list)
                else:
                    desc = "%s connected to %s" % (self.bridge_info.name, 
                                                   self.bridge_info.phy_list)
            else:
                if self.ipv4_info and self.ipv4_info.ip_network:
                    desc = "%s (%s)" % (self.bridge_info.name, 
                                        self.ipv4_info.ip_network)
                else:
                    desc = "%s" % (self.bridge_info.name,)
        return desc

    def __repr__(self):
        return str({"id":self.id,
                    "type":self.type,
                    "name":self.name,
                    "description":self.description,
                    "bridge_info" : self.bridge_info,
                    "vlan_info" : self.vlan_info,
                    "bond_info" : self.bond_info,
                    "ipv4_info" : self.ipv4_info,
                    "dhcp_info" : self.dhcp_info,
                    "nat_info"  : self.nat_info,
                    "is_deleted" : self.deleted
                    })

# keep track definition synced up or not at group level.
class GInfo:
    
    def __init__(self, group_id, def_id, def_type, 
                 status=UNKNOWN, oos_count = 0,
                 dt_time=None):
        self.group_id = group_id
        self.def_id = def_id
        self.def_type = def_type
        self.status = status
        self.oos_count = oos_count
        if dt_time:
            self.dt_time = dt_time
        else:
            self.dt_time = ct_time()

        self.deleted = False

    def set_deleted(self, value=True):
        self.deleted = value
    
    def is_deleted(self):
        return self.deleted

    def __repr__(self):
        return str( {"group_id":self.group_id,
                     "def_id" : self.def_id,
                     "def_type":self.def_type,
                     "status" : self.status,
                     "oos_count" : self.oos_count,
                     "dt_time" : self.dt_time,
                     "is_deleted" : self.deleted
                     }
                    )

    def dump(self):
        print "%s|%s|%s|%s|%s|%s|%s" % (self.group_id,
                                     self.def_id,
                                     self.def_type,
                                     self.deleted, 
                                     self.status,
                                     self.oos_count,
                                     self.dt_time)
                                     

                    
# Keep track of infromation at the node level for a particular definition
class NInfo:
    def __init__(self, node_id, group_id, 
                 def_id, def_type, 
                 status=UNKNOWN, dt_time=None,details=""):

        self.node_id = node_id
        self.group_id = group_id
        self.def_id = def_id
        self.def_type=def_type
        self.status = status
        self.dt_time = dt_time
        self.details = details
        self.deleted = False

    def set_deleted(self, value=True):
        self.deleted = value
    
    def is_deleted(self):
        return self.deleted

    def __repr__(self):
        return str( {"node_id":self.node_id,
                     "group_id":self.group_id,
                     "def_id" : self.def_id,
                     "def_type":self.def_type,
                     "status" : self.status,
                     "dt_time" : self.dt_time,
                     "details" : self.details,
                     "is_deleted" : self.deleted
                     }
                    )
    
    def dump(self):
        print "%s|%s|%s|%s|%s|%s|%s|%s" % (self.node_id,
                                        self.group_id,
                                        self.def_id,
                                        self.def_type,
                                        self.deleted,
                                        self.status,
                                        self.dt_time,
                                        self.details)
                                     


class NwManager:
    (src_path, s_src_scripts_location) = get_path("nw/scripts")
    s_scripts_location = "/var/cache/convirt/nw"

    (common_src_path, s_common_src_scripts_location) = get_path("common/scripts")
    s_common_scripts_location = "/var/cache/convirt/common"

    def __init__(self, store):
        self.store = store

        self.defs = {}
        self.group_defn_status = []
        self.node_defn_status = []
        
        self._read_defns() # read defs
        self._read_group_defn_status() # read group and nd relations
        self._read_node_defn_status()  # read node and nd relation/status
        
    # read nw definitions from store
    def _read_defns(self):
        self.defs = {}
        str_defns = self.store.get(XMConfig.APP_DATA,
                                      prop_nw_defns)
        s_defns = {}
        if str_defns:
            s_defns = eval(str_defns)

        for k, s_defn in s_defns.iteritems():
            pprint.pprint(s_defn)
            nd = NwDef(s_defn["id"],
                       s_defn["type"],
                       s_defn["name"],
                       s_defn["description"],
                       s_defn["is_deleted"],
                       dynamic_map(s_defn["bridge_info"]),
                       dynamic_map(s_defn["vlan_info"]),
                       dynamic_map(s_defn["bond_info"]),
                       dynamic_map(s_defn["ipv4_info"]),
                       dynamic_map(s_defn["dhcp_info"]),
                       dynamic_map(s_defn["nat_info"]),
                       )

            self.defs[nd.id] = nd

    def save_defns(self):
        self.store.set(XMConfig.APP_DATA, prop_nw_defns, str(self.defs))

    # return the nw defintions for a given group    
    def get_defns(self, group_id, node_id = None):
        print "group_id %s, node_id %s" % ( group_id, node_id)
        defs = [ self.defs[id] \
                     for id in self.get_defn_ids(group_id, node_id)]
        return defs

    # execute the GET_DETAILS on the give node and return the results
    # op = GET_DETAILS : Return details about all or specific network
    #                    [we should add a status to the data struct]   
    def get_details(self, defn, node, group):
        processor = self.nw_processor #self.nw_processors[nd.type]
        op = "GET_DETAILS"
        (exit_code, output) = self.exec_script(node, group.id, defn, 
                                               op=op)
        # lets process the output in to corresponding
        # details structure
        result = {}
        result["type"] = defn.type
        result["id"]   = defn.id
        result["op"]   = op
        result["name"] = defn.name
            
        # some more common things here
        if processor:
            processor(op,output, result)
            return result
        else:
            raise Exception("Can not process output. %s" % op )

    
    # return the definition ids for a given group
    def get_defn_ids(self, group_id, node_id = None):
        if node_id is None:
            ids = [ g_defn.def_id for g_defn in self.group_defn_status \
                        if g_defn.group_id == group_id ]
        else:
            ids = [ n_defn.def_id for n_defn in self.node_defn_status \
                        if n_defn.node_id == node_id ]
        
                                                
        return ids

    def get_defn(self, id):
        return self.defs.get(id)
        
    # add a nw definition to a group.
    def add_defn(self, defn, group, node):
        errs = []
        name = defn.name
        group_id=None
        node_id=None
        if group:
           group_id = group.id
        if node is not None:
           node_id = node.hostname

        for d in self.defs.itervalues():
            if d.name == name:
                if d.id in self.get_defn_ids(group_id, node_id):
                    raise Exception("Network definition with the same name exist, in this Server Pool")
        self.defs[defn.id] = defn
        self.save_defns()
        if group:
            self.group_defn_status.append(GInfo(group.id, defn.id, defn.type))
            self._save_group_defn_status()

        return self.on_update_defn(defn, group, node,errs)

    # a new nw definition got added
    def on_update_defn(self, defn, group, node, errs):
        # kick of client connection and setup from all nodes in a
        # group
        if group:
            for node in group.getNodeList().itervalues():
                # add the entry and then sync it.
                n_status_info = NInfo(node.hostname,
                                                   group.id,
                                                   defn.id,
                                                   defn.type,
                                                   OUT_OF_SYNC,
                                                   ct_time(),
                                                   "")
                n_status_info.set_deleted(defn.is_deleted())
                self.update_node_defn_status(n_status_info)

            for node in group.getNodeList().itervalues():
                self.attempt_sync(node, group.id, defn, 
                                        op="SYNC", errs=errs)

        else:
            n_status_info = NInfo(node.hostname,
                                  None,
                                  defn.id,
                                  defn.type,
                                  OUT_OF_SYNC,
                                  ct_time(),
                                  "")
            n_status_info.set_deleted(defn.is_deleted())
            self.update_node_defn_status(n_status_info)
            self.attempt_sync(node, None, defn, op="SYNC", errs=errs)

        return errs

   
    def rename_defn(self, id, new_name):
        defn = self.defs.get(id)
        if defn:
            defn.name = new_name
            self.save_defns()
        else:
            raise Exception("Can not rename Network definition.\n" + \
                                "Definition with id not Found.%s" % (id, ))

    def update_desc(self, id, new_desc):
        defn = self.defs.get(id)
        if defn:
            defn.description = new_desc
            self.save_defns()
        else:
            raise Exception("Can not update description.\n" + \
                                "Definition with id not Found.%s" % (id, ))

    def update_defn(self, defn, group, node):
        errs = []
        if not self.defs.get(defn.id):
            raise Exception("Network definition with id %s , name %s does not exist" % (defn.id,defn.name))
        self.defs[nd.id] = nd
        self.save_defns()
        return self.on_update_defn(defn, group, node, errs) # resync

    def remove_defn(self, defn, group, node):
        errs= []
        def_id = defn.id
        group_id = None
        if group:
            group_id = group.id
        node_id = None
        if node is not None:
            node_id = node.hostname

        defn.set_deleted(True)
        self.save_defns()

        for g_def in self.group_defn_status:
            if g_def.group_id == group_id and g_def.def_id == defn.id:
                g_def.set_deleted(True)

        for n_def in self.node_defn_status: 
            if n_def.group_id == group_id and n_def.def_id == defn.id:
                if node_id == None or n_def.node_id == node_id:
                    n_def.set_deleted(True)

        return self.on_update_defn(defn, group, node, errs)

    def get_group_status(self, def_id, group_id):
        for g in self.group_defn_status:
            if g.group_id == group_id and g.def_id == def_id :
                return g

    def get_node_status(self, def_id, node_id):
        for n in self.node_defn_status:
            if n.node_id == node_id and n.def_id == def_id :
                return n

    # read groups x nw definition map
    def _read_group_defn_status(self):
        self.group_defn_status = []
        grp_defns = self.store.get(XMConfig.APP_DATA,
                                       prop_group_defn_status)

        group_defn_info = []
        if grp_defns:
            group_defn_info = eval(grp_defns)
            
        for g_defni in group_defn_info:
            g_defn = GInfo(g_defni["group_id"],
                           g_defni["def_id"],
                           g_defni["def_type"],
                           g_defni["status"],
                           g_defni["oos_count"],
                           g_defni["dt_time"])

            g_defn.set_deleted(g_defni["is_deleted"])
            
            self.group_defn_status.append(g_defn)

    def _save_group_defn_status(self):
        self.store.set(XMConfig.APP_DATA, prop_group_defn_status, 
                       str(self.group_defn_status))

    def dump_group_defn_status(self):
        for g_defn in self.group_defn_status:
            g_defn.dump()
        print "\n"
        
    # read nodes x nw definition status
    def _read_node_defn_status(self):
        self.node_defn_status = []
        n_defn = self.store.get(XMConfig.APP_DATA,
                                       prop_node_defn_status)
        node_defn_info = []
        if n_defn:
            node_defn_info = eval(n_defn)
            
        for n_defni in node_defn_info:
            n_defn = NInfo(n_defni["node_id"],
                           n_defni["group_id"],
                           n_defni["def_id"],
                           n_defni["def_type"],
                           n_defni["status"],
                           n_defni["dt_time"],
                           n_defni["details"])

            n_defn.set_deleted(n_defni["is_deleted"])

            
            self.node_defn_status.append(n_defn)

    def _save_node_defn_status(self):
        self.store.set(XMConfig.APP_DATA, prop_node_defn_status, 
                       str(self.node_defn_status))
        
    def dump_node_defn_status(self):
        for n_defn in self.node_defn_status:
            n_defn.dump()
        print "\n"

        

    # update/add the node x nw defintion record.
    # also rollup the group x nw definition status.
    def update_node_defn_status(self, nsd):
        updated = False
        oos_count = 0 # out of sync count
        for n in self.node_defn_status:
            if n.node_id == nsd.node_id and n.group_id == nsd.group_id and \
                               n.def_id == nsd.def_id:
                # NOTE : found.. do not copy deleted flag.
                n.status = nsd.status
                n.dt_time = nsd.dt_time
                n.details = nsd.details
                self._save_node_defn_status()
                #print "NW Def Updated ", n.deleted
                updated = True

            # gather the out of sync ops, used for updating group status
            if  n.group_id == nsd.group_id and \
                               n.def_id == nsd.def_id and n.status != IN_SYNC:
                oos_count += 1



        if not updated:  ## Add it.  
            # did not find the match
            if not nsd.is_deleted():
                print "######## Added!! "
                self.node_defn_status.append(nsd)
                if nsd.status != IN_SYNC:
                    oos_count += 1
                self._save_node_defn_status()

        # update the corresponding GInfo
        if oos_count > 0 :
            g_status = OUT_OF_SYNC
        else:
            g_status = IN_SYNC

        # delete entries, if marked for deletion and in SYNC.
        deleted = False
        for n in self.node_defn_status:
            if n.is_deleted() and n.status == IN_SYNC:
                # print " DELETING NW DEF"
                self.node_defn_status.remove(n)
                deleted = True
                if n.group_id is None : #host only n/w
                    # Remove the definition too. 
                    # This is bit at a odd place.
                    del self.defs[n.def_id] 
                    self.save_defns()
        if deleted :
            self._save_node_defn_status()
            
        self.update_group_defn_status(nsd.group_id, nsd.def_id, nsd.def_type,
                                      oos_count, g_status)

        
    def update_group_defn_status(self, group_id, def_id, def_type, 
                                 oos_count = 0, g_status =None):
        if group_id is None:
            return

        if g_status is None: # compute it
            oos_count = 0
            for n in self.node_defn_status:
                # gather the out of sync ops, used for updating group status
                if  n.group_id == group_id and \
                        n.def_id == def_id and \
                        n.def_type == def_type  and n.status != IN_SYNC:
                    oos_count += 1
            # update the corresponding GInfo
            if oos_count > 0 :
                g_status = OUT_OF_SYNC
            else:
                g_status = IN_SYNC

        for g in self.group_defn_status:
            if g.group_id == group_id and g.def_id == def_id :
                g.status = g_status
                g.dt_time = ct_time()

                # Delete if we are all done.
                if g.is_deleted() and g.status == IN_SYNC:
                    ##print "Deleting group level def status"
                    self.group_defn_status.remove(g)

                    # Remove the definition too. 
                    # This is bit at a odd place.
          
                    del self.defs[def_id] 
                    self.save_defns()
                else:
                    #print " Updating group level", g.is_deleted()
                    pass
      
                self._save_group_defn_status()
                return
            

        defn=self.get_defn(def_id)
        if defn and not defn.is_deleted():
            print "Adding Group level def record!"
            self.group_defn_status.append(GInfo(group_id, defn, 
                                                def_id, def_type, g_status,
                                                oos_count))
            self._save_group_defn_status()

    # when a group is deleted.
    # Assume group is empty and in sync. 
    def on_delete_group(self, group):
        errs=[]
        # cleanup any state from group
        group_id = group.id

        # similar to someone has removed all definitions.
        modified = False
        for gs in self.group_defn_status:
            if gnd.group_id == group_id:
                defn = self.get_defn(gs.def_id)
                err = self.remove_defn(self, defn, group, None)
                errs = errs + err
        return errs

    # when a new group is added
    def on_add_group(self, group):
        # add tracking entry : we can be lazy...
        pass

    # when a node is removed from a group
    def on_remove_node(self, node, group):
        # node is gettig removed from the group
        # remove all nw definitions for this group
        group_id = None
        if group:
            group_id = group.id

        resulte = []
        for nsd in self.node_defn_status:
            if nsd.node_id == node.hostname and \
                                 nsd.group_id == group.id:
                nsd.set_deleted(True)
                nsd.status = OUT_OF_SYNC
        self._save_node_defn_status()

        # sync up.
        for nsd in self.node_defn_status:
            if nsd.node_id == node.hostname and \
                                 nsd.group_id == group_id:
                nd = self.defs[nsd.def_id]
                self.attempt_sync(node, group_id, defn, op="SYNC", errs=results)
                # Kick of task to remove SD from the node.
                    
        return results

                 
    # when a new node is getting added to the group.         
    def on_add_node(self, node, group):
        # node is gettig added
        # add all nw definitions from this group
        resulte = []
        for gs in self.group_defn_status:
            if gnd.group_id == group.group_id:
                defn = self.defs[gnd.def_id]
                # add the entry and then sync it.
                self.update_node_defn_status(NInfo(node.hostname,
                                                   group_id,
                                                   defn.id,
                                                   defn.type,
                                                   OUT_OF_SYNC,
                                                   ct_time(),
                                                   ""))
        for gs in self.group_defn_status:
            if gnd.group_id == group.group_id:
                defn = self.defs[gnd.def_id]
                self.attempt_sync(node, group.id, defn, 
                                  op="SYNC", errs=results)

        return results

    # we can go fine grained, but not expecting too many files here
    # just copy the whole tree..
    def prepare_scripts(self, dest_node, type):
        copyToRemote(self.s_src_scripts_location, dest_node,
                     self.s_scripts_location)
        copyToRemote(self.s_common_src_scripts_location, dest_node,
                     self.s_common_scripts_location)

    def props_to_cmd_param(self, props):
        cp = ""
        for p,v in props.iteritems():
            if v :
                if cp:
                    cp += "|"
                cp += "%s=%s" % (p,v)
        cp = "'%s'" % (cp, )
        return cp


    def parse_output(self,output, result):
        Lista = []
        for i in output.splitlines():
            d={}
            if not i:
                continue
            i = i.strip()

            if i.find("OUTPUT") !=0:
                    continue
            for j in i.split('|'):
                nameAndValue = j.lstrip().split('=')
                d[nameAndValue[0]]= nameAndValue[1]
            del d['OUTPUT']
            Lista.append(d)
        return Lista

    def parse_summary(self,output, result):
        for i in output.splitlines():
            d={}
            if not i:
                continue
            i = i.strip()

            if i.find("SUMMARY") !=0:
                    continue
            for j in i.split('|'):
                nameAndValue = j.lstrip().split('=')
                d[nameAndValue[0]]= nameAndValue[1]
            del d['SUMMARY']
            return d
        return None

    def parse_networks(self, output, result):
        return

     # nw processor
    def nw_processor(self, op, output, result):
        print "nw processor called with \n", output
        if op == "GET_DETAILS":
            result["DETAILS"] = self.parse_networks(output,result)

    def exec_script(self, node, group, defn, op="GET_DETAILS"):
        type = defn.type
         
        self.prepare_scripts(node, type)
        script_name = os.path.join(self.s_scripts_location,"scripts",
                                   "nw.sh")
        
        log_dir = node.config.get(XMConfig.PATHS,
                                  prop_log_dir)
        if log_dir is None or log_dir == '':
            log_dir = DEFAULT_LOG_DIR


        log_filename = os.path.join(log_dir, "nw/scripts",
                                         "nw_sh.log")

        # create the directory for log files
        mkdir2(node,os.path.dirname(log_filename))

        br_info = self.props_to_cmd_param(defn.bridge_info)
        vlan_info = self.props_to_cmd_param(defn.vlan_info)
        ipv4_info = self.props_to_cmd_param(defn.ipv4_info)
        bond_info = self.props_to_cmd_param(defn.bond_info)
        dhcp_info = self.props_to_cmd_param(defn.dhcp_info)
        nat_info = self.props_to_cmd_param(defn.nat_info)
         

        # help script find its location
        script_loc = os.path.join(self.s_scripts_location,"scripts")

         
        script_args= " -t " + type + \
            " -b " + br_info + \
            " -v " + vlan_info + \
            " -i " + ipv4_info + \
            " -p " + bond_info + \
            " -d " + dhcp_info + \
            " -n " + nat_info + \
            " -o " + op + \
            " -s " + script_loc + \
            " -l " + log_filename  
        
        cmd = script_name +  script_args


        # execute the script
        print "executing ",cmd
        (output, exit_code) = node.node_proxy.exec_cmd(cmd)
        
        print "EXIT CODE:", exit_code , output
        if exit_code != 0:
            raise Exception(output)
        
        return (exit_code, output)


    def attempt_sync(self, node, group_id, defn, op="SYNC", errs=None):
        try: 
            self.sync_node_defn(node,group_id, defn, op="SYNC")
        except Exception, ex:
            print "Error definition %s on %s : %s " % (defn.name, 
                                                       node.hostname, 
                                                       str(ex))
            traceback.print_exc()
            if errs is not None:
                errs.append("Error: %s,%s,%s" % (defn.name, 
                                                 node.hostname, str(ex)))

    # Sync the nw definition with the node
    # update the NInfo record.
    # update the GInfo record.
    # return code, output, structured output

    # op = REMOVE  : Remove the definition.
    #
    # op = SYNC : Add/Update/Remove a requested network
    #
    def sync_node_defn(self, node, group_id, defn, op="SYNC"):
         if not (op in ["SYNC", "REMOVE"] ): 
             raise Exception("Invalid network defn sync op " + op )

         if defn.is_deleted() and op == "SYNC":
             op = "REMOVE"

         nsd = None
         for n in self.node_defn_status:
             if n.node_id == node.hostname and n.group_id == group_id and \
                     n.def_id == defn.id:
                 nsd = n
                 break
         if nsd and nsd.is_deleted():
             op = "REMOVE"

         try:
             (exit_code, output) = self.exec_script(node, group_id,defn, op)
         except Exception, ex:
             print "SWITCHING CODE TO 222"
             traceback.print_exc()
             exit_code = 222
             output = str(ex)

         if exit_code:
             status = OUT_OF_SYNC
         else:
             status = IN_SYNC
            
         dt_time =  ct_time() # convirt TZ for now.
         details= "%d:%s" % (exit_code, output)

         # update or add record. And compute over all sync status
         self.update_node_defn_status(NInfo(node.hostname,
                                            group_id,
                                            defn.id,
                                            defn.type,
                                            status,
                                            dt_time,
                                            details))
         if exit_code != 0:
             raise Exception(details)
         else:
             return { "output" :output,
                      "exit_code" : exit_code }

            
# Test          
if __name__ == '__main__':
    from ManagedNode import ManagedNode
    from Groups import ServerGroup
    local_node = ManagedNode(hostname = LOCALHOST,
                             isRemote = False,
                             helper = None) #creds_helper)
    store = local_node.config
    n_manager = NwManager(store)
    server = "192.168.12.104"
    
    private_nw_bridge_info = dynamic_map({"name" : "br0",
                                          "phy_list" : None
                                          }
                                         )
    private_nw_ipv4_info = dynamic_map( { "ip_network" : "10.0.0.2" } )
    #private_nw_ipv4_info = dynamic_map( { "ip_network" : "172.16.0.0/24" } )

    #private_nw_dhcp_info = dynamic_map( { "dhcp_start" : "172.16.0.128",
    #                                      "dhcp_end"   : "172.16.0.254"
    #                                      }
    private_nw_dhcp_info = dynamic_map( { "dhcp_start" : "10.0.0.1",
                                          "dhcp_end"   : "10.255.255.254"
                                          }
                                        )
            
    #private_nw_nat_info = dynamic_map( { "interface" :"eth0" } )
    private_nw_nat_info = dynamic_map( { "interface" :"ANY" } )


    private_nw = NwDef(None, 
                       NwDef.HOST_PRIVATE_NW, 
                       "Private Isolated N/w",
                       "Private n/w for testing vm on the same box.",
                       bridge_info= private_nw_bridge_info,
                       ipv4_info = private_nw_ipv4_info,
                       dhcp_info = private_nw_dhcp_info,
                       nat_info = private_nw_nat_info)
              

    public_nw_bridge_info = dynamic_map({"name" : "pub_br0",
                                         "phy_list" : "peth0"
                                         }
                                        )

    public_nw = NwDef(None, 
                      NwDef.PUBLIC_NW,
                      "Public N/W", "Production network",
                       bridge_info= public_nw_bridge_info
                      )

    group = ServerGroup("Desktops", {'local_node': local_node})
    defs = n_manager.get_defns(group.id)
    for d in defs:
        print "Removing ..", d.name
        n_manager.remove_defn( d, group)


    n_manager.add_defn(private_nw, group)
#    n_manager.add_defn(public_nw, group)

    print "Group ID", group.id

    defs = n_manager.get_defns(group.id)
    for d in defs:
        print d

    n_manager.dump_group_defn_status()
    n_manager.dump_node_defn_status()
    
    print "check the config file"
    import sys;sys.stdin.readline()

    # Remove the definitions
    n_manager.remove_defn(private_nw,group)

    print "========== after update nfs and delete aoe ======"
    defs = n_manager.get_defns(group.id)
    for d in defs:
        print d

    n_manager.dump_group_defn_status()
    n_manager.dump_node_defn_status()

    #defs = n_manager.get_defns(group.id)
    #sd = defs[0]
    #disk_details = n_manager.get_defn_details(defn, local_node, group)
    #pprint.pprint(disk_details)
