merged cleanup branch with master

This commit is contained in:
Rod A Santiago 2017-06-19 18:03:39 -07:00
parent a4f47a17e3
commit 0a91fe7a3e
28 changed files with 9033 additions and 0 deletions

View file

View 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.getservernames()
# 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

View 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

View 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)

View file

@ -0,0 +1,876 @@
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', 'IPForward'],
'host': ['DefaultRoute', 'SSH'],
'PC': ['DefaultRoute', ],
'mdr': ['zebra', 'OSPFv3MDR', '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))

View 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
View 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

View 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

View 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)

File diff suppressed because it is too large Load diff