daemon: Initial support for importing a scenario using the new NRL
Network Modeling Framework (NMF) XML representation.
This commit is contained in:
parent
08c9fd8bf5
commit
1112da1417
3 changed files with 1093 additions and 12 deletions
|
@ -3,10 +3,13 @@
|
||||||
# See the LICENSE file included in this distribution.
|
# See the LICENSE file included in this distribution.
|
||||||
|
|
||||||
from xml.dom.minidom import parse
|
from xml.dom.minidom import parse
|
||||||
from xmlutils import getoneelement
|
from xmlutils import getFirstChildByTagName
|
||||||
from xmlparser0 import CoreDocumentParser0
|
from xmlparser0 import CoreDocumentParser0
|
||||||
|
from xmlparser1 import CoreDocumentParser1
|
||||||
|
|
||||||
class CoreVersionParser(object):
|
class CoreVersionParser(object):
|
||||||
|
DEFAULT_SCENARIO_VERSION = '1.0'
|
||||||
|
|
||||||
'''\
|
'''\
|
||||||
Helper class to check the version of Network Plan document. This
|
Helper class to check the version of Network Plan document. This
|
||||||
simply looks for a "Scenario" element; when present, this
|
simply looks for a "Scenario" element; when present, this
|
||||||
|
@ -19,9 +22,14 @@ class CoreVersionParser(object):
|
||||||
self.dom = options['dom']
|
self.dom = options['dom']
|
||||||
else:
|
else:
|
||||||
self.dom = parse(filename)
|
self.dom = parse(filename)
|
||||||
self.scenario = getoneelement(self.dom, 'Scenario')
|
scenario = getFirstChildByTagName(self.dom, 'scenario')
|
||||||
if self.scenario is not None:
|
if scenario:
|
||||||
self.version = 0.0
|
version = scenario.getAttribute('version')
|
||||||
|
if not version:
|
||||||
|
version = self.DEFAULT_SCENARIO_VERSION
|
||||||
|
self.version = version
|
||||||
|
elif getFirstChildByTagName(self.dom, 'Scenario'):
|
||||||
|
self.version = '0.0'
|
||||||
else:
|
else:
|
||||||
self.version = 'unknown'
|
self.version = 'unknown'
|
||||||
|
|
||||||
|
@ -29,8 +37,10 @@ def core_document_parser(session, filename, options):
|
||||||
vp = CoreVersionParser(filename, options)
|
vp = CoreVersionParser(filename, options)
|
||||||
if 'dom' not in options:
|
if 'dom' not in options:
|
||||||
options['dom'] = vp.dom
|
options['dom'] = vp.dom
|
||||||
if vp.version == 0.0:
|
if vp.version == '0.0':
|
||||||
doc = CoreDocumentParser0(session, filename, options)
|
doc = CoreDocumentParser0(session, filename, options)
|
||||||
|
elif vp.version == '1.0':
|
||||||
|
doc = CoreDocumentParser1(session, filename, options)
|
||||||
else:
|
else:
|
||||||
raise ValueError, 'unsupported document version: %s' % vp.version
|
raise ValueError, 'unsupported document version: %s' % vp.version
|
||||||
return doc
|
return doc
|
||||||
|
|
942
daemon/core/misc/xmlparser1.py
Normal file
942
daemon/core/misc/xmlparser1.py
Normal file
|
@ -0,0 +1,942 @@
|
||||||
|
#
|
||||||
|
# CORE
|
||||||
|
# Copyright (c) 2015 the Boeing Company.
|
||||||
|
# See the LICENSE file included in this distribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import random
|
||||||
|
from core.netns import nodes
|
||||||
|
from core import constants
|
||||||
|
from core.misc.ipaddr import MacAddr
|
||||||
|
from xml.dom.minidom import parse
|
||||||
|
from xmlutils import *
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.session = session
|
||||||
|
self.verbose = self.session.getcfgitembool('verbose', False)
|
||||||
|
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()
|
||||||
|
|
||||||
|
def info(self, msg):
|
||||||
|
s = 'XML parsing \'%s\': %s' % (self.filename, msg)
|
||||||
|
if self.session:
|
||||||
|
self.session.info(s)
|
||||||
|
else:
|
||||||
|
sys.stdout.write(s + '\n')
|
||||||
|
|
||||||
|
def warn(self, msg):
|
||||||
|
s = 'WARNING XML parsing \'%s\': %s' % (self.filename, msg)
|
||||||
|
if self.session:
|
||||||
|
self.session.warn(s)
|
||||||
|
else:
|
||||||
|
sys.stderr.write(s + '\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_scenario(dom):
|
||||||
|
scenario = getFirstChildByTagName(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 iterChildrenWithAttribute(element, 'member',
|
||||||
|
'type', 'interface'):
|
||||||
|
if_id = getChildTextTrim(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 == 'ethernet':
|
||||||
|
return nodes.PtpNet
|
||||||
|
elif network_type == 'satcom':
|
||||||
|
return nodes.PtpNet
|
||||||
|
elif network_type == 'wireless':
|
||||||
|
channel = getFirstChildByTagName(network, 'channel')
|
||||||
|
if channel:
|
||||||
|
# use an explicit CORE type if it exists
|
||||||
|
coretype = getFirstChildTextTrimWithAttribute(channel, 'type',
|
||||||
|
'domain', 'CORE')
|
||||||
|
if coretype:
|
||||||
|
if coretype == 'basic_range':
|
||||||
|
return nodes.WlanNode
|
||||||
|
elif coretype.startswith('emane'):
|
||||||
|
return nodes.EmaneNode
|
||||||
|
else:
|
||||||
|
self.warn('unknown network type: \'%s\'' % coretype)
|
||||||
|
return xmltypetonodeclass(self.session, coretype)
|
||||||
|
return nodes.WlanNode
|
||||||
|
self.warn('unknown network type: \'%s\'' % network_type)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_core_object(self, objcls, objid, objname, element, node_type):
|
||||||
|
obj = self.session.addobj(cls = objcls, objid = objid,
|
||||||
|
name = objname, start = self.start)
|
||||||
|
if self.verbose:
|
||||||
|
self.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.obj(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:
|
||||||
|
self.warn('unknown network: %s' % network.toxml('utf-8'))
|
||||||
|
assert False # XXX for testing
|
||||||
|
return
|
||||||
|
model_name = getFirstChildTextTrimWithAttribute(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 isinstance(dev2, nodes.RJ45Node):
|
||||||
|
# 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 iterChildrenWithName(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(getChildTextTrim(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 = getChildTextTrim(element)
|
||||||
|
link_params = self.parse_parameter_children(channel)
|
||||||
|
|
||||||
|
mobility = getFirstChildByTagName(channel, 'CORE:mobility')
|
||||||
|
if mobility:
|
||||||
|
mobility_model_name = \
|
||||||
|
getFirstChildTextTrimByTagName(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 = getFirstChildTextTrimByTagName(network, 'type')
|
||||||
|
if not net_type:
|
||||||
|
msg = 'no network type found for network: \'%s\'' % \
|
||||||
|
network.toxml('utf-8')
|
||||||
|
self.warn(msg)
|
||||||
|
assert False # XXX for testing
|
||||||
|
return
|
||||||
|
net_cls = self.network_class(network, net_type)
|
||||||
|
objid, net_name = self.get_common_attributes(network)
|
||||||
|
if self.verbose:
|
||||||
|
self.info('parsing network: %s %s' % (net_name, objid))
|
||||||
|
if objid in self.session._objs:
|
||||||
|
return
|
||||||
|
n = self.create_core_object(net_cls, objid, net_name,
|
||||||
|
network, None)
|
||||||
|
# handle channel parameters
|
||||||
|
for channel in iterChildrenWithName(network, 'channel'):
|
||||||
|
self.parse_network_channel(channel)
|
||||||
|
|
||||||
|
def parse_networks(self):
|
||||||
|
'''\
|
||||||
|
Parse all 'network' elements.
|
||||||
|
'''
|
||||||
|
for network in iterDescendantsWithName(self.scenario, 'network'):
|
||||||
|
self.parse_network(network)
|
||||||
|
|
||||||
|
def parse_addresses(self, interface):
|
||||||
|
mac = []
|
||||||
|
ipv4 = []
|
||||||
|
ipv6= []
|
||||||
|
hostname = []
|
||||||
|
for address in iterChildrenWithName(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')
|
||||||
|
self.warn(msg)
|
||||||
|
assert False # XXX for testing
|
||||||
|
continue
|
||||||
|
addr_text = getChildTextTrim(address)
|
||||||
|
if not addr_text:
|
||||||
|
msg = 'no text found for address ' \
|
||||||
|
'in interface: \'%s\'' % interface.toxml('utf-8')
|
||||||
|
self.warn(msg)
|
||||||
|
assert False # XXX for testing
|
||||||
|
continue
|
||||||
|
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'))
|
||||||
|
self.warn(msg)
|
||||||
|
assert False # XXX for testing
|
||||||
|
continue
|
||||||
|
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)
|
||||||
|
self.warn(msg)
|
||||||
|
assert False # XXX for testing
|
||||||
|
return
|
||||||
|
mac, ipv4, ipv6, hostname = self.parse_addresses(interface)
|
||||||
|
if mac:
|
||||||
|
hwaddr = MacAddr.fromstring(mac[0])
|
||||||
|
else:
|
||||||
|
hwaddr = None
|
||||||
|
ifindex = node.newnetif(network, addrlist = ipv4 + ipv6,
|
||||||
|
hwaddr = hwaddr, ifindex = None,
|
||||||
|
ifname = if_name)
|
||||||
|
# TODO: 'hostname' addresses are unused
|
||||||
|
if self.verbose:
|
||||||
|
msg = 'node \'%s\' interface \'%s\' connected ' \
|
||||||
|
'to network \'%s\'' % (node.name, if_name, network.name)
|
||||||
|
self.info(msg)
|
||||||
|
# set link parameters for wired links
|
||||||
|
if isinstance(network,
|
||||||
|
(nodes.HubNode, nodes.PtpNet, nodes.SwitchNode)):
|
||||||
|
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, tagName, 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 iterChildren(node, Node.ELEMENT_NODE):
|
||||||
|
if child.tagName == tagName 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 getChildTextTrim(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 = getFirstChildTextTrimWithAttribute(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 = getFirstChildTextTrimWithAttribute(interface, 'member',
|
||||||
|
'type', 'channel')
|
||||||
|
if not network_id or not channel_id:
|
||||||
|
return None
|
||||||
|
network = getFirstChildWithAttribute(self.scenario, 'network',
|
||||||
|
'id', network_id)
|
||||||
|
if not network:
|
||||||
|
return None
|
||||||
|
channel = getFirstChildWithAttribute(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:
|
||||||
|
self.warn('limiting negative x position of \'%s\' to zero: %s' %
|
||||||
|
(obj.name, x))
|
||||||
|
x = 0.0
|
||||||
|
if y < 0.0:
|
||||||
|
self.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:
|
||||||
|
self.warn('limiting negative x position of \'%s\' to zero: %s' %
|
||||||
|
(obj.name, x))
|
||||||
|
x = 0.0
|
||||||
|
if y < 0.0:
|
||||||
|
self.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:
|
||||||
|
self.warn('limiting negative x position of \'%s\' to zero: %s' %
|
||||||
|
(obj.name, x))
|
||||||
|
x = 0.0
|
||||||
|
if y < 0.0:
|
||||||
|
self.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')
|
||||||
|
self.warn(msg)
|
||||||
|
assert False # XXX for testing
|
||||||
|
return False
|
||||||
|
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:
|
||||||
|
self.warn("skipping unknown point type: '%s'" % point_type)
|
||||||
|
assert False # XXX for testing
|
||||||
|
return False
|
||||||
|
if self.verbose:
|
||||||
|
msg = 'set position of %s from point element: \'%s\'' % \
|
||||||
|
(obj.name, point.toxml('utf-8'))
|
||||||
|
self.info(msg)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def parse_device_service(self, service, node):
|
||||||
|
name = service.getAttribute('name')
|
||||||
|
session_service = self.session.services.getservicebyname(name)
|
||||||
|
if not session_service:
|
||||||
|
assert False # XXX for testing
|
||||||
|
return None
|
||||||
|
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 iterChildrenWithName(service, 'directory'):
|
||||||
|
dirname = directory.getAttribute('name')
|
||||||
|
dirs.append(str(dirname))
|
||||||
|
if dirs:
|
||||||
|
values.append("dirs=%s" % dirs)
|
||||||
|
startup = []
|
||||||
|
shutdown = []
|
||||||
|
validate = []
|
||||||
|
for command in iterChildrenWithName(service, 'command'):
|
||||||
|
command_type = command.getAttribute('type')
|
||||||
|
command_text = getChildTextTrim(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 iterChildrenWithName(service, 'file'):
|
||||||
|
filename = f.getAttribute('name')
|
||||||
|
if not filename:
|
||||||
|
continue;
|
||||||
|
filenames.append(filename)
|
||||||
|
data = getChildTextTrim(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 iterChildrenWithName(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 = getFirstChildByTagName(device, 'CORE:services')
|
||||||
|
if services:
|
||||||
|
services_str = self.parse_device_services(services, node)
|
||||||
|
if self.verbose:
|
||||||
|
self.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,
|
||||||
|
verbose = self.verbose)
|
||||||
|
|
||||||
|
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 iterChildrenWithName(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 = getFirstChildTextTrimWithAttribute(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 iterDescendantsWithName(self.scenario,
|
||||||
|
self.device_types):
|
||||||
|
interface = getFirstChildWithAttribute(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)
|
||||||
|
if self.verbose:
|
||||||
|
self.info('parsing layer-2 device: %s %s' % (device_name, objid))
|
||||||
|
try:
|
||||||
|
return self.session.obj(objid)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
device_type = self.device_type(device)
|
||||||
|
if device_type == 'hub':
|
||||||
|
device_class = nodes.HubNode
|
||||||
|
elif device_type == 'switch':
|
||||||
|
device_class = nodes.SwitchNode
|
||||||
|
else:
|
||||||
|
self.warn('unknown layer-2 device type: \'%s\'' % device_type)
|
||||||
|
assert False # XXX for testing
|
||||||
|
return None
|
||||||
|
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)
|
||||||
|
if self.verbose:
|
||||||
|
self.info('parsing layer-3 device: %s %s' % (device_name, objid))
|
||||||
|
try:
|
||||||
|
return self.session.obj(objid)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
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 iterChildrenWithName(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 iterDescendantsWithName(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 iterDescendantsWithName(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 = getFirstChildByTagName(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 = getFirstChildTextTrimByTagName(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 = getFirstChildByTagName(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 = getFirstChildByTagName(session_config, 'hooks')
|
||||||
|
if not hooks:
|
||||||
|
return
|
||||||
|
for hook in iterChildrenWithName(hooks, 'hook'):
|
||||||
|
filename = hook.getAttribute('name')
|
||||||
|
state = hook.getAttribute('state')
|
||||||
|
data = getChildTextTrim(hook)
|
||||||
|
if data is None:
|
||||||
|
data = '' # allow for empty file
|
||||||
|
hook_type = "hook:%s" % state
|
||||||
|
self.session.sethook(hook_type, filename = str(filename),
|
||||||
|
srcname = None, data = str(data))
|
||||||
|
|
||||||
|
def parse_session_metadata(self, session_config):
|
||||||
|
metadata = getFirstChildByTagName(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.additem(str(name), str(value))
|
||||||
|
|
||||||
|
def parse_session_config(self):
|
||||||
|
session_config = \
|
||||||
|
getFirstChildByTagName(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 = \
|
||||||
|
getFirstChildByTagName(self.scenario, 'CORE:defaultservices')
|
||||||
|
if not default_services:
|
||||||
|
return
|
||||||
|
for device in iterChildrenWithName(default_services, 'device'):
|
||||||
|
device_type = device.getAttribute('type')
|
||||||
|
if not device_type:
|
||||||
|
self.warn('parse_default_services: no type attribute ' \
|
||||||
|
'found for device')
|
||||||
|
continue
|
||||||
|
services = []
|
||||||
|
for service in iterChildrenWithName(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
|
||||||
|
if self.verbose:
|
||||||
|
self.info('default services for node type \'%s\' ' \
|
||||||
|
'set to: %s' % (t, s))
|
|
@ -135,6 +135,69 @@ def getoneelement(dom, name):
|
||||||
return None
|
return None
|
||||||
return e[0]
|
return e[0]
|
||||||
|
|
||||||
|
def iterDescendants(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 iterMatchingDescendants(dom, matchFunction, 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 iterDescendants(dom, max_depth):
|
||||||
|
if matchFunction(d):
|
||||||
|
yield d
|
||||||
|
|
||||||
|
def iterDescendantsWithName(dom, tagName, 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(tagName, basestring):
|
||||||
|
tagName = (tagName,)
|
||||||
|
def match(d):
|
||||||
|
return d.tagName in tagName
|
||||||
|
return iterMatchingDescendants(dom, match, max_depth)
|
||||||
|
|
||||||
|
def iterDescendantsWithAttribute(dom, tagName, attrName, attrValue,
|
||||||
|
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(tagName, basestring):
|
||||||
|
tagName = (tagName,)
|
||||||
|
def match(d):
|
||||||
|
return d.tagName in tagName and \
|
||||||
|
d.getAttribute(attrName) == attrValue
|
||||||
|
return iterMatchingDescendants(dom, match, max_depth)
|
||||||
|
|
||||||
def iterChildren(dom, nodeType):
|
def iterChildren(dom, nodeType):
|
||||||
'''\
|
'''\
|
||||||
Iterate over all child elements of the given type.
|
Iterate over all child elements of the given type.
|
||||||
|
@ -151,6 +214,12 @@ def gettextchild(dom):
|
||||||
return str(child.nodeValue)
|
return str(child.nodeValue)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getChildTextTrim(dom):
|
||||||
|
text = gettextchild(dom)
|
||||||
|
if text:
|
||||||
|
text = text.strip()
|
||||||
|
return text
|
||||||
|
|
||||||
def getparamssetattrs(dom, param_names, target):
|
def getparamssetattrs(dom, param_names, target):
|
||||||
''' XML helper to get <param name="name" value="value"/> tags and set
|
''' XML helper to get <param name="name" value="value"/> tags and set
|
||||||
the attribute in the target object. String type is used. Target object
|
the attribute in the target object. String type is used. Target object
|
||||||
|
@ -172,3 +241,63 @@ def xmltypetonodeclass(session, type):
|
||||||
return eval("nodes.%s" % type)
|
return eval("nodes.%s" % type)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def iterChildrenWithName(dom, tagName):
|
||||||
|
return iterDescendantsWithName(dom, tagName, 1)
|
||||||
|
|
||||||
|
def iterChildrenWithAttribute(dom, tagName, attrName, attrValue):
|
||||||
|
return iterDescendantsWithAttribute(dom, tagName, attrName, attrValue, 1)
|
||||||
|
|
||||||
|
def getFirstChildByTagName(dom, tagName):
|
||||||
|
'''\
|
||||||
|
Return the first child element whose name is contained in tagName
|
||||||
|
(or is named tagName if tagName is a string).
|
||||||
|
'''
|
||||||
|
for child in iterChildrenWithName(dom, tagName):
|
||||||
|
return child
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getFirstChildTextByTagName(dom, tagName):
|
||||||
|
'''\
|
||||||
|
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 = getFirstChildByTagName(dom, tagName)
|
||||||
|
if child:
|
||||||
|
return gettextchild(child)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getFirstChildTextTrimByTagName(dom, tagName):
|
||||||
|
text = getFirstChildTextByTagName(dom, tagName)
|
||||||
|
if text:
|
||||||
|
text = text.strip()
|
||||||
|
return text
|
||||||
|
|
||||||
|
def getFirstChildWithAttribute(dom, tagName, attrName, attrValue):
|
||||||
|
'''\
|
||||||
|
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 \
|
||||||
|
iterChildrenWithAttribute(dom, tagName, attrName, attrValue):
|
||||||
|
return child
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getFirstChildTextWithAttribute(dom, tagName, attrName, attrValue):
|
||||||
|
'''\
|
||||||
|
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 = getFirstChildWithAttribute(dom, tagName, attrName, attrValue)
|
||||||
|
if child:
|
||||||
|
return gettextchild(child)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getFirstChildTextTrimWithAttribute(dom, tagName, attrName, attrValue):
|
||||||
|
text = getFirstChildTextWithAttribute(dom, tagName, attrName, attrValue)
|
||||||
|
if text:
|
||||||
|
text = text.strip()
|
||||||
|
return text
|
||||||
|
|
Loading…
Reference in a new issue