initial commit after bringing over cleaned up code and testing some examples
This commit is contained in:
parent
c4858e6e0d
commit
00f4ebf5a9
93 changed files with 15189 additions and 13083 deletions
0
daemon/core/xml/__init__.py
Normal file
0
daemon/core/xml/__init__.py
Normal file
207
daemon/core/xml/xmldeployment.py
Normal file
207
daemon/core/xml/xmldeployment.py
Normal file
|
@ -0,0 +1,207 @@
|
|||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
from core import constants
|
||||
from core import emane
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import ipaddress
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.netns import nodes
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDeploymentWriter(object):
|
||||
def __init__(self, dom, root, session):
|
||||
self.dom = dom
|
||||
self.root = root
|
||||
self.session = session
|
||||
self.hostname = socket.gethostname()
|
||||
if emane.VERSION < emane.EMANE092:
|
||||
self.transport = None
|
||||
self.platform = None
|
||||
|
||||
@staticmethod
|
||||
def get_ipv4_addresses(hostname):
|
||||
if hostname == 'localhost':
|
||||
addr_list = []
|
||||
cmd = (constants.IP_BIN, '-o', '-f', 'inet', 'addr', 'show')
|
||||
output = subprocess.check_output(cmd)
|
||||
for line in output.split(os.linesep):
|
||||
split = line.split()
|
||||
if not split:
|
||||
continue
|
||||
addr = split[3]
|
||||
if not addr.startswith('127.'):
|
||||
addr_list.append(addr)
|
||||
return addr_list
|
||||
else:
|
||||
# TODO: handle other hosts
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def get_interface_names(hostname):
|
||||
"""
|
||||
Uses same methodology of get_ipv4_addresses() to get
|
||||
parallel list of interface names to go with ...
|
||||
"""
|
||||
if hostname == 'localhost':
|
||||
iface_list = []
|
||||
cmd = (constants.IP_BIN, '-o', '-f', 'inet', 'addr', 'show')
|
||||
output = subprocess.check_output(cmd)
|
||||
for line in output.split(os.linesep):
|
||||
split = line.split()
|
||||
if not split:
|
||||
continue
|
||||
interface_name = split[1]
|
||||
addr = split[3]
|
||||
if not addr.startswith('127.'):
|
||||
iface_list.append(interface_name)
|
||||
return iface_list
|
||||
else:
|
||||
# TODO: handle other hosts
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def find_device(scenario, name):
|
||||
tag_name = ('device', 'host', 'router')
|
||||
for d in xmlutils.iter_descendants_with_attribute(scenario, tag_name, 'name', name):
|
||||
return d
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def find_interface(device, name):
|
||||
for i in xmlutils.iter_descendants_with_attribute(device, 'interface', 'name', name):
|
||||
return i
|
||||
return None
|
||||
|
||||
def add_deployment(self):
|
||||
testbed = self.dom.createElement('container')
|
||||
testbed.setAttribute('name', 'TestBed')
|
||||
testbed.setAttribute('id', 'TestBed')
|
||||
self.root.base_element.appendChild(testbed)
|
||||
nodelist = []
|
||||
for obj in self.session.objects.itervalues():
|
||||
if isinstance(obj, nodes.PyCoreNode):
|
||||
nodelist.append(obj)
|
||||
name = self.hostname
|
||||
ipv4_addresses = self.get_ipv4_addresses('localhost')
|
||||
iface_names = self.get_interface_names('localhost')
|
||||
testhost = self.add_physical_host(testbed, name, ipv4_addresses, iface_names)
|
||||
for n in nodelist:
|
||||
self.add_virtual_host(testhost, n)
|
||||
# TODO: handle other servers
|
||||
# servers = self.session.broker.getserverlist()
|
||||
# servers.remove('localhost')
|
||||
|
||||
def add_child_element(self, parent, tag_name):
|
||||
el = self.dom.createElement(tag_name)
|
||||
parent.appendChild(el)
|
||||
return el
|
||||
|
||||
def add_child_element_with_nameattr(self, parent, tag_name, name, setid=True):
|
||||
el = self.add_child_element(parent, tag_name)
|
||||
el.setAttribute('name', name)
|
||||
if setid:
|
||||
el.setAttribute('id', '%s/%s' % (parent.getAttribute('id'), name))
|
||||
return el
|
||||
|
||||
def add_address(self, parent, address_type, address_str, address_iface=None):
|
||||
el = self.add_child_element(parent, 'address')
|
||||
el.setAttribute('type', address_type)
|
||||
if address_iface is not None:
|
||||
el.setAttribute('iface', address_iface)
|
||||
el.appendChild(self.dom.createTextNode(address_str))
|
||||
return el
|
||||
|
||||
def add_type(self, parent, type_str):
|
||||
el = self.add_child_element(parent, 'type')
|
||||
el.appendChild(self.dom.createTextNode(type_str))
|
||||
return el
|
||||
|
||||
def add_platform(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'emanePlatform', name)
|
||||
return el
|
||||
|
||||
def add_transport(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'transport', name)
|
||||
return el
|
||||
|
||||
def add_nem(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'nem', name)
|
||||
return el
|
||||
|
||||
def add_parameter(self, parent, name, val):
|
||||
el = self.add_child_element_with_nameattr(parent, 'parameter', name, False)
|
||||
el.appendChild(self.dom.createTextNode(val))
|
||||
return el
|
||||
|
||||
def add_mapping(self, parent, maptype, mapref):
|
||||
el = self.add_child_element(parent, 'mapping')
|
||||
el.setAttribute('type', maptype)
|
||||
el.setAttribute('ref', mapref)
|
||||
return el
|
||||
|
||||
def add_host(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'testHost', name)
|
||||
return el
|
||||
|
||||
def add_physical_host(self, parent, name, ipv4_addresses, iface_names):
|
||||
el = self.add_host(parent, name)
|
||||
self.add_type(el, 'physical')
|
||||
for i in range(0, len(ipv4_addresses)):
|
||||
addr = ipv4_addresses[i]
|
||||
if iface_names:
|
||||
interface_name = iface_names[i]
|
||||
else:
|
||||
interface_name = None
|
||||
self.add_address(el, 'IPv4', addr, interface_name)
|
||||
return el
|
||||
|
||||
def add_virtual_host(self, parent, obj):
|
||||
assert isinstance(obj, nodes.PyCoreNode)
|
||||
el = self.add_host(parent, obj.name)
|
||||
device = self.find_device(self.root.base_element, obj.name)
|
||||
if device is None:
|
||||
logger.warn('corresponding XML device not found for %s' % obj.name)
|
||||
return
|
||||
self.add_mapping(device, 'testHost', el.getAttribute('id'))
|
||||
self.add_type(el, 'virtual')
|
||||
for netif in obj.netifs():
|
||||
for address in netif.addrlist:
|
||||
addr, slash, prefixlen = address.partition('/')
|
||||
if ipaddress.is_ipv4_address(addr):
|
||||
addr_type = 'IPv4'
|
||||
elif ipaddress.is_ipv6_address(addr):
|
||||
addr_type = 'IPv6'
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.add_address(el, addr_type, address, netif.name)
|
||||
if nodeutils.is_node(netif.net, NodeTypes.EMANE):
|
||||
nem = self.add_emane_interface(parent, el, netif)
|
||||
interface = self.find_interface(device, netif.name)
|
||||
self.add_mapping(interface, 'nem', nem.getAttribute('id'))
|
||||
return el
|
||||
|
||||
def add_emane_interface(self, physical_host, virtual_host, netif, platform_name='p1', transport_name='t1'):
|
||||
nemid = netif.net.nemidmap[netif]
|
||||
if emane.VERSION < emane.EMANE092:
|
||||
if self.platform is None:
|
||||
self.platform = \
|
||||
self.add_platform(physical_host, name=platform_name)
|
||||
platform = self.platform
|
||||
if self.transport is None:
|
||||
self.transport = \
|
||||
self.add_transport(physical_host, name=transport_name)
|
||||
transport = self.transport
|
||||
else:
|
||||
platform = self.add_platform(virtual_host, name=platform_name)
|
||||
transport = self.add_transport(virtual_host, name=transport_name)
|
||||
nem_name = 'nem%s' % nemid
|
||||
nem = self.add_nem(platform, nem_name)
|
||||
self.add_parameter(nem, 'nemid', str(nemid))
|
||||
self.add_mapping(transport, 'nem', nem.getAttribute('id'))
|
||||
return nem
|
46
daemon/core/xml/xmlparser.py
Normal file
46
daemon/core/xml/xmlparser.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from xml.dom.minidom import parse
|
||||
|
||||
from core.xml.xmlparser0 import CoreDocumentParser0
|
||||
from core.xml.xmlparser1 import CoreDocumentParser1
|
||||
from core.xml.xmlutils import get_first_child_by_tag_name
|
||||
|
||||
|
||||
class CoreVersionParser(object):
|
||||
"""
|
||||
Helper class to check the version of Network Plan document. This
|
||||
simply looks for a "Scenario" element; when present, this
|
||||
indicates a 0.0 version document. The dom member is set in order
|
||||
to prevent parsing a file twice (it can be passed to the
|
||||
appropriate CoreDocumentParser class.)
|
||||
"""
|
||||
|
||||
DEFAULT_SCENARIO_VERSION = '1.0'
|
||||
|
||||
def __init__(self, filename, options):
|
||||
if 'dom' in options:
|
||||
self.dom = options['dom']
|
||||
else:
|
||||
self.dom = parse(filename)
|
||||
scenario = get_first_child_by_tag_name(self.dom, 'scenario')
|
||||
if scenario:
|
||||
version = scenario.getAttribute('version')
|
||||
if not version:
|
||||
version = self.DEFAULT_SCENARIO_VERSION
|
||||
self.version = version
|
||||
elif get_first_child_by_tag_name(self.dom, 'Scenario'):
|
||||
self.version = '0.0'
|
||||
else:
|
||||
self.version = 'unknown'
|
||||
|
||||
|
||||
def core_document_parser(session, filename, options):
|
||||
vp = CoreVersionParser(filename, options)
|
||||
if 'dom' not in options:
|
||||
options['dom'] = vp.dom
|
||||
if vp.version == '0.0':
|
||||
doc = CoreDocumentParser0(session, filename, options)
|
||||
elif vp.version == '1.0':
|
||||
doc = CoreDocumentParser1(session, filename, options)
|
||||
else:
|
||||
raise ValueError, 'unsupported document version: %s' % vp.version
|
||||
return doc
|
410
daemon/core/xml/xmlparser0.py
Normal file
410
daemon/core/xml/xmlparser0.py
Normal file
|
@ -0,0 +1,410 @@
|
|||
from xml.dom.minidom import parse
|
||||
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.service import ServiceManager
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDocumentParser0(object):
|
||||
def __init__(self, session, filename, options):
|
||||
self.session = session
|
||||
self.filename = filename
|
||||
if 'dom' in options:
|
||||
# this prevents parsing twice when detecting file versions
|
||||
self.dom = options['dom']
|
||||
else:
|
||||
self.dom = parse(filename)
|
||||
self.start = options['start']
|
||||
self.nodecls = options['nodecls']
|
||||
|
||||
self.np = xmlutils.get_one_element(self.dom, "NetworkPlan")
|
||||
if self.np is None:
|
||||
raise ValueError, "missing NetworkPlan!"
|
||||
self.mp = xmlutils.get_one_element(self.dom, "MotionPlan")
|
||||
self.sp = xmlutils.get_one_element(self.dom, "ServicePlan")
|
||||
self.meta = xmlutils.get_one_element(self.dom, "CoreMetaData")
|
||||
|
||||
self.coords = self.getmotiondict(self.mp)
|
||||
# link parameters parsed in parsenets(), applied in parsenodes()
|
||||
self.linkparams = {}
|
||||
|
||||
self.parsedefaultservices()
|
||||
self.parseorigin()
|
||||
self.parsenets()
|
||||
self.parsenodes()
|
||||
self.parseservices()
|
||||
self.parsemeta()
|
||||
|
||||
def getmotiondict(self, mp):
|
||||
"""
|
||||
Parse a MotionPlan into a dict with node names for keys and coordinates
|
||||
for values.
|
||||
"""
|
||||
if mp is None:
|
||||
return {}
|
||||
coords = {}
|
||||
for node in mp.getElementsByTagName("Node"):
|
||||
nodename = str(node.getAttribute("name"))
|
||||
if nodename == '':
|
||||
continue
|
||||
for m in node.getElementsByTagName("motion"):
|
||||
if m.getAttribute("type") != "stationary":
|
||||
continue
|
||||
point = m.getElementsByTagName("point")
|
||||
if len(point) == 0:
|
||||
continue
|
||||
txt = point[0].firstChild
|
||||
if txt is None:
|
||||
continue
|
||||
xyz = map(int, txt.nodeValue.split(','))
|
||||
z = None
|
||||
x, y = xyz[0:2]
|
||||
if len(xyz) == 3:
|
||||
z = xyz[2]
|
||||
coords[nodename] = (x, y, z)
|
||||
return coords
|
||||
|
||||
@staticmethod
|
||||
def getcommonattributes(obj):
|
||||
"""
|
||||
Helper to return tuple of attributes common to nodes and nets.
|
||||
"""
|
||||
id = int(obj.getAttribute("id"))
|
||||
name = str(obj.getAttribute("name"))
|
||||
type = str(obj.getAttribute("type"))
|
||||
return id, name, type
|
||||
|
||||
def parsenets(self):
|
||||
linkednets = []
|
||||
for net in self.np.getElementsByTagName("NetworkDefinition"):
|
||||
id, name, type = self.getcommonattributes(net)
|
||||
nodecls = xmlutils.xml_type_to_node_class(self.session, type)
|
||||
if not nodecls:
|
||||
logger.warn("skipping unknown network node '%s' type '%s'", name, type)
|
||||
continue
|
||||
n = self.session.add_object(cls=nodecls, objid=id, name=name, start=self.start)
|
||||
if name in self.coords:
|
||||
x, y, z = self.coords[name]
|
||||
n.setposition(x, y, z)
|
||||
xmlutils.get_params_set_attrs(net, ("icon", "canvas", "opaque"), n)
|
||||
if hasattr(n, "canvas") and n.canvas is not None:
|
||||
n.canvas = int(n.canvas)
|
||||
# links between two nets (e.g. switch-switch)
|
||||
for ifc in net.getElementsByTagName("interface"):
|
||||
netid = str(ifc.getAttribute("net"))
|
||||
ifcname = str(ifc.getAttribute("name"))
|
||||
linkednets.append((n, netid, ifcname))
|
||||
self.parsemodels(net, n)
|
||||
# link networks together now that they all have been parsed
|
||||
for n, netid, ifcname in linkednets:
|
||||
try:
|
||||
n2 = n.session.get_object_by_name(netid)
|
||||
except KeyError:
|
||||
logger.warn("skipping net %s interface: unknown net %s", n.name, netid)
|
||||
continue
|
||||
upstream = False
|
||||
netif = n.getlinknetif(n2)
|
||||
if netif is None:
|
||||
netif = n2.linknet(n)
|
||||
else:
|
||||
netif.swapparams('_params_up')
|
||||
upstream = True
|
||||
key = (n2.name, ifcname)
|
||||
if key in self.linkparams:
|
||||
for k, v in self.linkparams[key]:
|
||||
netif.setparam(k, v)
|
||||
if upstream:
|
||||
netif.swapparams('_params_up')
|
||||
|
||||
def parsenodes(self):
|
||||
for node in self.np.getElementsByTagName("Node"):
|
||||
id, name, type = self.getcommonattributes(node)
|
||||
if type == "rj45":
|
||||
nodecls = nodeutils.get_node_class(NodeTypes.RJ45)
|
||||
else:
|
||||
nodecls = self.nodecls
|
||||
n = self.session.add_object(cls=nodecls, objid=id, name=name, start=self.start)
|
||||
if name in self.coords:
|
||||
x, y, z = self.coords[name]
|
||||
n.setposition(x, y, z)
|
||||
n.type = type
|
||||
xmlutils.get_params_set_attrs(node, ("icon", "canvas", "opaque"), n)
|
||||
if hasattr(n, "canvas") and n.canvas is not None:
|
||||
n.canvas = int(n.canvas)
|
||||
for ifc in node.getElementsByTagName("interface"):
|
||||
self.parseinterface(n, ifc)
|
||||
|
||||
def parseinterface(self, n, ifc):
|
||||
"""
|
||||
Parse a interface block such as:
|
||||
<interface name="eth0" net="37278">
|
||||
<address type="mac">00:00:00:aa:00:01</address>
|
||||
<address>10.0.0.2/24</address>
|
||||
<address>2001::2/64</address>
|
||||
</interface>
|
||||
"""
|
||||
name = str(ifc.getAttribute("name"))
|
||||
netid = str(ifc.getAttribute("net"))
|
||||
hwaddr = None
|
||||
addrlist = []
|
||||
try:
|
||||
net = n.session.get_object_by_name(netid)
|
||||
except KeyError:
|
||||
logger.warn("skipping node %s interface %s: unknown net %s", n.name, name, netid)
|
||||
return
|
||||
for addr in ifc.getElementsByTagName("address"):
|
||||
addrstr = xmlutils.get_text_child(addr)
|
||||
if addrstr is None:
|
||||
continue
|
||||
if addr.getAttribute("type") == "mac":
|
||||
hwaddr = addrstr
|
||||
else:
|
||||
addrlist.append(addrstr)
|
||||
i = n.newnetif(net, addrlist=addrlist, hwaddr=hwaddr, ifindex=None, ifname=name)
|
||||
for model in ifc.getElementsByTagName("model"):
|
||||
self.parsemodel(model, n, n.objid)
|
||||
key = (n.name, name)
|
||||
if key in self.linkparams:
|
||||
netif = n.netif(i)
|
||||
for k, v in self.linkparams[key]:
|
||||
netif.setparam(k, v)
|
||||
|
||||
def parsemodels(self, dom, obj):
|
||||
"""
|
||||
Mobility/wireless model config is stored in a ConfigurableManager's
|
||||
config dict.
|
||||
"""
|
||||
nodenum = int(dom.getAttribute("id"))
|
||||
for model in dom.getElementsByTagName("model"):
|
||||
self.parsemodel(model, obj, nodenum)
|
||||
|
||||
def parsemodel(self, model, obj, nodenum):
|
||||
"""
|
||||
Mobility/wireless model config is stored in a ConfigurableManager's
|
||||
config dict.
|
||||
"""
|
||||
name = model.getAttribute("name")
|
||||
if name == '':
|
||||
return
|
||||
type = model.getAttribute("type")
|
||||
# convert child text nodes into key=value pairs
|
||||
kvs = xmlutils.get_text_elements_to_list(model)
|
||||
|
||||
mgr = self.session.mobility
|
||||
# TODO: the session.confobj() mechanism could be more generic;
|
||||
# it only allows registering Conf Message callbacks, but here
|
||||
# we want access to the ConfigurableManager, not the callback
|
||||
if name[:5] == "emane":
|
||||
mgr = self.session.emane
|
||||
elif name[:5] == "netem":
|
||||
mgr = None
|
||||
self.parsenetem(model, obj, kvs)
|
||||
|
||||
elif name[:3] == "xen":
|
||||
mgr = self.session.xen
|
||||
# TODO: assign other config managers here
|
||||
if mgr:
|
||||
mgr.setconfig_keyvalues(nodenum, name, kvs)
|
||||
|
||||
def parsenetem(self, model, obj, kvs):
|
||||
"""
|
||||
Determine interface and invoke setparam() using the parsed
|
||||
(key, value) pairs.
|
||||
"""
|
||||
ifname = model.getAttribute("netif")
|
||||
peer = model.getAttribute("peer")
|
||||
key = (peer, ifname)
|
||||
# nodes and interfaces do not exist yet, at this point of the parsing,
|
||||
# save (key, value) pairs for later
|
||||
try:
|
||||
# kvs = map(lambda(k, v): (int(v)), kvs)
|
||||
kvs = map(self.numericvalue, kvs)
|
||||
except ValueError:
|
||||
logger.warn("error parsing link parameters for '%s' on '%s'", ifname, peer)
|
||||
self.linkparams[key] = kvs
|
||||
|
||||
@staticmethod
|
||||
def numericvalue(keyvalue):
|
||||
(key, value) = keyvalue
|
||||
if '.' in str(value):
|
||||
value = float(value)
|
||||
else:
|
||||
value = int(value)
|
||||
return key, value
|
||||
|
||||
def parseorigin(self):
|
||||
"""
|
||||
Parse any origin tag from the Mobility Plan and set the CoreLocation
|
||||
reference point appropriately.
|
||||
"""
|
||||
origin = xmlutils.get_one_element(self.mp, "origin")
|
||||
if not origin:
|
||||
return
|
||||
location = self.session.location
|
||||
geo = []
|
||||
attrs = ("lat", "lon", "alt")
|
||||
for i in xrange(3):
|
||||
a = origin.getAttribute(attrs[i])
|
||||
if a is not None:
|
||||
a = float(a)
|
||||
geo.append(a)
|
||||
location.setrefgeo(geo[0], geo[1], geo[2])
|
||||
scale = origin.getAttribute("scale100")
|
||||
if scale is not None:
|
||||
location.refscale = float(scale)
|
||||
point = xmlutils.get_one_element(origin, "point")
|
||||
if point is not None and point.firstChild is not None:
|
||||
xyz = point.firstChild.nodeValue.split(',')
|
||||
if len(xyz) == 2:
|
||||
xyz.append('0.0')
|
||||
if len(xyz) == 3:
|
||||
xyz = map(lambda (x): float(x), xyz)
|
||||
location.refxyz = (xyz[0], xyz[1], xyz[2])
|
||||
|
||||
def parsedefaultservices(self):
|
||||
"""
|
||||
Prior to parsing nodes, use session.services manager to store
|
||||
default services for node types
|
||||
"""
|
||||
for node in self.sp.getElementsByTagName("Node"):
|
||||
type = node.getAttribute("type")
|
||||
if type == '':
|
||||
continue # node-specific service config
|
||||
services = []
|
||||
for service in node.getElementsByTagName("Service"):
|
||||
services.append(str(service.getAttribute("name")))
|
||||
self.session.services.defaultservices[type] = services
|
||||
logger.info("default services for type %s set to %s" % (type, services))
|
||||
|
||||
def parseservices(self):
|
||||
"""
|
||||
After node objects exist, parse service customizations and add them
|
||||
to the nodes.
|
||||
"""
|
||||
svclists = {}
|
||||
# parse services and store configs into session.services.configs
|
||||
for node in self.sp.getElementsByTagName("Node"):
|
||||
name = node.getAttribute("name")
|
||||
if name == '':
|
||||
continue # node type without name
|
||||
n = self.session.get_object_by_name(name)
|
||||
if n is None:
|
||||
logger.warn("skipping service config for unknown node '%s'" % name)
|
||||
continue
|
||||
for service in node.getElementsByTagName("Service"):
|
||||
svcname = service.getAttribute("name")
|
||||
if self.parseservice(service, n):
|
||||
if n.objid in svclists:
|
||||
svclists[n.objid] += "|" + svcname
|
||||
else:
|
||||
svclists[n.objid] = svcname
|
||||
# nodes in NetworkPlan but not in ServicePlan use the
|
||||
# default services for their type
|
||||
for node in self.np.getElementsByTagName("Node"):
|
||||
id, name, type = self.getcommonattributes(node)
|
||||
if id in svclists:
|
||||
continue # custom config exists
|
||||
else:
|
||||
svclists[int(id)] = None # use defaults
|
||||
|
||||
# associate nodes with services
|
||||
for objid in sorted(svclists.keys()):
|
||||
n = self.session.get_object(objid)
|
||||
self.session.services.addservicestonode(node=n, nodetype=n.type, services_str=svclists[objid])
|
||||
|
||||
def parseservice(self, service, n):
|
||||
"""
|
||||
Use session.services manager to store service customizations before
|
||||
they are added to a node.
|
||||
"""
|
||||
name = service.getAttribute("name")
|
||||
svc = ServiceManager.get(name)
|
||||
if svc is None:
|
||||
return False
|
||||
values = []
|
||||
startup_idx = service.getAttribute("startup_idx")
|
||||
if startup_idx is not None:
|
||||
values.append("startidx=%s" % startup_idx)
|
||||
startup_time = service.getAttribute("start_time")
|
||||
if startup_time is not None:
|
||||
values.append("starttime=%s" % startup_time)
|
||||
dirs = []
|
||||
for dir in service.getElementsByTagName("Directory"):
|
||||
dirname = dir.getAttribute("name")
|
||||
dirs.append(dirname)
|
||||
if len(dirs):
|
||||
values.append("dirs=%s" % dirs)
|
||||
|
||||
startup = []
|
||||
shutdown = []
|
||||
validate = []
|
||||
for cmd in service.getElementsByTagName("Command"):
|
||||
type = cmd.getAttribute("type")
|
||||
cmdstr = xmlutils.get_text_child(cmd)
|
||||
if cmdstr is None:
|
||||
continue
|
||||
if type == "start":
|
||||
startup.append(cmdstr)
|
||||
elif type == "stop":
|
||||
shutdown.append(cmdstr)
|
||||
elif type == "validate":
|
||||
validate.append(cmdstr)
|
||||
if len(startup):
|
||||
values.append("cmdup=%s" % startup)
|
||||
if len(shutdown):
|
||||
values.append("cmddown=%s" % shutdown)
|
||||
if len(validate):
|
||||
values.append("cmdval=%s" % validate)
|
||||
|
||||
files = []
|
||||
for file in service.getElementsByTagName("File"):
|
||||
filename = file.getAttribute("name")
|
||||
files.append(filename)
|
||||
data = xmlutils.get_text_child(file)
|
||||
typestr = "service:%s:%s" % (name, filename)
|
||||
self.session.services.setservicefile(nodenum=n.objid, type=typestr,
|
||||
filename=filename,
|
||||
srcname=None, data=data)
|
||||
if len(files):
|
||||
values.append("files=%s" % files)
|
||||
if not bool(service.getAttribute("custom")):
|
||||
return True
|
||||
self.session.services.setcustomservice(n.objid, svc, values)
|
||||
return True
|
||||
|
||||
def parsehooks(self, hooks):
|
||||
''' Parse hook scripts from XML into session._hooks.
|
||||
'''
|
||||
for hook in hooks.getElementsByTagName("Hook"):
|
||||
filename = hook.getAttribute("name")
|
||||
state = hook.getAttribute("state")
|
||||
data = xmlutils.get_text_child(hook)
|
||||
if data is None:
|
||||
data = "" # allow for empty file
|
||||
type = "hook:%s" % state
|
||||
self.session.set_hook(type, file_name=filename, source_name=None, data=data)
|
||||
|
||||
def parsemeta(self):
|
||||
opt = xmlutils.get_one_element(self.meta, "SessionOptions")
|
||||
if opt:
|
||||
for param in opt.getElementsByTagName("param"):
|
||||
k = str(param.getAttribute("name"))
|
||||
v = str(param.getAttribute("value"))
|
||||
if v == '':
|
||||
v = xmlutils.get_text_child(param) # allow attribute/text for newlines
|
||||
setattr(self.session.options, k, v)
|
||||
hooks = xmlutils.get_one_element(self.meta, "Hooks")
|
||||
if hooks:
|
||||
self.parsehooks(hooks)
|
||||
meta = xmlutils.get_one_element(self.meta, "MetaData")
|
||||
if meta:
|
||||
for param in meta.getElementsByTagName("param"):
|
||||
k = str(param.getAttribute("name"))
|
||||
v = str(param.getAttribute("value"))
|
||||
if v == '':
|
||||
v = xmlutils.get_text_child(param)
|
||||
self.session.metadata.add_item(k, v)
|
878
daemon/core/xml/xmlparser1.py
Normal file
878
daemon/core/xml/xmlparser1.py
Normal file
|
@ -0,0 +1,878 @@
|
|||
import random
|
||||
from xml.dom.minidom import Node
|
||||
from xml.dom.minidom import parse
|
||||
|
||||
from core import constants
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.misc.ipaddress import MacAddress
|
||||
from core.service import ServiceManager
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDocumentParser1(object):
|
||||
layer2_device_types = 'hub', 'switch'
|
||||
layer3_device_types = 'host', 'router'
|
||||
device_types = layer2_device_types + layer3_device_types
|
||||
|
||||
# TODO: support CORE interface classes:
|
||||
# RJ45Node
|
||||
# TunnelNode
|
||||
|
||||
def __init__(self, session, filename, options):
|
||||
"""
|
||||
|
||||
:param core.session.Session session:
|
||||
:param filename:
|
||||
:param options:
|
||||
:return:
|
||||
"""
|
||||
self.session = session
|
||||
self.filename = filename
|
||||
if 'dom' in options:
|
||||
# this prevents parsing twice when detecting file versions
|
||||
self.dom = options['dom']
|
||||
else:
|
||||
self.dom = parse(filename)
|
||||
self.start = options['start']
|
||||
self.nodecls = options['nodecls']
|
||||
self.scenario = self.get_scenario(self.dom)
|
||||
self.location_refgeo_set = False
|
||||
self.location_refxyz_set = False
|
||||
# saved link parameters saved when parsing networks and applied later
|
||||
self.link_params = {}
|
||||
# map from id-string to objid, for files having node names but
|
||||
# not node numbers
|
||||
self.objidmap = {}
|
||||
self.objids = set()
|
||||
self.default_services = {}
|
||||
if self.scenario:
|
||||
self.parse_scenario()
|
||||
|
||||
@staticmethod
|
||||
def get_scenario(dom):
|
||||
scenario = xmlutils.get_first_child_by_tag_name(dom, 'scenario')
|
||||
if not scenario:
|
||||
raise ValueError, 'no scenario element found'
|
||||
version = scenario.getAttribute('version')
|
||||
if version and version != '1.0':
|
||||
raise ValueError, \
|
||||
'unsupported scenario version found: \'%s\'' % version
|
||||
return scenario
|
||||
|
||||
def parse_scenario(self):
|
||||
self.parse_default_services()
|
||||
self.parse_session_config()
|
||||
self.parse_network_plan()
|
||||
|
||||
def assign_id(self, idstr, idval):
|
||||
if idstr in self.objidmap:
|
||||
assert self.objidmap[idstr] == idval and idval in self.objids
|
||||
return
|
||||
self.objidmap[idstr] = idval
|
||||
self.objids.add(idval)
|
||||
|
||||
def rand_id(self):
|
||||
while True:
|
||||
x = random.randint(0, 0xffff)
|
||||
if x not in self.objids:
|
||||
return x
|
||||
|
||||
def get_id(self, idstr):
|
||||
"""
|
||||
Get a, possibly new, object id (node number) corresponding to
|
||||
the given XML string id.
|
||||
"""
|
||||
if not idstr:
|
||||
idn = self.rand_id()
|
||||
self.objids.add(idn)
|
||||
return idn
|
||||
elif idstr in self.objidmap:
|
||||
return self.objidmap[idstr]
|
||||
else:
|
||||
try:
|
||||
idn = int(idstr)
|
||||
except ValueError:
|
||||
idn = self.rand_id()
|
||||
self.assign_id(idstr, idn)
|
||||
return idn
|
||||
|
||||
def get_common_attributes(self, node):
|
||||
"""
|
||||
Return id, name attributes for the given XML element. These
|
||||
attributes are common to nodes and networks.
|
||||
"""
|
||||
idstr = node.getAttribute('id')
|
||||
# use an explicit set COREID if it exists
|
||||
coreid = self.find_core_id(node)
|
||||
|
||||
if coreid:
|
||||
idn = int(coreid)
|
||||
if idstr:
|
||||
self.assign_id(idstr, idn)
|
||||
else:
|
||||
idn = self.get_id(idstr)
|
||||
|
||||
# TODO: consider supporting unicode; for now convert to an
|
||||
# ascii string
|
||||
namestr = str(node.getAttribute('name'))
|
||||
return idn, namestr
|
||||
|
||||
def iter_network_member_devices(self, element):
|
||||
# element can be a network or a channel
|
||||
for interface in xmlutils.iter_children_with_attribute(element, 'member', 'type', 'interface'):
|
||||
if_id = xmlutils.get_child_text_trim(interface)
|
||||
assert if_id # XXX for testing
|
||||
if not if_id:
|
||||
continue
|
||||
device, if_name = self.find_device_with_interface(if_id)
|
||||
assert device, 'no device for if_id: %s' % if_id # XXX for testing
|
||||
if device:
|
||||
yield device, if_name
|
||||
|
||||
def network_class(self, network, network_type):
|
||||
"""
|
||||
Return the corresponding CORE network class for the given
|
||||
network/network_type.
|
||||
"""
|
||||
if network_type in ['ethernet', 'satcom']:
|
||||
return nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
|
||||
elif network_type == 'wireless':
|
||||
channel = xmlutils.get_first_child_by_tag_name(network, 'channel')
|
||||
if channel:
|
||||
# use an explicit CORE type if it exists
|
||||
coretype = xmlutils.get_first_child_text_trim_with_attribute(channel, 'type', 'domain', 'CORE')
|
||||
if coretype:
|
||||
if coretype == 'basic_range':
|
||||
return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
|
||||
elif coretype.startswith('emane'):
|
||||
return nodeutils.get_node_class(NodeTypes.EMANE)
|
||||
else:
|
||||
logger.warn('unknown network type: \'%s\'', coretype)
|
||||
return xmlutils.xml_type_to_node_class(self.session, coretype)
|
||||
return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
|
||||
logger.warn('unknown network type: \'%s\'', network_type)
|
||||
return None
|
||||
|
||||
def create_core_object(self, objcls, objid, objname, element, node_type):
|
||||
obj = self.session.add_object(cls=objcls, objid=objid, name=objname, start=self.start)
|
||||
logger.info('added object objid=%s name=%s cls=%s' % (objid, objname, objcls))
|
||||
self.set_object_position(obj, element)
|
||||
self.set_object_presentation(obj, element, node_type)
|
||||
return obj
|
||||
|
||||
def get_core_object(self, idstr):
|
||||
if idstr and idstr in self.objidmap:
|
||||
objid = self.objidmap[idstr]
|
||||
return self.session.get_object(objid)
|
||||
return None
|
||||
|
||||
def parse_network_plan(self):
|
||||
# parse the scenario in the following order:
|
||||
# 1. layer-2 devices
|
||||
# 2. other networks (ptp/wlan)
|
||||
# 3. layer-3 devices
|
||||
self.parse_layer2_devices()
|
||||
self.parse_networks()
|
||||
self.parse_layer3_devices()
|
||||
|
||||
def set_ethernet_link_parameters(self, channel, link_params, mobility_model_name, mobility_params):
|
||||
# save link parameters for later use, indexed by the tuple
|
||||
# (device_id, interface_name)
|
||||
for dev, if_name in self.iter_network_member_devices(channel):
|
||||
if self.device_type(dev) in self.device_types:
|
||||
dev_id = dev.getAttribute('id')
|
||||
key = (dev_id, if_name)
|
||||
self.link_params[key] = link_params
|
||||
if mobility_model_name or mobility_params:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_wireless_link_parameters(self, channel, link_params, mobility_model_name, mobility_params):
|
||||
network = self.find_channel_network(channel)
|
||||
network_id = network.getAttribute('id')
|
||||
if network_id in self.objidmap:
|
||||
nodenum = self.objidmap[network_id]
|
||||
else:
|
||||
logger.warn('unknown network: %s', network.toxml('utf-8'))
|
||||
assert False # XXX for testing
|
||||
model_name = xmlutils.get_first_child_text_trim_with_attribute(channel, 'type', 'domain', 'CORE')
|
||||
if not model_name:
|
||||
model_name = 'basic_range'
|
||||
if model_name == 'basic_range':
|
||||
mgr = self.session.mobility
|
||||
elif model_name.startswith('emane'):
|
||||
mgr = self.session.emane
|
||||
elif model_name.startswith('xen'):
|
||||
mgr = self.session.xen
|
||||
else:
|
||||
# TODO: any other config managers?
|
||||
raise NotImplementedError
|
||||
mgr.setconfig_keyvalues(nodenum, model_name, link_params.items())
|
||||
if mobility_model_name and mobility_params:
|
||||
mgr.setconfig_keyvalues(nodenum, mobility_model_name, mobility_params.items())
|
||||
|
||||
def link_layer2_devices(self, device1, ifname1, device2, ifname2):
|
||||
"""
|
||||
Link two layer-2 devices together.
|
||||
"""
|
||||
devid1 = device1.getAttribute('id')
|
||||
dev1 = self.get_core_object(devid1)
|
||||
devid2 = device2.getAttribute('id')
|
||||
dev2 = self.get_core_object(devid2)
|
||||
assert dev1 and dev2 # XXX for testing
|
||||
if dev1 and dev2:
|
||||
# TODO: review this
|
||||
if nodeutils.is_node(dev2, NodeTypes.RJ45):
|
||||
# RJ45 nodes have different linknet()
|
||||
netif = dev2.linknet(dev1)
|
||||
else:
|
||||
netif = dev1.linknet(dev2)
|
||||
self.set_wired_link_parameters(dev1, netif, devid1, ifname1)
|
||||
|
||||
@classmethod
|
||||
def parse_xml_value(cls, valtext):
|
||||
if not valtext:
|
||||
return None
|
||||
try:
|
||||
if not valtext.translate(None, '0123456789'):
|
||||
val = int(valtext)
|
||||
else:
|
||||
val = float(valtext)
|
||||
except ValueError:
|
||||
val = str(valtext)
|
||||
return val
|
||||
|
||||
@classmethod
|
||||
def parse_parameter_children(cls, parent):
|
||||
params = {}
|
||||
for parameter in xmlutils.iter_children_with_name(parent, 'parameter'):
|
||||
param_name = parameter.getAttribute('name')
|
||||
assert param_name # XXX for testing
|
||||
if not param_name:
|
||||
continue
|
||||
# TODO: consider supporting unicode; for now convert
|
||||
# to an ascii string
|
||||
param_name = str(param_name)
|
||||
param_val = cls.parse_xml_value(xmlutils.get_child_text_trim(parameter))
|
||||
# TODO: check if the name already exists?
|
||||
if param_name and param_val:
|
||||
params[param_name] = param_val
|
||||
return params
|
||||
|
||||
def parse_network_channel(self, channel):
|
||||
element = self.search_for_element(channel, 'type', lambda x: not x.hasAttributes())
|
||||
channel_type = xmlutils.get_child_text_trim(element)
|
||||
link_params = self.parse_parameter_children(channel)
|
||||
|
||||
mobility = xmlutils.get_first_child_by_tag_name(channel, 'CORE:mobility')
|
||||
if mobility:
|
||||
mobility_model_name = xmlutils.get_first_child_text_trim_by_tag_name(mobility, 'type')
|
||||
mobility_params = self.parse_parameter_children(mobility)
|
||||
else:
|
||||
mobility_model_name = None
|
||||
mobility_params = None
|
||||
if channel_type == 'wireless':
|
||||
self.set_wireless_link_parameters(channel, link_params, mobility_model_name, mobility_params)
|
||||
elif channel_type == 'ethernet':
|
||||
# TODO: maybe this can be done in the loop below to avoid
|
||||
# iterating through channel members multiple times
|
||||
self.set_ethernet_link_parameters(channel, link_params, mobility_model_name, mobility_params)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
layer2_device = []
|
||||
for dev, if_name in self.iter_network_member_devices(channel):
|
||||
if self.device_type(dev) in self.layer2_device_types:
|
||||
layer2_device.append((dev, if_name))
|
||||
assert len(layer2_device) <= 2
|
||||
if len(layer2_device) == 2:
|
||||
self.link_layer2_devices(layer2_device[0][0], layer2_device[0][1],
|
||||
layer2_device[1][0], layer2_device[1][1])
|
||||
|
||||
def parse_network(self, network):
|
||||
"""
|
||||
Each network element should have an 'id' and 'name' attribute
|
||||
and include the following child elements:
|
||||
|
||||
type (one)
|
||||
member (zero or more with type="interface" or type="channel")
|
||||
channel (zero or more)
|
||||
"""
|
||||
layer2_members = set()
|
||||
layer3_members = 0
|
||||
for dev, if_name in self.iter_network_member_devices(network):
|
||||
if not dev:
|
||||
continue
|
||||
devtype = self.device_type(dev)
|
||||
if devtype in self.layer2_device_types:
|
||||
layer2_members.add(dev)
|
||||
elif devtype in self.layer3_device_types:
|
||||
layer3_members += 1
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if len(layer2_members) == 0:
|
||||
net_type = xmlutils.get_first_child_text_trim_by_tag_name(network, 'type')
|
||||
if not net_type:
|
||||
logger.warn('no network type found for network: \'%s\'', network.toxml('utf-8'))
|
||||
assert False # XXX for testing
|
||||
net_cls = self.network_class(network, net_type)
|
||||
objid, net_name = self.get_common_attributes(network)
|
||||
logger.info('parsing network: name=%s id=%s' % (net_name, objid))
|
||||
if objid in self.session.objects:
|
||||
return
|
||||
n = self.create_core_object(net_cls, objid, net_name, network, None)
|
||||
|
||||
# handle channel parameters
|
||||
for channel in xmlutils.iter_children_with_name(network, 'channel'):
|
||||
self.parse_network_channel(channel)
|
||||
|
||||
def parse_networks(self):
|
||||
"""
|
||||
Parse all 'network' elements.
|
||||
"""
|
||||
for network in xmlutils.iter_descendants_with_name(self.scenario, 'network'):
|
||||
self.parse_network(network)
|
||||
|
||||
def parse_addresses(self, interface):
|
||||
mac = []
|
||||
ipv4 = []
|
||||
ipv6 = []
|
||||
hostname = []
|
||||
for address in xmlutils.iter_children_with_name(interface, 'address'):
|
||||
addr_type = address.getAttribute('type')
|
||||
if not addr_type:
|
||||
msg = 'no type attribute found for address ' \
|
||||
'in interface: \'%s\'' % interface.toxml('utf-8')
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
addr_text = xmlutils.get_child_text_trim(address)
|
||||
if not addr_text:
|
||||
msg = 'no text found for address ' \
|
||||
'in interface: \'%s\'' % interface.toxml('utf-8')
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
if addr_type == 'mac':
|
||||
mac.append(addr_text)
|
||||
elif addr_type == 'IPv4':
|
||||
ipv4.append(addr_text)
|
||||
elif addr_type == 'IPv6':
|
||||
ipv6.append(addr_text)
|
||||
elif addr_type == 'hostname':
|
||||
hostname.append(addr_text)
|
||||
else:
|
||||
msg = 'skipping unknown address type \'%s\' in ' \
|
||||
'interface: \'%s\'' % (addr_type, interface.toxml('utf-8'))
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
return mac, ipv4, ipv6, hostname
|
||||
|
||||
def parse_interface(self, node, device_id, interface):
|
||||
"""
|
||||
Each interface can have multiple 'address' elements.
|
||||
"""
|
||||
if_name = interface.getAttribute('name')
|
||||
network = self.find_interface_network_object(interface)
|
||||
if not network:
|
||||
msg = 'skipping node \'%s\' interface \'%s\': ' \
|
||||
'unknown network' % (node.name, if_name)
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
mac, ipv4, ipv6, hostname = self.parse_addresses(interface)
|
||||
if mac:
|
||||
hwaddr = MacAddress.from_string(mac[0])
|
||||
else:
|
||||
hwaddr = None
|
||||
ifindex = node.newnetif(network, addrlist=ipv4 + ipv6, hwaddr=hwaddr, ifindex=None, ifname=if_name)
|
||||
# TODO: 'hostname' addresses are unused
|
||||
msg = 'node \'%s\' interface \'%s\' connected ' \
|
||||
'to network \'%s\'' % (node.name, if_name, network.name)
|
||||
logger.info(msg)
|
||||
# set link parameters for wired links
|
||||
if nodeutils.is_node(network, (NodeTypes.HUB, NodeTypes.PEER_TO_PEER, NodeTypes.SWITCH)):
|
||||
netif = node.netif(ifindex)
|
||||
self.set_wired_link_parameters(network, netif, device_id)
|
||||
|
||||
def set_wired_link_parameters(self, network, netif, device_id, netif_name=None):
|
||||
if netif_name is None:
|
||||
netif_name = netif.name
|
||||
key = (device_id, netif_name)
|
||||
if key in self.link_params:
|
||||
link_params = self.link_params[key]
|
||||
if self.start:
|
||||
bw = link_params.get('bw')
|
||||
delay = link_params.get('delay')
|
||||
loss = link_params.get('loss')
|
||||
duplicate = link_params.get('duplicate')
|
||||
jitter = link_params.get('jitter')
|
||||
network.linkconfig(netif, bw=bw, delay=delay, loss=loss, duplicate=duplicate, jitter=jitter)
|
||||
else:
|
||||
for k, v in link_params.iteritems():
|
||||
netif.setparam(k, v)
|
||||
|
||||
@staticmethod
|
||||
def search_for_element(node, tag_name, match=None):
|
||||
"""
|
||||
Search the given node and all ancestors for an element named
|
||||
tagName that satisfies the given matching function.
|
||||
"""
|
||||
while True:
|
||||
for child in xmlutils.iter_children(node, Node.ELEMENT_NODE):
|
||||
if child.tagName == tag_name and (match is None or match(child)):
|
||||
return child
|
||||
node = node.parentNode
|
||||
if not node:
|
||||
break
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_core_id(cls, node):
|
||||
def match(x):
|
||||
domain = x.getAttribute('domain')
|
||||
return domain == 'COREID'
|
||||
|
||||
alias = cls.search_for_element(node, 'alias', match)
|
||||
if alias:
|
||||
return xmlutils.get_child_text_trim(alias)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_point(cls, node):
|
||||
return cls.search_for_element(node, 'point')
|
||||
|
||||
@staticmethod
|
||||
def find_channel_network(channel):
|
||||
p = channel.parentNode
|
||||
if p and p.tagName == 'network':
|
||||
return p
|
||||
return None
|
||||
|
||||
def find_interface_network_object(self, interface):
|
||||
network_id = xmlutils.get_first_child_text_trim_with_attribute(interface, 'member', 'type', 'network')
|
||||
if not network_id:
|
||||
# support legacy notation: <interface net="netid" ...
|
||||
network_id = interface.getAttribute('net')
|
||||
obj = self.get_core_object(network_id)
|
||||
if obj:
|
||||
# the network_id should exist for ptp or wlan/emane networks
|
||||
return obj
|
||||
# the network should correspond to a layer-2 device if the
|
||||
# network_id does not exist
|
||||
channel_id = xmlutils.get_first_child_text_trim_with_attribute(interface, 'member', 'type', 'channel')
|
||||
if not network_id or not channel_id:
|
||||
return None
|
||||
network = xmlutils.get_first_child_with_attribute(self.scenario, 'network', 'id', network_id)
|
||||
if not network:
|
||||
return None
|
||||
channel = xmlutils.get_first_child_with_attribute(network, 'channel', 'id', channel_id)
|
||||
if not channel:
|
||||
return None
|
||||
device = None
|
||||
for dev, if_name in self.iter_network_member_devices(channel):
|
||||
if self.device_type(dev) in self.layer2_device_types:
|
||||
assert not device # XXX
|
||||
device = dev
|
||||
if device:
|
||||
obj = self.get_core_object(device.getAttribute('id'))
|
||||
if obj:
|
||||
return obj
|
||||
return None
|
||||
|
||||
def set_object_position_pixel(self, obj, point):
|
||||
x = float(point.getAttribute('x'))
|
||||
y = float(point.getAttribute('y'))
|
||||
z = point.getAttribute('z')
|
||||
if z:
|
||||
z = float(z)
|
||||
else:
|
||||
z = 0.0
|
||||
# TODO: zMode is unused
|
||||
# z_mode = point.getAttribute('zMode'))
|
||||
if x < 0.0:
|
||||
logger.warn('limiting negative x position of \'%s\' to zero: %s' % (obj.name, x))
|
||||
x = 0.0
|
||||
if y < 0.0:
|
||||
logger.warn('limiting negative y position of \'%s\' to zero: %s' % (obj.name, y))
|
||||
y = 0.0
|
||||
obj.setposition(x, y, z)
|
||||
|
||||
def set_object_position_gps(self, obj, point):
|
||||
lat = float(point.getAttribute('lat'))
|
||||
lon = float(point.getAttribute('lon'))
|
||||
zalt = point.getAttribute('z')
|
||||
if zalt:
|
||||
zalt = float(zalt)
|
||||
else:
|
||||
zalt = 0.0
|
||||
# TODO: zMode is unused
|
||||
# z_mode = point.getAttribute('zMode'))
|
||||
if not self.location_refgeo_set:
|
||||
# for x,y,z conversion, we need a reasonable refpt; this
|
||||
# picks the first coordinates as the origin
|
||||
self.session.location.setrefgeo(lat, lon, zalt)
|
||||
self.location_refgeo_set = True
|
||||
x, y, z = self.session.location.getxyz(lat, lon, zalt)
|
||||
if x < 0.0:
|
||||
logger.warn('limiting negative x position of \'%s\' to zero: %s' % (obj.name, x))
|
||||
x = 0.0
|
||||
if y < 0.0:
|
||||
logger.warn('limiting negative y position of \'%s\' to zero: %s' % (obj.name, y))
|
||||
y = 0.0
|
||||
obj.setposition(x, y, z)
|
||||
|
||||
def set_object_position_cartesian(self, obj, point):
|
||||
# TODO: review this
|
||||
xm = float(point.getAttribute('x'))
|
||||
ym = float(point.getAttribute('y'))
|
||||
zm = point.getAttribute('z')
|
||||
if zm:
|
||||
zm = float(zm)
|
||||
else:
|
||||
zm = 0.0
|
||||
# TODO: zMode is unused
|
||||
# z_mode = point.getAttribute('zMode'))
|
||||
if not self.location_refxyz_set:
|
||||
self.session.location.refxyz = xm, ym, zm
|
||||
self.location_refxyz_set = True
|
||||
# need to convert meters to pixels
|
||||
x = self.session.location.m2px(xm) + self.session.location.refxyz[0]
|
||||
y = self.session.location.m2px(ym) + self.session.location.refxyz[1]
|
||||
z = self.session.location.m2px(zm) + self.session.location.refxyz[2]
|
||||
if x < 0.0:
|
||||
logger.warn('limiting negative x position of \'%s\' to zero: %s' % (obj.name, x))
|
||||
x = 0.0
|
||||
if y < 0.0:
|
||||
logger.warn('limiting negative y position of \'%s\' to zero: %s' % (obj.name, y))
|
||||
y = 0.0
|
||||
obj.setposition(x, y, z)
|
||||
|
||||
def set_object_position(self, obj, element):
|
||||
"""
|
||||
Set the x,y,x position of obj from the point associated with
|
||||
the given element.
|
||||
"""
|
||||
point = self.find_point(element)
|
||||
if not point:
|
||||
return False
|
||||
point_type = point.getAttribute('type')
|
||||
if not point_type:
|
||||
msg = 'no type attribute found for point: \'%s\'' % \
|
||||
point.toxml('utf-8')
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
elif point_type == 'pixel':
|
||||
self.set_object_position_pixel(obj, point)
|
||||
elif point_type == 'gps':
|
||||
self.set_object_position_gps(obj, point)
|
||||
elif point_type == 'cart':
|
||||
self.set_object_position_cartesian(obj, point)
|
||||
else:
|
||||
logger.warn("skipping unknown point type: '%s'" % point_type)
|
||||
assert False # XXX for testing
|
||||
|
||||
logger.info('set position of %s from point element: \'%s\'', obj.name, point.toxml('utf-8'))
|
||||
return True
|
||||
|
||||
def parse_device_service(self, service, node):
|
||||
name = service.getAttribute('name')
|
||||
session_service = ServiceManager.get(name)
|
||||
if not session_service:
|
||||
assert False # XXX for testing
|
||||
values = []
|
||||
startup_idx = service.getAttribute('startup_idx')
|
||||
if startup_idx:
|
||||
values.append('startidx=%s' % startup_idx)
|
||||
startup_time = service.getAttribute('start_time')
|
||||
if startup_time:
|
||||
values.append('starttime=%s' % startup_time)
|
||||
dirs = []
|
||||
for directory in xmlutils.iter_children_with_name(service, 'directory'):
|
||||
dirname = directory.getAttribute('name')
|
||||
dirs.append(str(dirname))
|
||||
if dirs:
|
||||
values.append("dirs=%s" % dirs)
|
||||
startup = []
|
||||
shutdown = []
|
||||
validate = []
|
||||
for command in xmlutils.iter_children_with_name(service, 'command'):
|
||||
command_type = command.getAttribute('type')
|
||||
command_text = xmlutils.get_child_text_trim(command)
|
||||
if not command_text:
|
||||
continue
|
||||
if command_type == 'start':
|
||||
startup.append(str(command_text))
|
||||
elif command_type == 'stop':
|
||||
shutdown.append(str(command_text))
|
||||
elif command_type == 'validate':
|
||||
validate.append(str(command_text))
|
||||
if startup:
|
||||
values.append('cmdup=%s' % startup)
|
||||
if shutdown:
|
||||
values.append('cmddown=%s' % shutdown)
|
||||
if validate:
|
||||
values.append('cmdval=%s' % validate)
|
||||
filenames = []
|
||||
files = []
|
||||
for f in xmlutils.iter_children_with_name(service, 'file'):
|
||||
filename = f.getAttribute('name')
|
||||
if not filename:
|
||||
continue
|
||||
filenames.append(filename)
|
||||
data = xmlutils.get_child_text_trim(f)
|
||||
if data:
|
||||
data = str(data)
|
||||
else:
|
||||
data = None
|
||||
typestr = 'service:%s:%s' % (name, filename)
|
||||
files.append((typestr, filename, data))
|
||||
if filenames:
|
||||
values.append('files=%s' % filenames)
|
||||
custom = service.getAttribute('custom')
|
||||
if custom and custom.lower() == 'true':
|
||||
self.session.services.setcustomservice(node.objid, session_service, values)
|
||||
# NOTE: if a custom service is used, setservicefile() must be
|
||||
# called after the custom service exists
|
||||
for typestr, filename, data in files:
|
||||
self.session.services.setservicefile(
|
||||
nodenum=node.objid,
|
||||
type=typestr,
|
||||
filename=filename,
|
||||
srcname=None,
|
||||
data=data
|
||||
)
|
||||
return str(name)
|
||||
|
||||
def parse_device_services(self, services, node):
|
||||
"""
|
||||
Use session.services manager to store service customizations
|
||||
before they are added to a node.
|
||||
"""
|
||||
service_names = []
|
||||
for service in xmlutils.iter_children_with_name(services, 'service'):
|
||||
name = self.parse_device_service(service, node)
|
||||
if name:
|
||||
service_names.append(name)
|
||||
return '|'.join(service_names)
|
||||
|
||||
def add_device_services(self, node, device, node_type):
|
||||
"""
|
||||
Add services to the given node.
|
||||
"""
|
||||
services = xmlutils.get_first_child_by_tag_name(device, 'CORE:services')
|
||||
if services:
|
||||
services_str = self.parse_device_services(services, node)
|
||||
logger.info('services for node \'%s\': %s' % (node.name, services_str))
|
||||
elif node_type in self.default_services:
|
||||
services_str = None # default services will be added
|
||||
else:
|
||||
return
|
||||
self.session.services.addservicestonode(
|
||||
node=node,
|
||||
nodetype=node_type,
|
||||
services_str=services_str
|
||||
)
|
||||
|
||||
def set_object_presentation(self, obj, element, node_type):
|
||||
# defaults from the CORE GUI
|
||||
default_icons = {
|
||||
'router': 'router.gif',
|
||||
'host': 'host.gif',
|
||||
'PC': 'pc.gif',
|
||||
'mdr': 'mdr.gif',
|
||||
# 'prouter': 'router_green.gif',
|
||||
# 'xen': 'xen.gif'
|
||||
}
|
||||
icon_set = False
|
||||
for child in xmlutils.iter_children_with_name(element, 'CORE:presentation'):
|
||||
canvas = child.getAttribute('canvas')
|
||||
if canvas:
|
||||
obj.canvas = int(canvas)
|
||||
icon = child.getAttribute('icon')
|
||||
if icon:
|
||||
icon = str(icon).replace("$CORE_DATA_DIR",
|
||||
constants.CORE_DATA_DIR)
|
||||
obj.icon = icon
|
||||
icon_set = True
|
||||
if not icon_set and node_type in default_icons:
|
||||
obj.icon = default_icons[node_type]
|
||||
|
||||
def device_type(self, device):
|
||||
if device.tagName in self.device_types:
|
||||
return device.tagName
|
||||
return None
|
||||
|
||||
def core_node_type(self, device):
|
||||
# use an explicit CORE type if it exists
|
||||
coretype = xmlutils.get_first_child_text_trim_with_attribute(device, 'type', 'domain', 'CORE')
|
||||
if coretype:
|
||||
return coretype
|
||||
return self.device_type(device)
|
||||
|
||||
def find_device_with_interface(self, interface_id):
|
||||
# TODO: suport generic 'device' elements
|
||||
for device in xmlutils.iter_descendants_with_name(self.scenario, self.device_types):
|
||||
interface = xmlutils.get_first_child_with_attribute(device, 'interface', 'id', interface_id)
|
||||
if interface:
|
||||
if_name = interface.getAttribute('name')
|
||||
return device, if_name
|
||||
return None, None
|
||||
|
||||
def parse_layer2_device(self, device):
|
||||
objid, device_name = self.get_common_attributes(device)
|
||||
logger.info('parsing layer-2 device: name=%s id=%s' % (device_name, objid))
|
||||
|
||||
try:
|
||||
return self.session.get_object(objid)
|
||||
except KeyError:
|
||||
logger.exception("error geting object: %s", objid)
|
||||
|
||||
device_type = self.device_type(device)
|
||||
if device_type == 'hub':
|
||||
device_class = nodeutils.get_node_class(NodeTypes.HUB)
|
||||
elif device_type == 'switch':
|
||||
device_class = nodeutils.get_node_class(NodeTypes.SWITCH)
|
||||
else:
|
||||
logger.warn('unknown layer-2 device type: \'%s\'' % device_type)
|
||||
assert False # XXX for testing
|
||||
|
||||
n = self.create_core_object(device_class, objid, device_name, device, None)
|
||||
return n
|
||||
|
||||
def parse_layer3_device(self, device):
|
||||
objid, device_name = self.get_common_attributes(device)
|
||||
logger.info('parsing layer-3 device: name=%s id=%s', device_name, objid)
|
||||
|
||||
try:
|
||||
return self.session.get_object(objid)
|
||||
except KeyError:
|
||||
logger.exception("error getting session object: %s", objid)
|
||||
|
||||
device_cls = self.nodecls
|
||||
core_node_type = self.core_node_type(device)
|
||||
n = self.create_core_object(device_cls, objid, device_name, device, core_node_type)
|
||||
n.type = core_node_type
|
||||
self.add_device_services(n, device, core_node_type)
|
||||
for interface in xmlutils.iter_children_with_name(device, 'interface'):
|
||||
self.parse_interface(n, device.getAttribute('id'), interface)
|
||||
return n
|
||||
|
||||
def parse_layer2_devices(self):
|
||||
"""
|
||||
Parse all layer-2 device elements. A device can be: 'switch',
|
||||
'hub'.
|
||||
"""
|
||||
# TODO: suport generic 'device' elements
|
||||
for device in xmlutils.iter_descendants_with_name(self.scenario, self.layer2_device_types):
|
||||
self.parse_layer2_device(device)
|
||||
|
||||
def parse_layer3_devices(self):
|
||||
"""
|
||||
Parse all layer-3 device elements. A device can be: 'host',
|
||||
'router'.
|
||||
"""
|
||||
# TODO: suport generic 'device' elements
|
||||
for device in xmlutils.iter_descendants_with_name(self.scenario, self.layer3_device_types):
|
||||
self.parse_layer3_device(device)
|
||||
|
||||
def parse_session_origin(self, session_config):
|
||||
"""
|
||||
Parse the first origin tag and set the CoreLocation reference
|
||||
point appropriately.
|
||||
"""
|
||||
# defaults from the CORE GUI
|
||||
self.session.location.setrefgeo(47.5791667, -122.132322, 2.0)
|
||||
self.session.location.refscale = 150.0
|
||||
origin = xmlutils.get_first_child_by_tag_name(session_config, 'origin')
|
||||
if not origin:
|
||||
return
|
||||
lat = origin.getAttribute('lat')
|
||||
lon = origin.getAttribute('lon')
|
||||
alt = origin.getAttribute('alt')
|
||||
if lat and lon and alt:
|
||||
self.session.location.setrefgeo(float(lat), float(lon), float(alt))
|
||||
self.location_refgeo_set = True
|
||||
scale100 = origin.getAttribute("scale100")
|
||||
if scale100:
|
||||
self.session.location.refscale = float(scale100)
|
||||
point = xmlutils.get_first_child_text_trim_by_tag_name(origin, 'point')
|
||||
if point:
|
||||
xyz = point.split(',')
|
||||
if len(xyz) == 2:
|
||||
xyz.append('0.0')
|
||||
if len(xyz) == 3:
|
||||
self.session.location.refxyz = (float(xyz[0]), float(xyz[1]), float(xyz[2]))
|
||||
self.location_refxyz_set = True
|
||||
|
||||
def parse_session_options(self, session_config):
|
||||
options = xmlutils.get_first_child_by_tag_name(session_config, 'options')
|
||||
if not options:
|
||||
return
|
||||
params = self.parse_parameter_children(options)
|
||||
for name, value in params.iteritems():
|
||||
if name and value:
|
||||
setattr(self.session.options, str(name), str(value))
|
||||
|
||||
def parse_session_hooks(self, session_config):
|
||||
"""
|
||||
Parse hook scripts.
|
||||
"""
|
||||
hooks = xmlutils.get_first_child_by_tag_name(session_config, 'hooks')
|
||||
if not hooks:
|
||||
return
|
||||
for hook in xmlutils.iter_children_with_name(hooks, 'hook'):
|
||||
filename = hook.getAttribute('name')
|
||||
state = hook.getAttribute('state')
|
||||
data = xmlutils.get_child_text_trim(hook)
|
||||
if data is None:
|
||||
data = '' # allow for empty file
|
||||
hook_type = "hook:%s" % state
|
||||
self.session.set_hook(hook_type, file_name=str(filename), source_name=None, data=str(data))
|
||||
|
||||
def parse_session_metadata(self, session_config):
|
||||
metadata = xmlutils.get_first_child_by_tag_name(session_config, 'metadata')
|
||||
if not metadata:
|
||||
return
|
||||
params = self.parse_parameter_children(metadata)
|
||||
for name, value in params.iteritems():
|
||||
if name and value:
|
||||
self.session.metadata.add_item(str(name), str(value))
|
||||
|
||||
def parse_session_config(self):
|
||||
session_config = xmlutils.get_first_child_by_tag_name(self.scenario, 'CORE:sessionconfig')
|
||||
if not session_config:
|
||||
return
|
||||
self.parse_session_origin(session_config)
|
||||
self.parse_session_options(session_config)
|
||||
self.parse_session_hooks(session_config)
|
||||
self.parse_session_metadata(session_config)
|
||||
|
||||
def parse_default_services(self):
|
||||
# defaults from the CORE GUI
|
||||
self.default_services = {
|
||||
'router': ['zebra', 'OSPFv2', 'OSPFv3', 'vtysh', 'IPForward'],
|
||||
'host': ['DefaultRoute', 'SSH'],
|
||||
'PC': ['DefaultRoute', ],
|
||||
'mdr': ['zebra', 'OSPFv3MDR', 'vtysh', 'IPForward'],
|
||||
# 'prouter': ['zebra', 'OSPFv2', 'OSPFv3', 'vtysh', 'IPForward'],
|
||||
# 'xen': ['zebra', 'OSPFv2', 'OSPFv3', 'vtysh', 'IPForward'],
|
||||
}
|
||||
default_services = xmlutils.get_first_child_by_tag_name(self.scenario, 'CORE:defaultservices')
|
||||
if not default_services:
|
||||
return
|
||||
for device in xmlutils.iter_children_with_name(default_services, 'device'):
|
||||
device_type = device.getAttribute('type')
|
||||
if not device_type:
|
||||
logger.warn('parse_default_services: no type attribute found for device')
|
||||
continue
|
||||
services = []
|
||||
for service in xmlutils.iter_children_with_name(device, 'service'):
|
||||
name = service.getAttribute('name')
|
||||
if name:
|
||||
services.append(str(name))
|
||||
self.default_services[device_type] = services
|
||||
# store default services for the session
|
||||
for t, s in self.default_services.iteritems():
|
||||
self.session.services.defaultservices[t] = s
|
||||
logger.info('default services for node type \'%s\' set to: %s' % (t, s))
|
36
daemon/core/xml/xmlsession.py
Normal file
36
daemon/core/xml/xmlsession.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Helpers for loading and saving XML files. savesessionxml(session, filename) is
|
||||
the main public interface here.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import nodeutils
|
||||
from core.xml.xmlparser import core_document_parser
|
||||
from core.xml.xmlwriter import core_document_writer
|
||||
|
||||
|
||||
def open_session_xml(session, filename, start=False, nodecls=None):
|
||||
"""
|
||||
Import a session from the EmulationScript XML format.
|
||||
"""
|
||||
|
||||
# set default node class when one is not provided
|
||||
if not nodecls:
|
||||
nodecls = nodeutils.get_node_class(NodeTypes.DEFAULT)
|
||||
|
||||
options = {'start': start, 'nodecls': nodecls}
|
||||
doc = core_document_parser(session, filename, options)
|
||||
if start:
|
||||
session.name = os.path.basename(filename)
|
||||
session.filename = filename
|
||||
session.instantiate()
|
||||
|
||||
|
||||
def save_session_xml(session, filename, version):
|
||||
"""
|
||||
Export a session to the EmulationScript XML format.
|
||||
"""
|
||||
doc = core_document_writer(session, version)
|
||||
doc.writexml(filename)
|
338
daemon/core/xml/xmlutils.py
Normal file
338
daemon/core/xml/xmlutils.py
Normal file
|
@ -0,0 +1,338 @@
|
|||
from xml.dom.minidom import Node
|
||||
|
||||
from core.misc import log
|
||||
from core.netns import nodes
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
def add_elements_from_list(dom, parent, iterable, name, attr_name):
|
||||
"""
|
||||
XML helper to iterate through a list and add items to parent using tags
|
||||
of the given name and the item value as an attribute named attr_name.
|
||||
Example: addelementsfromlist(dom, parent, ('a','b','c'), "letter", "value")
|
||||
<parent>
|
||||
<letter value="a"/>
|
||||
<letter value="b"/>
|
||||
<letter value="c"/>
|
||||
</parent>
|
||||
"""
|
||||
for item in iterable:
|
||||
element = dom.createElement(name)
|
||||
element.setAttribute(attr_name, item)
|
||||
parent.appendChild(element)
|
||||
|
||||
|
||||
def add_text_elements_from_list(dom, parent, iterable, name, attrs):
|
||||
"""
|
||||
XML helper to iterate through a list and add items to parent using tags
|
||||
of the given name, attributes specified in the attrs tuple, and having the
|
||||
text of the item within the tags.
|
||||
Example: addtextelementsfromlist(dom, parent, ('a','b','c'), "letter",
|
||||
(('show','True'),))
|
||||
<parent>
|
||||
<letter show="True">a</letter>
|
||||
<letter show="True">b</letter>
|
||||
<letter show="True">c</letter>
|
||||
</parent>
|
||||
"""
|
||||
for item in iterable:
|
||||
element = dom.createElement(name)
|
||||
for k, v in attrs:
|
||||
element.setAttribute(k, v)
|
||||
parent.appendChild(element)
|
||||
txt = dom.createTextNode(item)
|
||||
element.appendChild(txt)
|
||||
|
||||
|
||||
def add_text_elements_from_tuples(dom, parent, iterable, attrs=()):
|
||||
"""
|
||||
XML helper to iterate through a list of tuples and add items to
|
||||
parent using tags named for the first tuple element,
|
||||
attributes specified in the attrs tuple, and having the
|
||||
text of second tuple element.
|
||||
Example: addtextelementsfromtuples(dom, parent,
|
||||
(('first','a'),('second','b'),('third','c')),
|
||||
(('show','True'),))
|
||||
<parent>
|
||||
<first show="True">a</first>
|
||||
<second show="True">b</second>
|
||||
<third show="True">c</third>
|
||||
</parent>
|
||||
"""
|
||||
for name, value in iterable:
|
||||
element = dom.createElement(name)
|
||||
for k, v in attrs:
|
||||
element.setAttribute(k, v)
|
||||
parent.appendChild(element)
|
||||
txt = dom.createTextNode(value)
|
||||
element.appendChild(txt)
|
||||
|
||||
|
||||
def get_text_elements_to_list(parent):
|
||||
"""
|
||||
XML helper to parse child text nodes from the given parent and return
|
||||
a list of (key, value) tuples.
|
||||
"""
|
||||
r = []
|
||||
for n in parent.childNodes:
|
||||
if n.nodeType != Node.ELEMENT_NODE:
|
||||
continue
|
||||
k = str(n.nodeName)
|
||||
v = '' # sometimes want None here?
|
||||
for c in n.childNodes:
|
||||
if c.nodeType != Node.TEXT_NODE:
|
||||
continue
|
||||
v = str(c.nodeValue)
|
||||
break
|
||||
r.append((k, v))
|
||||
return r
|
||||
|
||||
|
||||
def add_param_to_parent(dom, parent, name, value):
|
||||
"""
|
||||
XML helper to add a <param name="name" value="value"/> tag to the parent
|
||||
element, when value is not None.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
p = dom.createElement("param")
|
||||
parent.appendChild(p)
|
||||
p.setAttribute("name", name)
|
||||
p.setAttribute("value", "%s" % value)
|
||||
return p
|
||||
|
||||
|
||||
def add_text_param_to_parent(dom, parent, name, value):
|
||||
"""
|
||||
XML helper to add a <param name="name">value</param> tag to the parent
|
||||
element, when value is not None.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
p = dom.createElement("param")
|
||||
parent.appendChild(p)
|
||||
p.setAttribute("name", name)
|
||||
txt = dom.createTextNode(value)
|
||||
p.appendChild(txt)
|
||||
return p
|
||||
|
||||
|
||||
def add_param_list_to_parent(dom, parent, name, values):
|
||||
"""
|
||||
XML helper to return a parameter list and optionally add it to the
|
||||
parent element:
|
||||
<paramlist name="name">
|
||||
<item value="123">
|
||||
<item value="456">
|
||||
</paramlist>
|
||||
"""
|
||||
if values is None:
|
||||
return None
|
||||
p = dom.createElement("paramlist")
|
||||
if parent:
|
||||
parent.appendChild(p)
|
||||
p.setAttribute("name", name)
|
||||
for v in values:
|
||||
item = dom.createElement("item")
|
||||
item.setAttribute("value", str(v))
|
||||
p.appendChild(item)
|
||||
return p
|
||||
|
||||
|
||||
def get_one_element(dom, name):
|
||||
e = dom.getElementsByTagName(name)
|
||||
if len(e) == 0:
|
||||
return None
|
||||
return e[0]
|
||||
|
||||
|
||||
def iter_descendants(dom, max_depth=0):
|
||||
"""
|
||||
Iterate over all descendant element nodes in breadth first order.
|
||||
Only consider nodes up to max_depth deep when max_depth is greater
|
||||
than zero.
|
||||
"""
|
||||
nodes = [dom]
|
||||
depth = 0
|
||||
current_depth_nodes = 1
|
||||
next_depth_nodes = 0
|
||||
while nodes:
|
||||
n = nodes.pop(0)
|
||||
for child in n.childNodes:
|
||||
if child.nodeType == Node.ELEMENT_NODE:
|
||||
yield child
|
||||
nodes.append(child)
|
||||
next_depth_nodes += 1
|
||||
current_depth_nodes -= 1
|
||||
if current_depth_nodes == 0:
|
||||
depth += 1
|
||||
if max_depth > 0 and depth == max_depth:
|
||||
return
|
||||
current_depth_nodes = next_depth_nodes
|
||||
next_depth_nodes = 0
|
||||
|
||||
|
||||
def iter_matching_descendants(dom, match_function, max_depth=0):
|
||||
"""
|
||||
Iterate over descendant elements where matchFunction(descendant)
|
||||
returns true. Only consider nodes up to max_depth deep when
|
||||
max_depth is greater than zero.
|
||||
"""
|
||||
for d in iter_descendants(dom, max_depth):
|
||||
if match_function(d):
|
||||
yield d
|
||||
|
||||
|
||||
def iter_descendants_with_name(dom, tag_name, max_depth=0):
|
||||
"""
|
||||
Iterate over descendant elements whose name is contained in
|
||||
tagName (or is named tagName if tagName is a string). Only
|
||||
consider nodes up to max_depth deep when max_depth is greater than
|
||||
zero.
|
||||
"""
|
||||
if isinstance(tag_name, basestring):
|
||||
tag_name = (tag_name,)
|
||||
|
||||
def match(d):
|
||||
return d.tagName in tag_name
|
||||
|
||||
return iter_matching_descendants(dom, match, max_depth)
|
||||
|
||||
|
||||
def iter_descendants_with_attribute(dom, tag_name, attr_name, attr_value, max_depth=0):
|
||||
"""
|
||||
Iterate over descendant elements whose name is contained in
|
||||
tagName (or is named tagName if tagName is a string) and have an
|
||||
attribute named attrName with value attrValue. Only consider
|
||||
nodes up to max_depth deep when max_depth is greater than zero.
|
||||
"""
|
||||
if isinstance(tag_name, basestring):
|
||||
tag_name = (tag_name,)
|
||||
|
||||
def match(d):
|
||||
return d.tagName in tag_name and \
|
||||
d.getAttribute(attr_name) == attr_value
|
||||
|
||||
return iter_matching_descendants(dom, match, max_depth)
|
||||
|
||||
|
||||
def iter_children(dom, node_type):
|
||||
"""
|
||||
Iterate over all child elements of the given type.
|
||||
"""
|
||||
for child in dom.childNodes:
|
||||
if child.nodeType == node_type:
|
||||
yield child
|
||||
|
||||
|
||||
def get_text_child(dom):
|
||||
"""
|
||||
Return the text node of the given element.
|
||||
"""
|
||||
for child in iter_children(dom, Node.TEXT_NODE):
|
||||
return str(child.nodeValue)
|
||||
return None
|
||||
|
||||
|
||||
def get_child_text_trim(dom):
|
||||
text = get_text_child(dom)
|
||||
if text:
|
||||
text = text.strip()
|
||||
return text
|
||||
|
||||
|
||||
def get_params_set_attrs(dom, param_names, target):
|
||||
"""
|
||||
XML helper to get <param name="name" value="value"/> tags and set
|
||||
the attribute in the target object. String type is used. Target object
|
||||
attribute is unchanged if the XML attribute is not present.
|
||||
"""
|
||||
params = dom.getElementsByTagName("param")
|
||||
for param in params:
|
||||
param_name = param.getAttribute("name")
|
||||
value = param.getAttribute("value")
|
||||
if value is None:
|
||||
continue # never reached?
|
||||
if param_name in param_names:
|
||||
setattr(target, param_name, str(value))
|
||||
|
||||
|
||||
def xml_type_to_node_class(session, type):
|
||||
"""
|
||||
Helper to convert from a type string to a class name in nodes.*.
|
||||
"""
|
||||
if hasattr(nodes, type):
|
||||
# TODO: remove and use a mapping to known nodes
|
||||
logger.error("using eval to retrieve node type: %s", type)
|
||||
return eval("nodes.%s" % type)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def iter_children_with_name(dom, tag_name):
|
||||
return iter_descendants_with_name(dom, tag_name, 1)
|
||||
|
||||
|
||||
def iter_children_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
return iter_descendants_with_attribute(dom, tag_name, attr_name, attr_value, 1)
|
||||
|
||||
|
||||
def get_first_child_by_tag_name(dom, tag_name):
|
||||
"""
|
||||
Return the first child element whose name is contained in tagName
|
||||
(or is named tagName if tagName is a string).
|
||||
"""
|
||||
for child in iter_children_with_name(dom, tag_name):
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_by_tag_name(dom, tag_name):
|
||||
"""
|
||||
Return the corresponding text of the first child element whose
|
||||
name is contained in tagName (or is named tagName if tagName is a
|
||||
string).
|
||||
"""
|
||||
child = get_first_child_by_tag_name(dom, tag_name)
|
||||
if child:
|
||||
return get_text_child(child)
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_trim_by_tag_name(dom, tag_name):
|
||||
text = get_first_child_text_by_tag_name(dom, tag_name)
|
||||
if text:
|
||||
text = text.strip()
|
||||
return text
|
||||
|
||||
|
||||
def get_first_child_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
"""
|
||||
Return the first child element whose name is contained in tagName
|
||||
(or is named tagName if tagName is a string) that has an attribute
|
||||
named attrName with value attrValue.
|
||||
"""
|
||||
for child in \
|
||||
iter_children_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
"""
|
||||
Return the corresponding text of the first child element whose
|
||||
name is contained in tagName (or is named tagName if tagName is a
|
||||
string) that has an attribute named attrName with value attrValue.
|
||||
"""
|
||||
child = get_first_child_with_attribute(dom, tag_name, attr_name, attr_value)
|
||||
if child:
|
||||
return get_text_child(child)
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_trim_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
text = get_first_child_text_with_attribute(dom, tag_name, attr_name, attr_value)
|
||||
if text:
|
||||
text = text.strip()
|
||||
return text
|
12
daemon/core/xml/xmlwriter.py
Normal file
12
daemon/core/xml/xmlwriter.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from core.xml.xmlwriter0 import CoreDocumentWriter0
|
||||
from core.xml.xmlwriter1 import CoreDocumentWriter1
|
||||
|
||||
|
||||
def core_document_writer(session, version):
|
||||
if version == '0.0':
|
||||
doc = CoreDocumentWriter0(session)
|
||||
elif version == '1.0':
|
||||
doc = CoreDocumentWriter1(session)
|
||||
else:
|
||||
raise ValueError('unsupported document version: %s' % version)
|
||||
return doc
|
389
daemon/core/xml/xmlwriter0.py
Normal file
389
daemon/core/xml/xmlwriter0.py
Normal file
|
@ -0,0 +1,389 @@
|
|||
import os
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
import pwd
|
||||
|
||||
from core.coreobj import PyCoreNet
|
||||
from core.coreobj import PyCoreNode
|
||||
from core.enumerations import RegisterTlvs
|
||||
from core.misc import log
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
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):
|
||||
logger.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._objects_lock:
|
||||
for net in self.session.objects.itervalues():
|
||||
if not isinstance(net, 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 == RegisterTlvs.MOBILITY.value:
|
||||
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._objects_lock:
|
||||
for node in self.session.objects.itervalues():
|
||||
if not isinstance(node, 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)
|
||||
xmlutils.add_param_to_parent(self, n, "icon", node.icon)
|
||||
xmlutils.add_param_to_parent(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))
|
||||
xmlutils.add_elements_from_list(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)
|
||||
|
||||
xmlutils.add_text_elements_from_list(self, s, svc._startup, "Command", (("type", "start"),))
|
||||
xmlutils.add_text_elements_from_list(self, s, svc._shutdown, "Command", (("type", "stop"),))
|
||||
xmlutils.add_text_elements_from_list(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]):
|
||||
xmlutils.add_text_param_to_parent(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():
|
||||
xmlutils.add_text_param_to_parent(self, meta, k, v)
|
||||
# addparamtoparent(self, meta, k, v)
|
1018
daemon/core/xml/xmlwriter1.py
Normal file
1018
daemon/core/xml/xmlwriter1.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue