text: #
  # CORE
  # Copyright (c)2011-2013 the Boeing Company.
  # See the LICENSE file included in this distribution.
  #
  # author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
  #
  
  import os
  import pwd
  from core.netns import nodes
  from core.api import coreapi
  from xml.dom.minidom import Document
  from xmlutils import *
  
  class CoreDocumentWriter0(Document):
      ''' Utility class for writing a CoreSession to XML. The init method builds
      an xml.dom.minidom.Document, and the writexml() method saves the XML file.
      '''
      def __init__(self, session):
          ''' Create an empty Scenario XML Document, then populate it with
          objects from the given session.
          '''
          Document.__init__(self)
          self.session = session
          self.scenario = self.createElement("Scenario")
          self.np = self.createElement("NetworkPlan")
          self.mp = self.createElement("MotionPlan")
          self.sp = self.createElement("ServicePlan")
          self.meta = self.createElement("CoreMetaData")
  
          self.appendChild(self.scenario)
          self.scenario.appendChild(self.np)
          self.scenario.appendChild(self.mp)
          self.scenario.appendChild(self.sp)
          self.scenario.appendChild(self.meta)
          
          self.populatefromsession()
  
      def populatefromsession(self):
          self.session.emane.setup() # not during runtime?
          self.addorigin()
          self.adddefaultservices()
          self.addnets()
          self.addnodes()
          self.addmetadata()
              
      def writexml(self, filename):
          self.session.info("saving session XML file %s" % filename)
          f = open(filename, "w")
          Document.writexml(self, writer=f, indent="", addindent="  ", newl="\n", \
                            encoding="UTF-8")
          f.close()
          if self.session.user is not None:
              uid = pwd.getpwnam(self.session.user).pw_uid
              gid = os.stat(self.session.sessiondir).st_gid
              os.chown(filename, uid, gid)
              
      def addnets(self):
          ''' Add PyCoreNet objects as NetworkDefinition XML elements.
          '''
          with self.session._objslock:
              for net in self.session.objs():
                  if not isinstance(net, nodes.PyCoreNet):
                      continue
                  self.addnet(net)
  
      def addnet(self, net):
          ''' Add one PyCoreNet object as a NetworkDefinition XML element.
          '''
          n = self.createElement("NetworkDefinition")
          self.np.appendChild(n)
          n.setAttribute("name", net.name)
          # could use net.brname
          n.setAttribute("id", "%s" % net.objid)
          n.setAttribute("type", "%s" % net.__class__.__name__)
          self.addnetinterfaces(n, net)
          # key used with tunnel node
          if hasattr(net, 'grekey') and net.grekey is not None:
              n.setAttribute("key", "%s" % net.grekey)
          # link parameters
          for netif in net.netifs(sort=True):
              self.addnetem(n, netif)        
          # wireless/mobility models
          modelconfigs = net.session.mobility.getmodels(net)
          modelconfigs += net.session.emane.getmodels(net)
          self.addmodels(n, modelconfigs)
          self.addposition(net)
          
      def addnetem(self, n, netif):
          ''' Similar to addmodels(); used for writing netem link effects
          parameters. TODO: Interface parameters should be moved to the model
          construct, then this separate method shouldn't be required.
          '''
          params = netif.getparams()
          if len(params) == 0:
              return
          model = self.createElement("model")
          model.setAttribute("name", "netem")
          model.setAttribute("netif", netif.name)
          if hasattr(netif, "node") and netif.node is not None:
              model.setAttribute("peer", netif.node.name)
          # link between switches uses one veth interface
          elif hasattr(netif, "othernet") and netif.othernet is not None:
              if netif.othernet.name == n.getAttribute("name"):
                  model.setAttribute("peer", netif.net.name)
              else:
                  model.setAttribute("peer", netif.othernet.name)
                  model.setAttribute("netif", netif.localname)
              # hack used for upstream parameters for link between switches
              # (see LxBrNet.linknet())
              if netif.othernet.objid == int(n.getAttribute("id")):
                  netif.swapparams('_params_up')
                  params = netif.getparams()
                  netif.swapparams('_params_up')
          has_params = False
          for k, v in params:
              # default netem parameters are 0 or None
              if v is None or v == 0:
                  continue
              if k == "has_netem" or k == "has_tbf":
                  continue
              key = self.createElement(k)
              key.appendChild(self.createTextNode("%s" % v))
              model.appendChild(key)
              has_params = True
          if has_params:
              n.appendChild(model)
          
      def addmodels(self, n, configs):
          ''' Add models from a list of model-class, config values tuples.
          '''
          for (m, conf) in configs:
              model = self.createElement("model")
              n.appendChild(model)
              model.setAttribute("name", m._name)
              type = "wireless"
              if m._type == coreapi.CORE_TLV_REG_MOBILITY:
                  type = "mobility"
              model.setAttribute("type", type)
              for i, k in enumerate(m.getnames()):
                  key = self.createElement(k)
                  value = conf[i]
                  if value is None:
                      value = ""
                  key.appendChild(self.createTextNode("%s" % value))
                  model.appendChild(key)
  
      def addnodes(self):
          ''' Add PyCoreNode objects as node XML elements.
          '''
          with self.session._objslock:
              for node in self.session.objs():
                  if not isinstance(node, nodes.PyCoreNode):
                      continue
                  self.addnode(node)
  
      def addnode(self, node):
          ''' Add a PyCoreNode object as node XML elements.
          '''
          n = self.createElement("Node")
          self.np.appendChild(n)
          n.setAttribute("name", node.name)
          n.setAttribute("id", "%s" % node.nodeid())
          if node.type:
              n.setAttribute("type", node.type)
          self.addinterfaces(n, node)
          self.addposition(node)
          addparamtoparent(self, n, "icon", node.icon)
          addparamtoparent(self, n, "canvas", node.canvas)
          self.addservices(node)
          
      def addinterfaces(self, n, node):
          ''' Add PyCoreNetIfs to node XML elements.
          '''
          for ifc in node.netifs(sort=True):
              i = self.createElement("interface")
              n.appendChild(i)
              i.setAttribute("name", ifc.name)
              netmodel = None
              if ifc.net:
                  i.setAttribute("net", ifc.net.name)
                  if hasattr(ifc.net, "model"):
                      netmodel = ifc.net.model
              if ifc.mtu and ifc.mtu != 1500:
                  i.setAttribute("mtu", "%s" % ifc.mtu)
              # could use ifc.params, transport_type
              self.addaddresses(i, ifc)
              # per-interface models
              if netmodel and netmodel._name[:6] == "emane_":
                  cfg = self.session.emane.getifcconfig(node.objid, netmodel._name,
                                                        None, ifc)
                  if cfg:
                      self.addmodels(i, ((netmodel, cfg),) )
  
  
      def addnetinterfaces(self, n, net):
          ''' Similar to addinterfaces(), but only adds interface elements to the
          supplied XML node that would not otherwise appear in the Node elements.
          These are any interfaces that link two switches/hubs together.
          '''
          for ifc in net.netifs(sort=True):
              if not hasattr(ifc, "othernet") or not ifc.othernet:
                  continue
              i = self.createElement("interface")
              n.appendChild(i)
              if net.objid == ifc.net.objid:
                  i.setAttribute("name", ifc.localname)
                  i.setAttribute("net", ifc.othernet.name)
              else:
                  i.setAttribute("name", ifc.name)
                  i.setAttribute("net", ifc.net.name)
  
      def addposition(self, node):
          ''' Add object coordinates as location XML element.
          '''
          (x,y,z) = node.position.get()
          if x is None or y is None:
              return
          # <Node name="n1">
          mpn = self.createElement("Node")
          mpn.setAttribute("name", node.name)
          self.mp.appendChild(mpn)
          
          #   <motion type="stationary">
          motion = self.createElement("motion")
          motion.setAttribute("type", "stationary")
          mpn.appendChild(motion)
          
          #       <point>$X$,$Y$,$Z$</point>
          pt = self.createElement("point")
          motion.appendChild(pt)
          coordstxt = "%s,%s" % (x,y)
          if z:
              coordstxt += ",%s" % z
          coords = self.createTextNode(coordstxt)
          pt.appendChild(coords)
  
      def addorigin(self):
          ''' Add origin to Motion Plan using canvas reference point.
              The CoreLocation class maintains this reference point.
          '''
          refgeo = self.session.location.refgeo
          origin = self.createElement("origin")
          attrs = ("lat","lon","alt")
          have_origin = False
          for i in xrange(3):
              if refgeo[i] is not None:
                  origin.setAttribute(attrs[i], str(refgeo[i]))
                  have_origin = True
          if not have_origin:
              return
          if self.session.location.refscale != 1.0: # 100 pixels = refscale m
              origin.setAttribute("scale100", str(self.session.location.refscale))
          if self.session.location.refxyz != (0.0, 0.0, 0.0):
              pt = self.createElement("point")
              origin.appendChild(pt)
              x,y,z = self.session.location.refxyz
              coordstxt = "%s,%s" % (x,y)
              if z:
                  coordstxt += ",%s" % z
              coords = self.createTextNode(coordstxt)
              pt.appendChild(coords)
  
          self.mp.appendChild(origin)
          
      def adddefaultservices(self):
          ''' Add default services and node types to the ServicePlan.
          '''
          for type in self.session.services.defaultservices:
              defaults = self.session.services.getdefaultservices(type)
              spn = self.createElement("Node")
              spn.setAttribute("type", type)
              self.sp.appendChild(spn)
              for svc in defaults:
                  s = self.createElement("Service")
                  spn.appendChild(s)
                  s.setAttribute("name", str(svc._name))
          
      def addservices(self, node):
          ''' Add services and their customizations to the ServicePlan.
          '''
          if len(node.services) == 0:
              return
          defaults = self.session.services.getdefaultservices(node.type)
          if node.services == defaults:
              return
          spn = self.createElement("Node")
          spn.setAttribute("name", node.name)
          self.sp.appendChild(spn)
  
          for svc in node.services:
              s = self.createElement("Service")
              spn.appendChild(s)
              s.setAttribute("name", str(svc._name))
              s.setAttribute("startup_idx", str(svc._startindex))
              if svc._starttime != "":
                  s.setAttribute("start_time", str(svc._starttime))
              # only record service names if not a customized service
              if not svc._custom:
                  continue
              s.setAttribute("custom", str(svc._custom))
              addelementsfromlist(self, s, svc._dirs, "Directory", "name")
              
              for fn in svc._configs:
                  if len(fn) == 0:
                      continue
                  f = self.createElement("File")
                  f.setAttribute("name", fn)
                  # all file names are added to determine when a file has been deleted
                  s.appendChild(f)
                  data = self.session.services.getservicefiledata(svc, fn)
                  if data is None:
                      # this includes only customized file contents and skips
                      # the auto-generated files
                      continue
                  txt = self.createTextNode(data)
                  f.appendChild(txt)
                      
              addtextelementsfromlist(self, s, svc._startup, "Command",
                                      (("type","start"),))
              addtextelementsfromlist(self, s, svc._shutdown, "Command",
                                      (("type","stop"),))
              addtextelementsfromlist(self, s, svc._validate, "Command",
                                      (("type","validate"),))
  
      def addaddresses(self, i, netif):
          ''' Add MAC and IP addresses to interface XML elements.
          '''
          if netif.hwaddr:
              h = self.createElement("address")
              i.appendChild(h)
              h.setAttribute("type", "mac")
              htxt = self.createTextNode("%s" % netif.hwaddr)
              h.appendChild(htxt)
          for addr in netif.addrlist:
              a = self.createElement("address")
              i.appendChild(a)
              # a.setAttribute("type", )
              atxt = self.createTextNode("%s" % addr)
              a.appendChild(atxt)
              
      def addhooks(self):
          ''' Add hook script XML elements to the metadata tag.
          '''
          hooks = self.createElement("Hooks")
          for state in sorted(self.session._hooks.keys()):
              for (filename, data) in self.session._hooks[state]:
                  hook = self.createElement("Hook")
                  hook.setAttribute("name", filename)
                  hook.setAttribute("state", str(state))
                  txt = self.createTextNode(data)
                  hook.appendChild(txt)
                  hooks.appendChild(hook)
          if hooks.hasChildNodes():
              self.meta.appendChild(hooks)
              
      def addmetadata(self):
          ''' Add CORE-specific session meta-data XML elements.
          '''
          # options
          options = self.createElement("SessionOptions")
          defaults = self.session.options.getdefaultvalues()
          for i, (k, v) in enumerate(self.session.options.getkeyvaluelist()):
              if str(v) != str(defaults[i]):
                  addtextparamtoparent(self, options, k, v)
                  #addparamtoparent(self, options, k, v)
          if options.hasChildNodes():
              self.meta.appendChild(options)
          # hook scripts
          self.addhooks()
          # meta
          meta = self.createElement("MetaData")
          self.meta.appendChild(meta)
          for (k, v) in self.session.metadata.items():
              addtextparamtoparent(self, meta, k, v)
              #addparamtoparent(self, meta, k, v)
  