import random from xml.dom.minidom import Node from xml.dom.minidom import parse from core import constants from core import logger from core.conf import ConfigShim from core.enumerations import NodeTypes from core.misc import nodeutils from core.misc.ipaddress import MacAddress from core.service import ServiceManager, ServiceShim from core.xml import xmlutils 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(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 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.set_model_config(node_id=nodenum, model_name=model_name, config=link_params) if mobility_model_name and mobility_params: self.session.mobility.set_model_config(node_id=nodenum, model_name=mobility_model_name, config=mobility_params) 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: