Source code for fnss.adapters.jfed

"""Adapter for jFed

Provides function to convert an FNSS Topology object into a jFed
rspec file and viceversa.

`jFed <http://jfed.iminds.be/>_` is a Java-based framework to support the
integration of federated testbed, developed by
`iMinds <http://www.iminds.be/>_` in the contex of the
`Fed4FIRE <http://www.fed4fire.eu/>_` project funded by the Framework
Programme 7 (FP7) of the European Union.
"""
from __future__ import division
import xml.etree.cElementTree as ET

import networkx as nx

from fnss import Topology, get_delays, get_capacities
import fnss.util as util
import fnss.units as units


__all__ = ['to_jfed', 'from_jfed']


[docs]def to_jfed(topology, path, testbed="wall1.ilabt.iminds.be", encoding="utf-8", prettyprint=True): """Convert a topology object into an RSPEC file for jFed Parameters ---------- topology : Topology The topology object path : str The file to which the RSPEC will be written testbed : str, optional URI of the testbed to use encoding : str, optional The encoding of the target file prettyprint : bool, optional Indent the XML code in the output file Notes ----- It currently supports only undirected topologies, if a topology is directed it is converted to undirected """ if topology.is_directed(): topology = topology.to_undirected() topology = nx.convert_node_labels_to_integers(topology) if 'capacity_unit' in topology.graph: capacity_norm = units.capacity_units[topology.graph['capacity_unit']] / units.capacity_units['Kbps'] if 'delay_unit' in topology.graph: delay_norm = units.time_units[topology.graph['delay_unit']] / units.time_units['ms'] delays = get_delays(topology) capacities = get_capacities(topology) # Node positions (randomly generated) pos = nx.random_layout(topology) # Create mapping between links and interface IDs if_names = {} for v in topology.adj: next_hops = sorted(topology.adj[v].keys()) if_names[v] = {next_hop: i for i, next_hop in enumerate(next_hops)} head = ET.Element('rspec') head.attrib["generated_by"] = "FNSS" head.attrib['xsi:schemaLocation'] = "http://www.geni.net/resources/rspec/3 http://www.geni.net/resources/rspec/3/request.xsd" head.attrib['xmlns'] = "http://www.geni.net/resources/rspec/3" head.attrib["xmlns:jFed"] = "http://jfed.iminds.be/rspec/ext/jfed/1" head.attrib["xmlns:jFedBonfire"] = "http://jfed.iminds.be/rspec/ext/jfed-bonfire/1" head.attrib["xmlns:delay"] = "http://www.protogeni.net/resources/rspec/ext/delay/1" head.attrib["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance" # Iterate over nodes for v in topology.nodes(): node = ET.SubElement(head, 'node') node.attrib['client_id'] = "node%s" % str(v) node.attrib['component_manager_id'] = "urn:publicid:IDN+%s+authority+cm" % testbed node.attrib["exclusive"] = "true" sliver_type = ET.SubElement(node, 'sliver_type') sliver_type.attrib['name'] = topology.node[v]['sliver_type'] if 'sliver_type' in topology.node[v] else 'raw-pc' location = ET.SubElement(node, 'jFed:location') x, y = pos[v] location.attrib['x'] = str(1000 * x) location.attrib['y'] = str(500 * y) for if_name in if_names[v].values(): interface = ET.SubElement(node, 'interface') interface.attrib['client_id'] = "node%s:if%s" % (str(v), str(if_name)) # The convention in jFed is to identify links with "linkX" where X is an # integer but making sure that links and nodes have different integers link_id = topology.number_of_nodes() - 1 for u, v in topology.edges(): link_id += 1 link = ET.SubElement(head, 'link') link.attrib['client_id'] = "link%s" % str(link_id) component_manager = ET.SubElement(link, 'component_manager') component_manager.attrib['name'] = "urn:publicid:IDN+%s+authority+cm" % testbed u_if = "node%s:if%s" % (str(u), str(if_names[u][v])) v_if = "node%s:if%s" % (str(v), str(if_names[v][u])) for source, dest in ((u_if, v_if), (v_if, u_if)): prop = ET.SubElement(link, 'property') prop.attrib["source_id"] = source prop.attrib["dest_id"] = dest if (u, v) in delays: prop.attrib['latency'] = str(delay_norm * delays[(u, v)]) if (u, v) in capacities: prop.attrib['capacity'] = str(capacity_norm * capacities[(u, v)]) interface_ref = ET.SubElement(link, 'interface_ref') interface_ref.attrib['client_id'] = source if prettyprint: util.xml_indent(head) ET.ElementTree(head).write(path, encoding=encoding)
[docs]def from_jfed(path): """Read a jFed RSPEC file and returns an FNSS topology modelling the network topology of the jFed experiment specification. Parameters ---------- path : str The path of the jFed RSPEC file to parse Returns ------- topology: Topology The parsed topology Notes ----- This function does not support directed topologies and unidirectional links It is possible in jFed to create multipoint links (links with more than 2 endpoints). Such types of link cannot be modelled in FNSS. Therefore, any attempt to convert an RSPEC with such links will fail. """ # This implementation could be improved by using SOAP libraries, but it # would require to add another dependency to the project. # The current implementation, although not really elegant, works fine tree = ET.parse(path) head = tree.getroot() xmlns = "http://www.geni.net/resources/rspec/3" topology = Topology() # Flags to indicate whether the parsed topology is annotated with # capacities and delays has_delays = False has_capacities = False # Dict mapping interface name to the node it belongs to if_map = {} # Iterate over nodes for node in head.findall('{%s}node' % xmlns): client_id = node.attrib['client_id'] component_manager_id = node.attrib['component_manager_id'] exclusive = bool(node.attrib['exclusive']) for interface in node.findall('{%s}interface' % xmlns): if_id = interface.attrib['client_id'] if_map[if_id] = client_id topology.add_node(client_id, component_manager_id=component_manager_id, exclusive=exclusive) # Iterate over links for edge in head.findall('{%s}link' % xmlns): client_id = edge.attrib['client_id'] component_manager = edge.find('{%s}component_manager' % xmlns).attrib['name'] interfaces = edge.findall('{%s}interface_ref' % xmlns) # A link may connect more than two nodes. These links cannot be # represented in an FNSS Topology. # This statement also encompasses the potential case of one interface # only that should not happen if the file is correctly formatted. if len(interfaces) != 2: raise ValueError("Link %s is not a point-to-point link but a shared " "medium connecting %d interfaces. These links are " "not supported. Change your topology and try again" % (client_id, len(interfaces))) u = if_map[interfaces[0].attrib['client_id']] v = if_map[interfaces[1].attrib['client_id']] edge_attr = dict(component_manager=component_manager, client_id=client_id) # There are normally two property tags per edge. We only parse one # because should have same attributes if links are bidirectional. # This function does not support unidirectional links prop = edge.find('{%s}property' % xmlns) if prop: if 'capacity' in prop.attrib: edge_attr['capacity'] = prop.attrib['capacity'] has_capacities = True if 'latency' in prop.attrib: edge_attr['delay'] = prop.attrib['latency'] has_delays = True topology.add_edge(u, v, **edge_attr) # Set capacity and delay units if has_capacities: topology.graph['capacity_unit'] = 'kbps' if has_delays: topology.graph['delay_unit'] = 'ms' return topology