882 lines
35 KiB
Python
882 lines
35 KiB
Python
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):
|
|
"""
|
|
Creates an CoreDocumentParser1 object.
|
|
|
|
:param core.session.Session session:
|
|
:param str filename: file name to open and parse
|
|
:param dict options: parsing options
|
|
:return:
|
|
"""
|
|
logger.info("creating xml parser: file (%s) options(%s)", 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.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
|
|
logger.info("setting wireless link params: node(%s) model(%s) mobility_model(%s)",
|
|
nodenum, model_name, mobility_model_name)
|
|
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))
|