From: Alina Quereilhac Date: Sun, 3 Aug 2014 18:13:16 +0000 (+0200) Subject: Merging nepi-3.1-multirun into nepi-3-dev X-Git-Tag: nepi-3.2.0~105 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=6096716dbc88a1d9e6a1be8cac477006225d890e;hp=a4338a678b157a98f4c8e068d1126fa3b515ae1b;p=nepi.git Merging nepi-3.1-multirun into nepi-3-dev --- diff --git a/setup.py b/setup.py index 00001d8b..c8247ec2 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,6 @@ setup( platforms = "Linux, OSX", packages = [ "nepi", - "nepi.design", "nepi.execution", "nepi.resources", "nepi.resources.all", @@ -27,7 +26,8 @@ setup( "nepi.resources.omf", "nepi.resources.planetlab", "nepi.resources.planetlab.openvswitch", - "nepi.util"], + "nepi.util", + "nepi.util.parsers"], package_dir = {"": "src"}, package_data = { "nepi.resources.planetlab" : [ "scripts/*.py" ], diff --git a/src/nepi/design/__init__.py b/src/nepi/design/__init__.py deleted file mode 100644 index 013e4b7e..00000000 --- a/src/nepi/design/__init__.py +++ /dev/null @@ -1 +0,0 @@ -#!/usr/bin/python diff --git a/src/nepi/design/box.py b/src/nepi/design/box.py deleted file mode 100644 index 2da0710a..00000000 --- a/src/nepi/design/box.py +++ /dev/null @@ -1,121 +0,0 @@ -# -# NEPI, a framework to manage network experiments -# Copyright (C) 2013 INRIA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# -# Author: Alina Quereilhac - -from nepi.util import guid - -guid_gen = guid.GuidGenerator() - -class Attributes(object): - def __init__(self): - super(Attributes, self).__init__() - self._attributes = dict() - - def __getattr__(self, name): - try: - return self._attributes[name] - except: - return super(Attributes, self).__getattribute__(name) - - def __setattr__(self, name, value): - try: - if value == None: - old = self._attributes[name] - del self._attributes[name] - return old - - self._attributes[name] = value - return value - except: - return super(Attributes, self).__setattr__(name, value) - -class Connections(object): - def __init__(self): - super(Connections, self).__init__() - self._connections = set() - - def __getattr__(self, guid_or_label): - try: - for b in self._connections: - if guid_or_label in [b.guid, b.label]: - return b - except: - return super(Connections, self).__getattribute__(guid_or_label) - -class Box(object): - def __init__(self, label = None, guid = None): - super(Box, self).__init__() - self._guid = guid_gen.next(guid) - self._a = Attributes() - self._c = Connections() - self._tags = set() - self.label = label or self._guid - - # Graphical information to draw box - self.x = 0 - self.y = 0 - self.width = 4 - self.height = 4 - - @property - def tags(self): - return self._tags - - @property - def attributes(self): - return self._a._attributes.keys() - - @property - def a(self): - return self._a - - @property - def c(self): - return self._c - - @property - def guid(self): - return self._guid - - @property - def connections(self): - return set(self._c._connections) - - def tadd(self, name): - self._tags.add(name) - - def tdel(self, name): - self._tags.remove(name) - - def thas(self, name): - return name in self._tags - - def connect(self, box, cascade = True): - self._c._connections.add(box) - if cascade: - box.connect(self, cascade = False) - - def disconnect(self, box, cascade = True): - self._c._connections.remove(box) - if cascade: - box.disconnect(self, cascade = False) - - def is_connected(self, box): - return box in self.connections - diff --git a/src/nepi/execution/attribute.py b/src/nepi/execution/attribute.py index fa2b1043..19fbfc2c 100644 --- a/src/nepi/execution/attribute.py +++ b/src/nepi/execution/attribute.py @@ -190,6 +190,7 @@ class Attribute(object): adequate validation""" return True + @property def has_changed(self): """ Returns true if the value has changed from the default """ return self.value != self.default diff --git a/src/nepi/execution/ec.py b/src/nepi/execution/ec.py index 257742ee..31e11ef5 100644 --- a/src/nepi/execution/ec.py +++ b/src/nepi/execution/ec.py @@ -24,6 +24,8 @@ from nepi.execution.resource import ResourceFactory, ResourceAction, \ ResourceState, ResourceState2str from nepi.execution.scheduler import HeapScheduler, Task, TaskStatus from nepi.execution.trace import TraceAttr +from nepi.util.serializer import ECSerializer, SFormats +from nepi.util.plotter import ECPlotter, PFormats # TODO: use multiprocessing instead of threading # TODO: Allow to reconnect to a running experiment instance! (reconnect mode vs deploy mode) @@ -151,6 +153,12 @@ class ExperimentController(object): """ + @classmethod + def load(cls, path, format = SFormats.XML): + serializer = ECSerializer() + ec = serializer.load(path) + return ec + def __init__(self, exp_id = None): super(ExperimentController, self).__init__() @@ -200,8 +208,8 @@ class ExperimentController(object): # The runner is a pool of threads used to parallelize # execution of tasks - nthreads = int(os.environ.get("NEPI_NTHREADS", "20")) - self._runner = ParallelRun(maxthreads = nthreads) + self._nthreads = 20 + self._runner = None # Event processing thread self._cond = threading.Condition() @@ -246,6 +254,14 @@ class ExperimentController(object): """ return self._run_id + @property + def nthreads(self): + """ Returns the number of processing nthreads used + + """ + return self._nthreads + + @property def abort(self): """ Returns True if the experiment has failed and should be interrupted, @@ -365,7 +381,23 @@ class ExperimentController(object): guids.append(guid) time.sleep(0.5) - + + def plot(self, fpath = None, format= PFormats.FIGURE, persist = False): + plotter = ECPlotter() + fpath = plotter.plot(self, fpath = fpath, format= format, + persist = persist) + return fpath + + def serialize(self, format = SFormats.XML): + serializer = ECSerializer() + sec = serializer.load(self, format = format) + return sec + + def save(self, path, format = SFormats.XML): + serializer = ECSerializer() + path = serializer.save(self, path, format = format) + return path + def get_task(self, tid): """ Returns a task by its id @@ -380,7 +412,7 @@ class ExperimentController(object): def get_resource(self, guid): """ Returns a registered ResourceManager by its guid - :param guid: Id of the task + :param guid: Id of the resource :type guid: int :rtype: ResourceManager @@ -389,6 +421,21 @@ class ExperimentController(object): rm = self._resources.get(guid) return rm + def get_resources_by_type(self, rtype): + """ Returns a registered ResourceManager by its guid + + :param rtype: Resource type + :type rtype: string + + :rtype: list of ResourceManagers + + """ + rms = [] + for guid, rm in self._resources.iteritems(): + if rm.get_rtype() == type: + rms.append(rm) + return rms + def remove_resource(self, guid): del self._resources[guid] @@ -1000,6 +1047,8 @@ class ExperimentController(object): """ + self._nthreads = int(os.environ.get("NEPI_NTHREADS", str(self._nthreads))) + self._runner = ParallelRun(maxthreads = self.nthreads) self._runner.start() while not self._stop: diff --git a/src/nepi/execution/resource.py b/src/nepi/execution/resource.py index 5ec0be38..c389cfe2 100644 --- a/src/nepi/execution/resource.py +++ b/src/nepi/execution/resource.py @@ -31,7 +31,7 @@ import sys import threading import weakref -reschedule_delay = "1s" +reschedule_delay = "0.5s" class ResourceAction: """ Action that a user can order to a Resource Manager @@ -615,7 +615,7 @@ class ResourceManager(Logger): :rtype: str """ attr = self._attrs[name] - return attr.has_changed() + return attr.has_changed def has_flag(self, name, flag): """ Returns true if the attribute has the flag 'flag' diff --git a/src/nepi/execution/runner.py b/src/nepi/execution/runner.py new file mode 100644 index 00000000..60e75a4f --- /dev/null +++ b/src/nepi/execution/runner.py @@ -0,0 +1,161 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController + +import math +import numpy +import os +import tempfile +import time + +class ExperimentRunner(object): + """ The ExperimentRunner entity is reponsible of + re-running an experiment described by an ExperimentController + multiple time. + + """ + def __init__(self): + super(ExperimentRunner, self).__init__() + + def run(self, ec, min_runs = 1, max_runs = -1, wait_time = 0, + wait_guids = [], compute_metric_callback = None, + evaluate_convergence_callback = None ): + """ Re-runs a same experiment multiple times + + :param ec: Experiment description of experiment to run + :type name: ExperimentController + :rtype: EperimentController + + :param min_runs: Minimum number of repetitions for experiment + :type name: int + :rtype: int + + :param max_runs: Maximum number of repetitions for experiment + :type name: int + :rtype: int + + :param wait_time: Time to wait in seconds between invoking + ec.deploy() and ec.release() + :type name: float + :rtype: float + + :param wait_guids: List of guids to pass to ec.wait_finished + after invoking ec.deploy() + :type name: list + :rtype: list of int + + :param compute_metric_callback: function to invoke after each + experiment run, to compute an experiment metric. + It will be invoked with the ec and the run count as arguments, + and it must return a numeric value for the computed metric: + + metric = compute_metric_callback(ec, run) + + :type name: function + :rtype: function + + :param evaluate_convergence_callback: function to evaluate whether the + collected metric samples have converged and the experiment runner + can stop. It will be invoked with the ec, the run count and the + list of collected metric samples as argument, and it must return + either True or False: + + stop = evaluate_convergence_callback(ec, run, metrics) + + If stop is True, then the runner will exit. + + :type name: function + :rtype: function + + """ + + if (not max_runs or max_runs < 0) and not compute_metric_callback: + msg = "Undefined STOP condition, set stop_callback or max_runs" + raise RuntimeError, msg + + if compute_metric_callback and not evaluate_convergence_callback: + evaluate_convergence_callback = self.evaluate_normal_convergence + ec.logger.info(" Treating data as normal to evaluate convergence. " + "Experiment will stop when the standard error with 95% " + "confidence interval is >= 5% of the mean of the collected samples ") + + # Set useRunId = True in Collectors to make sure results are + # independently stored. + collectors = ec.get_resources_by_type("Collector") + for collector in collectors: + collector.set("useRunId", True) + + dirpath = tempfile.mkdtemp() + filepath = ec.save(dirpath) + + samples = [] + run = 0 + while True: + run += 1 + + ec = self.run_experiment(filepath, wait_time, wait_guids) + + ec.logger.info(" RUN %d \n" % run) + + if run >= min_runs and max_runs > -1 and run >= max_runs : + break + + if compute_metric_callback: + metric = compute_metric_callback(ec, run) + if metric is not None: + samples.append(metric) + + if run >= min_runs and evaluate_convergence_callback: + if evaluate_convergence_callback(ec, run, samples): + break + del ec + + return run + + def evaluate_normal_convergence(self, ec, run, samples): + if len(samples) == 0: + msg = "0 samples collected" + raise RuntimeError, msg + + x = numpy.array(samples) + n = len(samples) + std = x.std() + se = std / math.sqrt(n) + m = x.mean() + se95 = se * 2 + + ec.logger.info(" RUN %d - SAMPLES %d MEAN %.2f STD %.2f SE95%% %.2f \n" % ( + run, n, m, std, se95 ) ) + + return m * 0.05 >= se95 + + def run_experiment(self, filepath, wait_time, wait_guids): + ec = ExperimentController.load(filepath) + + ec.deploy() + + ec.wait_finished(wait_guids) + time.sleep(wait_time) + + ec.release() + + return ec + + diff --git a/src/nepi/resources/all/collector.py b/src/nepi/resources/all/collector.py index bb8c1c8d..af0811cf 100644 --- a/src/nepi/resources/all/collector.py +++ b/src/nepi/resources/all/collector.py @@ -28,7 +28,7 @@ import tempfile @clsinit_copy class Collector(ResourceManager): - """ The collector is reponsible of collecting traces + """ The collector entity is reponsible of collecting traces of a same type associated to RMs into a local directory. .. class:: Class Args : diff --git a/src/nepi/resources/ns3/ns3base.py b/src/nepi/resources/ns3/ns3base.py index f86bea77..455e546f 100644 --- a/src/nepi/resources/ns3/ns3base.py +++ b/src/nepi/resources/ns3/ns3base.py @@ -32,6 +32,7 @@ class NS3Base(ResourceManager): self._uuid = None self._connected = set() self._trace_filename = dict() + self._node = None @property def connected(self): @@ -47,10 +48,12 @@ class NS3Base(ResourceManager): @property def node(self): - from nepi.resources.ns3.ns3node import NS3BaseNode - nodes = self.get_connected(NS3BaseNode.get_rtype()) - if nodes: return nodes[0] - return None + if not self._node: + from nepi.resources.ns3.ns3node import NS3BaseNode + nodes = self.get_connected(NS3BaseNode.get_rtype()) + if nodes: self._nodes[0] + + return self._node def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0): filename = self._trace_filename.get(name) @@ -80,7 +83,7 @@ class NS3Base(ResourceManager): kwargs = dict() for attr in self._attrs.values(): - if not ( attr.has_flag(Flags.Construct) and attr.has_changed() ): + if not ( attr.has_flag(Flags.Construct) and attr.has_changed ): continue kwargs[attr.name] = attr._value diff --git a/src/nepi/resources/ns3/ns3node.py b/src/nepi/resources/ns3/ns3node.py index 48a7c16d..a85ba0dc 100644 --- a/src/nepi/resources/ns3/ns3node.py +++ b/src/nepi/resources/ns3/ns3node.py @@ -25,6 +25,15 @@ from nepi.resources.ns3.ns3base import NS3Base class NS3BaseNode(NS3Base): _rtype = "abstract::ns3::Node" + def __init__(self, ec, guid): + super(NS3BaseNode, self).__init__(ec, guid) + self._simulation = None + self._ipv4 = None + self._arp = None + self._mobility = None + self._devices = None + self._dceapplications = None + @classmethod def _register_attributes(cls): enablestack = Attribute("enableStack", @@ -38,55 +47,72 @@ class NS3BaseNode(NS3Base): @property def simulation(self): - from nepi.resources.ns3.ns3simulation import NS3Simulation - for guid in self.connections: - rm = self.ec.get_resource(guid) - if isinstance(rm, NS3Simulation): - return rm - - msg = "Node not connected to simulation" - self.error(msg) - raise RuntimeError, msg - + if not self._simulation: + from nepi.resources.ns3.ns3simulation import NS3Simulation + for guid in self.connections: + rm = self.ec.get_resource(guid) + if isinstance(rm, NS3Simulation): + self._simulation = rm + + if not self._simulation: + msg = "Node not connected to simulation" + self.error(msg) + raise RuntimeError, msg + + return self._simulation + @property def ipv4(self): - from nepi.resources.ns3.ns3ipv4l3protocol import NS3BaseIpv4L3Protocol - ipv4s = self.get_connected(NS3BaseIpv4L3Protocol.get_rtype()) - if ipv4s: return ipv4s[0] - return None + if not self._ipv4: + from nepi.resources.ns3.ns3ipv4l3protocol import NS3BaseIpv4L3Protocol + ipv4s = self.get_connected(NS3BaseIpv4L3Protocol.get_rtype()) + if ipv4s: + self._ipv4 = ipv4s[0] + + return self._ipv4 @property def arp(self): - from nepi.resources.ns3.ns3arpl3protocol import NS3BaseArpL3Protocol - arps = self.get_connected(NS3BaseArpL3Protocol.get_rtype()) - if arps: return arps[0] - return None + if not self._arp: + from nepi.resources.ns3.ns3arpl3protocol import NS3BaseArpL3Protocol + arps = self.get_connected(NS3BaseArpL3Protocol.get_rtype()) + if arps: + self._arp = arps[0] + + return self._arp @property def mobility(self): - from nepi.resources.ns3.ns3mobilitymodel import NS3BaseMobilityModel - mobility = self.get_connected(NS3BaseMobilityModel.get_rtype()) - if mobility: return mobility[0] - return None + if not self._mobility: + from nepi.resources.ns3.ns3mobilitymodel import NS3BaseMobilityModel + mobility = self.get_connected(NS3BaseMobilityModel.get_rtype()) + if mobility: + self._mobility = mobility[0] + + return self._mobility @property def devices(self): - from nepi.resources.ns3.ns3netdevice import NS3BaseNetDevice - devices = self.get_connected(NS3BaseNetDevice.get_rtype()) + if not self._devices: + from nepi.resources.ns3.ns3netdevice import NS3BaseNetDevice + devices = self.get_connected(NS3BaseNetDevice.get_rtype()) + + if not devices: + msg = "Node not connected to devices" + self.error(msg) + raise RuntimeError, msg - if not devices: - msg = "Node not connected to devices" - self.error(msg) - raise RuntimeError, msg + self._devices = devices - return devices + return self._devices @property def dceapplications(self): - from nepi.resources.ns3.ns3dceapplication import NS3BaseDceApplication - dceapplications = self.get_connected(NS3BaseDceApplication.get_rtype()) + if not self._dceapplications: + from nepi.resources.ns3.ns3dceapplication import NS3BaseDceApplication + self._dceapplications = self.get_connected(NS3BaseDceApplication.get_rtype()) - return dceapplications + return self._dceapplications @property def _rms_to_wait(self): diff --git a/src/nepi/util/parser.py b/src/nepi/util/parser.py deleted file mode 100644 index 58cb79b9..00000000 --- a/src/nepi/util/parser.py +++ /dev/null @@ -1,165 +0,0 @@ -# -# NEPI, a framework to manage network experiments -# Copyright (C) 2013 INRIA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Author: Alina Quereilhac - -from nepi.design.box import Box - -from xml.dom import minidom -import sys - -STRING = "string" -BOOL = "bool" -INTEGER = "integer" -DOUBLE = "float" - -def xmlencode(s): - if isinstance(s, str): - rv = s.decode("latin1") - elif not isinstance(s, unicode): - rv = unicode(s) - else: - rv = s - return rv.replace(u'\x00',u'�') - -def xmldecode(s): - return s.replace(u'�',u'\x00').encode("utf8") - -def from_type(value): - if isinstance(value, str): - return STRING - if isinstance(value, bool): - return BOOL - if isinstance(value, int): - return INTEGER - if isinstance(value, float): - return DOUBLE - -def to_type(type, value): - if type == STRING: - return str(value) - if type == BOOL: - return value == "True" - if type == INTEGER: - return int(value) - if type == DOUBLE: - return float(value) - -class XMLParser(object): - def to_xml(self, box): - doc = minidom.Document() - - root = doc.createElement("boxes") - doc.appendChild(root) - - traversed = dict() - self._traverse_boxes(doc, traversed, box) - - # Keep the order - for guid in sorted(traversed.keys()): - bnode = traversed[guid] - root.appendChild(bnode) - - try: - xml = doc.toprettyxml(indent=" ", encoding="UTF-8") - except: - print >>sys.stderr, "Oops: generating XML from %s" % (data,) - raise - - return xml - - def _traverse_boxes(self, doc, traversed, box): - bnode = doc.createElement("box") - bnode.setAttribute("guid", xmlencode(box.guid)) - bnode.setAttribute("label", xmlencode(box.label)) - bnode.setAttribute("x", xmlencode(box.x)) - bnode.setAttribute("y", xmlencode(box.y)) - bnode.setAttribute("width", xmlencode(box.width)) - bnode.setAttribute("height", xmlencode(box.height)) - - traversed[box.guid] = bnode - - anode = doc.createElement("attributes") - bnode.appendChild(anode) - for name in sorted(box.attributes): - value = getattr(box.a, name) - aanode = doc.createElement("attribute") - anode.appendChild(aanode) - aanode.setAttribute("name", xmlencode(name)) - aanode.setAttribute("value", xmlencode(value)) - aanode.setAttribute("type", from_type(value)) - - tnode = doc.createElement("tags") - bnode.appendChild(tnode) - for tag in sorted(box.tags): - ttnode = doc.createElement("tag") - tnode.appendChild(ttnode) - ttnode.setAttribute("name", xmlencode(tag)) - - cnode = doc.createElement("connections") - bnode.appendChild(cnode) - for b in sorted(box.connections): - ccnode = doc.createElement("connection") - cnode.appendChild(ccnode) - ccnode.setAttribute("guid", xmlencode(b.guid)) - if b.guid not in traversed: - self._traverse_boxes(doc, traversed, b) - - def from_xml(self, xml): - doc = minidom.parseString(xml) - bnode_list = doc.getElementsByTagName("box") - - boxes = dict() - connections = dict() - - for bnode in bnode_list: - if bnode.nodeType == doc.ELEMENT_NODE: - guid = int(bnode.getAttribute("guid")) - label = xmldecode(bnode.getAttribute("label")) - x = float(bnode.getAttribute("x")) - y = float(bnode.getAttribute("y")) - height = float(bnode.getAttribute("height")) - width = float(bnode.getAttribute("width")) - box = Box(label=label, guid=guid) - boxes[guid] = box - - anode_list = bnode.getElementsByTagName("attribute") - for anode in anode_list: - name = xmldecode(anode.getAttribute("name")) - value = xmldecode(anode.getAttribute("value")) - type = xmldecode(anode.getAttribute("type")) - value = to_type(type, value) - setattr(box.a, name, value) - - tnode_list = bnode.getElementsByTagName("tag") - for tnode in tnode_list: - value = xmldecode(tnode.getAttribute("name")) - box.tadd(value) - - connections[box] = set() - cnode_list = bnode.getElementsByTagName("connection") - for cnode in cnode_list: - guid = int(cnode.getAttribute("guid")) - connections[box].add(guid) - - for box, conns in connections.iteritems(): - for guid in conns: - b = boxes[guid] - box.connect(b) - - return box - diff --git a/src/nepi/util/parsers/__init__.py b/src/nepi/util/parsers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/nepi/util/parsers/xml_parser.py b/src/nepi/util/parsers/xml_parser.py new file mode 100644 index 00000000..d5bc8a3f --- /dev/null +++ b/src/nepi/util/parsers/xml_parser.py @@ -0,0 +1,294 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.util.timefuncs import stformat, tsformat + +from xml.dom import minidom + +import datetime +import sys +import os + +STRING = "string" +BOOL = "bool" +INTEGER = "integer" +DOUBLE = "float" + +def xmlencode(s): + if isinstance(s, str): + rv = s.decode("latin1") + if isinstance(s, datetime.datetime): + rv = tsformat(s) + elif not isinstance(s, unicode): + rv = unicode(s) + else: + rv = s + return rv.replace(u'\x00',u'�') + +def xmldecode(s, cast = str): + ret = s.replace(u'�',u'\x00').encode("ascii") + ret = cast(ret) + if s == "None": + return None + return ret + +def from_type(value): + if isinstance(value, bool): + return BOOL + if isinstance(value, int): + return INTEGER + if isinstance(value, float): + return DOUBLE + + return STRING + +def to_type(type, value): + if not value: + return value + + if type == STRING: + return str(value) + if type == BOOL: + return value == "True" + if type == INTEGER: + return int(value) + if type == DOUBLE: + return float(value) + +class ECXMLParser(object): + def to_xml(self, ec): + + doc = minidom.Document() + + self._ec_to_xml(doc, ec) + + try: + xml = doc.toprettyxml(indent=" ", encoding="UTF-8") + except: + print >>sys.stderr, "Oops: generating XML from %s" % (data,) + raise + + return xml + + def _ec_to_xml(self, doc, ec): + ecnode = doc.createElement("experiment") + ecnode.setAttribute("exp_id", xmlencode(ec.exp_id)) + ecnode.setAttribute("run_id", xmlencode(ec.run_id)) + ecnode.setAttribute("nthreads", xmlencode(ec.nthreads)) + doc.appendChild(ecnode) + + for guid, rm in ec._resources.iteritems(): + self._rm_to_xml(doc, ecnode, ec, guid, rm) + + return doc + + def _rm_to_xml(self, doc, ecnode, ec, guid, rm): + rmnode = doc.createElement("rm") + rmnode.setAttribute("guid", xmlencode(guid)) + rmnode.setAttribute("rtype", xmlencode(rm._rtype)) + rmnode.setAttribute("state", xmlencode(rm._state)) + if rm._start_time: + rmnode.setAttribute("start_time", xmlencode(rm._start_time)) + if rm._stop_time: + rmnode.setAttribute("stop_time", xmlencode(rm._stop_time)) + if rm._discover_time: + rmnode.setAttribute("discover_time", xmlencode(rm._discover_time)) + if rm._provision_time: + rmnode.setAttribute("provision_time", xmlencode(rm._provision_time)) + if rm._ready_time: + rmnode.setAttribute("ready_time", xmlencode(rm._ready_time)) + if rm._release_time: + rmnode.setAttribute("release_time", xmlencode(rm._release_time)) + if rm._failed_time: + rmnode.setAttribute("failed_time", xmlencode(rm._failed_time)) + ecnode.appendChild(rmnode) + + anode = doc.createElement("attributes") + attributes = False + + for attr in rm._attrs.values(): + if attr.has_changed: + attributes = True + aanode = doc.createElement("attribute") + aanode.setAttribute("name", xmlencode(attr.name)) + aanode.setAttribute("value", xmlencode(attr.value)) + aanode.setAttribute("type", from_type(attr.value)) + anode.appendChild(aanode) + + if attributes: + rmnode.appendChild(anode) + + cnode = doc.createElement("connections") + connections = False + + for guid in rm._connections: + connections = True + ccnode = doc.createElement("connection") + ccnode.setAttribute("guid", xmlencode(guid)) + cnode.appendChild(ccnode) + + if connections: + rmnode.appendChild(cnode) + + cnnode = doc.createElement("conditions") + conditions = False + + for action, conds in rm._conditions.iteritems(): + conditions = True + for (group, state, time) in conds: + ccnnode = doc.createElement("condition") + ccnnode.setAttribute("action", xmlencode(action)) + ccnnode.setAttribute("group", xmlencode(group)) + ccnnode.setAttribute("state", xmlencode(state)) + ccnnode.setAttribute("time", xmlencode(time)) + cnnode.appendChild(ccnnode) + + if conditions: + rmnode.appendChild(cnnode) + + tnode = doc.createElement("traces") + traces = False + + for trace in rm._trcs.values(): + if trace.enabled: + traces = True + ttnode = doc.createElement("trace") + ttnode.setAttribute("name", xmlencode(trace.name)) + tnode.appendChild(ttnode) + + if traces: + rmnode.appendChild(tnode) + + def from_xml(self, xml): + doc = minidom.parseString(xml) + return self._ec_from_xml(doc) + + def _ec_from_xml(self, doc): + from nepi.execution.ec import ExperimentController + ec = None + + ecnode_list = doc.getElementsByTagName("experiment") + for ecnode in ecnode_list: + if ecnode.nodeType == doc.ELEMENT_NODE: + exp_id = xmldecode(ecnode.getAttribute("exp_id")) + run_id = xmldecode(ecnode.getAttribute("run_id")) + nthreads = xmldecode(ecnode.getAttribute("nthreads")) + + os.environ["NEPI_NTHREADS"] = nthreads + ec = ExperimentController(exp_id = exp_id) + + connections = set() + + rmnode_list = ecnode.getElementsByTagName("rm") + for rmnode in rmnode_list: + if rmnode.nodeType == doc.ELEMENT_NODE: + self._rm_from_xml(doc, rmnode, ec, connections) + + for (guid1, guid2) in connections: + ec.register_connection(guid1, guid2) + + break + + return ec + + def _rm_from_xml(self, doc, rmnode, ec, connections): + start_time = None + stop_time = None + discover_time = None + provision_time = None + ready_time = None + release_time = None + failed_time = None + + guid = xmldecode(rmnode.getAttribute("guid"), int) + rtype = xmldecode(rmnode.getAttribute("rtype")) + + # FOR NOW ONLY STATE NEW IS ALLOWED + state = 0 + """ + state = xmldecode(rmnode.getAttribute("state"), int) + + if rmnode.hasAttribute("start_time"): + start_time = xmldecode(rmnode.getAttribute("start_time"), + datetime.datetime) + if rmnode.hasAttribute("stop_time"): + stop_time = xmldecode(rmnode.getAttribute("stop_time"), + datetime.datetime) + if rmnode.hasAttribute("discover_time"): + dicover_time = xmldecode(rmnode.getAttribute("discover_time"), + datetime.datetime) + if rmnode.hasAttribute("provision_time"): + provision_time = xmldecode(rmnode.getAttribute("provision_time"), + datetime.datetime) + if rmnode.hasAttribute("ready_time"): + ready_time = xmldecode(rmnode.getAttribute("ready_time"), + datetime.datetime) + if rmnode.hasAttribute("release_time"): + release_time = xmldecode(rmnode.getAttribute("release_time"), + datetime.datetime) + if rmnode.hasAttribute("failed_time"): + failed_time = xmldecode(rmnode.getAttribute("failed_time"), + datetime.datetime) + """ + + ec.register_resource(rtype, guid = guid) + rm = ec.get_resource(guid) + rm.set_state_time(state, "_start_time", start_time) + rm.set_state_time(state, "_stop_time", stop_time) + rm.set_state_time(state, "_discover_time", discover_time) + rm.set_state_time(state, "_provision_time", provision_time) + rm.set_state_time(state, "_ready_time", ready_time) + rm.set_state_time(state, "_release_time", release_time) + rm.set_state_time(state, "_failed_time", failed_time) + + anode_list = rmnode.getElementsByTagName("attributes") + if anode_list: + aanode_list = anode_list[0].getElementsByTagName("attribute") + for aanode in aanode_list: + name = xmldecode(aanode.getAttribute("name")) + value = xmldecode(aanode.getAttribute("value")) + tipe = xmldecode(aanode.getAttribute("type")) + value = to_type(tipe, value) + rm.set(name, value) + + cnode_list = rmnode.getElementsByTagName("connections") + if cnode_list: + ccnode_list = cnode_list[0].getElementsByTagName("connection") + for ccnode in ccnode_list: + guid2 = xmldecode(ccnode.getAttribute("guid"), int) + connections.add((guid, guid2)) + + tnode_list = rmnode.getElementsByTagName("traces") + if tnode_list: + ttnode_list = tnode_list[0].getElementsByTagName("trace") + for ttnode in ttnode_list: + name = xmldecode(ttnode.getAttribute("name")) + ec.enable_trace(guid, name) + + cnnode_list = rmnode.getElementsByTagName("conditions") + if cnnode_list: + ccnnode_list = cnnode_list[0].getElementsByTagName("condition") + for ccnnode in ccnnode_list: + action = xmldecode(ccnnode.getAttribute("action"), int) + group = xmldecode(ccnnode.getAttribute("group"), eval) + state = xmldecode(ccnnode.getAttribute("state"), int) + time = xmldecode(ccnnode.getAttribute("time")) + time = to_type('STRING', time) + ec.register_condition(guid, action, group, state, time = time) + diff --git a/src/nepi/util/plotter.py b/src/nepi/util/plotter.py new file mode 100644 index 00000000..c21f5988 --- /dev/null +++ b/src/nepi/util/plotter.py @@ -0,0 +1,94 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +import networkx +import os + +class PFormats: + DOT = "dot" + FIGURE = "figure" + +class ECPlotter(object): + def plot(self, ec, fpath = None, format= PFormats.FIGURE, persist = False): + graph, labels = self._ec2graph(ec) + + add_extension = False + + if persist and not fpath: + import tempfile + dirpath = tempfile.mkdtemp() + fpath = os.path.join(dirpath, "%s_%s" % (ec.exp_id, ec.run_id)) + add_extension = True + + if format == PFormats.FIGURE: + import matplotlib.pyplot as plt + pos = networkx.graphviz_layout(graph, prog="neato") + networkx.draw(graph, pos = pos, node_color="white", + node_size = 500, with_labels=True) + + label = "\n".join(map(lambda v: "%s: %s" % (v[0], v[1]), labels.iteritems())) + plt.annotate(label, xy=(0.0, 0.95), xycoords='axes fraction') + + if persist: + if add_extension: + fpath += ".png" + + plt.savefig(fpath, bbox_inches="tight") + else: + plt.show() + + elif format == PFormats.DOT: + if persist: + if add_extension: + fpath += ".dot" + + networkx.write_dot(graph, fpath) + else: + import subprocess + subprocess.call(["dot", "-Tps", fpath, "-o", "%s.ps" % fpath]) + subprocess.call(["evince","%s.ps" % fpath]) + + return fpath + + def _ec2graph(self, ec): + graph = networkx.Graph(graph = dict(overlap = "false")) + + labels = dict() + connections = set() + + for guid, rm in ec._resources.iteritems(): + label = rm.get_rtype() + + graph.add_node(guid, + label = "%d %s" % (guid, label), + width = 50/72.0, # 1 inch = 72 points + height = 50/72.0, + shape = "circle") + + labels[guid] = label + + for guid2 in rm.connections: + # Avoid adding a same connection twice + if (guid2, guid) not in connections: + connections.add((guid, guid2)) + + for (guid1, guid2) in connections: + graph.add_edge(guid1, guid2) + + return graph, labels diff --git a/src/nepi/util/serializer.py b/src/nepi/util/serializer.py new file mode 100644 index 00000000..9ed216f2 --- /dev/null +++ b/src/nepi/util/serializer.py @@ -0,0 +1,61 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +import datetime +import os + +class SFormats: + XML = "xml" + +class ECSerializer(object): + def load(self, path, format = SFormats.XML): + if format == SFormats.XML: + from nepi.util.parsers.xml_parser import ECXMLParser + + parser = ECXMLParser() + f = open(path, "r") + xml = f.read() + f.close() + + ec = parser.from_xml(xml) + + return ec + + def serialize(self, ec, format = SFormats.XML): + if format == SFormats.XML: + from nepi.util.parsers.xml_parser import ECXMLParser + + parser = ECXMLParser() + sec = parser.to_xml(ec) + + return sec + + def save(self, ec, path, format = SFormats.XML): + date = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + filename = "%s_%s" % (ec.exp_id, date) + + if format == SFormats.XML: + path = os.path.join(path, "%s.xml" % filename) + sec = self.serialize(ec, format = format) + f = open(path, "w") + f.write(sec) + f.close() + + return path + diff --git a/test/design/box.py b/test/design/box.py deleted file mode 100755 index d1b9c031..00000000 --- a/test/design/box.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -# NEPI, a framework to manage network experiments -# Copyright (C) 2013 INRIA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Author: Alina Quereilhac - - -from nepi.design.box import Box - -import unittest - -class BoxDesignTestCase(unittest.TestCase): - def test_simple_design(self): - node1 = Box() - node2 = Box() - - node1.label = "uno" - node2.label = "dos" - - node1.tadd('nodo') - node2.tadd('mynodo') - - self.assertEquals(node1.tags, set(['nodo'])) - self.assertEquals(node2.tags, set(['mynodo'])) - - node1.a.hola = "chau" - node2.a.hello = "bye" - - self.assertEquals(node1.a.hola, "chau") - self.assertEquals(node2.a.hello, "bye") - - node1.connect(node2) - - self.assertEquals(node1.connections, set([node2])) - self.assertEquals(node2.connections, set([node1])) - self.assertTrue(node1.is_connected(node2)) - self.assertTrue(node2.is_connected(node1)) - - self.assertEquals(node1.c.dos.a.hello, "bye") - self.assertEquals(node2.c.uno.a.hola, "chau") - - node2.disconnect(node1) - - self.assertEquals(node1.connections, set([])) - self.assertEquals(node2.connections, set([])) - self.assertFalse(node1.is_connected(node2)) - self.assertFalse(node2.is_connected(node1)) - - self.assertRaises(AttributeError, node1.c.dos) - self.assertRaises(AttributeError, node2.c.uno) - - -if __name__ == '__main__': - unittest.main() - diff --git a/test/execution/runner.py b/test/execution/runner.py new file mode 100755 index 00000000..b06d3832 --- /dev/null +++ b/test/execution/runner.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController +from nepi.execution.resource import ResourceManager, ResourceState, \ + clsinit_copy, ResourceAction, ResourceFactory +from nepi.execution.runner import ExperimentRunner + +import functools +import os +import shutil +import tempfile +import time +import unittest + +reschedule_delay = "0.5s" +deploy_time = 0 +run_time = 0 + +class Link(ResourceManager): + _rtype = "dummy::Link" + def do_deploy(self): + time.sleep(deploy_time) + super(Link, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Interface(ResourceManager): + _rtype = "dummy::Interface" + + def do_deploy(self): + node = self.get_connected(Node.get_rtype())[0] + link = self.get_connected(Link.get_rtype())[0] + + if node.state < ResourceState.READY or \ + link.state < ResourceState.READY: + self.ec.schedule(reschedule_delay, self.deploy) + self.logger.debug(" -------- RESCHEDULING ------- ") + else: + time.sleep(deploy_time) + super(Interface, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Node(ResourceManager): + _rtype = "dummy::Node" + + def do_deploy(self): + self.logger.debug(" -------- DO_DEPLOY ------- ") + time.sleep(deploy_time) + super(Node, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Application(ResourceManager): + _rtype = "dummy::Application" + + def do_deploy(self): + node = self.get_connected(Node.get_rtype())[0] + + if node.state < ResourceState.READY: + self.ec.schedule(reschedule_delay, self.deploy) + self.logger.debug(" -------- RESCHEDULING ------- ") + else: + time.sleep(deploy_time) + super(Application, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + + def do_start(self): + super(Application, self).do_start() + time.sleep(run_time) + self.ec.schedule("0s", self.stop) + +ResourceFactory.register_type(Application) +ResourceFactory.register_type(Node) +ResourceFactory.register_type(Interface) +ResourceFactory.register_type(Link) + +class RunnerTestCase(unittest.TestCase): + def test_runner_max_runs(self): + node_count = 4 + app_count = 2 + + ec = ExperimentController(exp_id = "max-runs-test") + + # Add simulated nodes and applications + nodes = list() + apps = list() + ifaces = list() + + for i in xrange(node_count): + node = ec.register_resource("dummy::Node") + nodes.append(node) + + iface = ec.register_resource("dummy::Interface") + ec.register_connection(node, iface) + ifaces.append(iface) + + for i in xrange(app_count): + app = ec.register_resource("dummy::Application") + ec.register_connection(node, app) + apps.append(app) + + link = ec.register_resource("dummy::Link") + + for iface in ifaces: + ec.register_connection(link, iface) + + rnr = ExperimentRunner() + runs = rnr.run(ec, min_runs = 5, max_runs = 10, wait_guids = apps, + wait_time = 0) + + self.assertEquals(runs, 10) + + def test_runner_convergence(self): + node_count = 4 + app_count = 2 + + ec = ExperimentController(exp_id = "convergence-test") + + # Add simulated nodes and applications + nodes = list() + apps = list() + ifaces = list() + + for i in xrange(node_count): + node = ec.register_resource("dummy::Node") + nodes.append(node) + + iface = ec.register_resource("dummy::Interface") + ec.register_connection(node, iface) + ifaces.append(iface) + + for i in xrange(app_count): + app = ec.register_resource("dummy::Application") + ec.register_connection(node, app) + apps.append(app) + + link = ec.register_resource("dummy::Link") + + for iface in ifaces: + ec.register_connection(link, iface) + + samples = [10, 10, 10, 10, 12, 10, 12, 10, 10, 11] + + def compute_metric_callback(samples, ec, run): + return samples[run-1] + + metric_callback = functools.partial(compute_metric_callback, samples) + + rnr = ExperimentRunner() + runs = rnr.run(ec, min_runs = 5, + compute_metric_callback = metric_callback, + wait_guids = apps, + wait_time = 0) + + self.assertEquals(runs, 10) + +if __name__ == '__main__': + unittest.main() + diff --git a/test/resources/linux/application.py b/test/resources/linux/application.py index a1f1397b..0a76dcce 100755 --- a/test/resources/linux/application.py +++ b/test/resources/linux/application.py @@ -250,6 +250,7 @@ main (void) ec.register_connection(app, node) + ec.deploy() ec.wait_finished([app]) diff --git a/test/resources/linux/ccn/ccncat.py b/test/resources/linux/ccn/ccncat.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ccn/ccnpeek.py b/test/resources/linux/ccn/ccnpeek.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ccn/ccnping.py b/test/resources/linux/ccn/ccnping.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ccn/fibentry.py b/test/resources/linux/ccn/fibentry.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/multirun.py b/test/resources/linux/multirun.py new file mode 100755 index 00000000..d548bd2c --- /dev/null +++ b/test/resources/linux/multirun.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController +from nepi.execution.runner import ExperimentRunner + +from test_utils import skipIfNotAlive, skipInteractive + +import functools +import glob +import os +import re +import shutil +import time +import tempfile +import unittest + +_ping_re = re.compile("[^/]+rtt min/avg/max/mdev = (?P\d\.\d+)/(?P\d\.\d+)/(?P\d\.\d+)/(?P\d\.\d+)[^/]+", re.MULTILINE) + +class LinuxMultiRunTestCase(unittest.TestCase): + def setUp(self): + self.fedora_host = "nepi2.pl.sophia.inria.fr" + self.fedora_user = "inria_nepi" + + self.ubuntu_host = "roseval.pl.sophia.inria.fr" + self.ubuntu_user = "inria_nepi" + + self.target = "nepi5.pl.sophia.inria.fr" + + @skipIfNotAlive + def t_simple_multirun(self, host, user, depends): + + dirpath = tempfile.mkdtemp() + + ec = ExperimentController(exp_id="test-condition-multirun") + + node = ec.register_resource("LinuxNode") + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "cleanHome", True) + ec.set(node, "cleanProcesses", True) + + ping = ec.register_resource("LinuxApplication") + ec.set(ping, "command", "ping -c10 nepi.inria.fr") + ec.register_connection(ping, node) + + collector = ec.register_resource("Collector") + ec.set(collector, "traceName", "stdout") + ec.set(collector, "storeDir", dirpath) + ec.set(collector, "useRunId", True) + ec.register_connection(ping, collector) + + def compute_metric_callback(ping, ec, run): + stdout = ec.trace(ping, "stdout") + + m = _ping_re.match(stdout) + if not m: + return None + + return float(m.groupdict()["min"]) + + metric_callback = functools.partial(compute_metric_callback, ping) + + rnr = ExperimentRunner() + runs = rnr.run(ec, min_runs = 5, + compute_metric_callback = metric_callback, + wait_guids = [ping], + wait_time = 0) + + self.assertTrue(runs >= 5) + + dircount = 0 + for d in os.listdir(dirpath): + path = os.path.join(dirpath, d) + if os.path.isdir(path): + dircount += 1 + logs = glob.glob(os.path.join(path, "*.stdout")) + self.assertEquals(len(logs), 1) + + self.assertEquals(runs, dircount) + + shutil.rmtree(dirpath) + + def test_simple_multirun_fedora(self): + self.t_simple_multirun(self.fedora_host, self.fedora_user, "nc") + + def test_simple_multirun_ubuntu(self): + self.t_simple_multirun(self.ubuntu_host, self.ubuntu_user, "netcat") + +if __name__ == '__main__': + unittest.main() + diff --git a/test/resources/linux/ns3/ccn/ns3dceccn.py b/test/resources/linux/ns3/ccn/ns3dceccn.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ns3/ccn/ns3dceccnpeek.py b/test/resources/linux/ns3/ccn/ns3dceccnpeek.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ns3/ns3client.py b/test/resources/linux/ns3/ns3client.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ns3/ns3dceapplication.py b/test/resources/linux/ns3/ns3dceapplication.py old mode 100644 new mode 100755 index 8528f69a..3f9443a4 --- a/test/resources/linux/ns3/ns3dceapplication.py +++ b/test/resources/linux/ns3/ns3dceapplication.py @@ -136,6 +136,10 @@ class LinuxNS3DceApplicationTest(unittest.TestCase): self.fedora_user = "inria_nepi" self.fedora_identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME']) + self.ubuntu_host = "roseval.pl.sophia.inria.fr" + self.ubuntu_user = "inria_nepi" + self.ubuntu_identity = "%s/.ssh/id_rsa" % (os.environ['HOME']) + @skipIfNotAlive def t_dce_ping(self, host, user = None, identity = None): ec = ExperimentController(exp_id = "test-dce-ping") @@ -248,10 +252,17 @@ class LinuxNS3DceApplicationTest(unittest.TestCase): ### create applications ccnd1 = ec.register_resource("ns3::LinuxCCNDceApplication") - # NOTE THAT INSTALLATION MIGHT FAIL IF openjdk-6-jdk is not installed - ec.set(ccnd1, "depends", "libpcap0.8-dev openjdk-6-jdk ant1.8 autoconf " - "libssl-dev libexpat-dev libpcap-dev libecryptfs0 libxml2-utils auto" - "make gawk gcc g++ git-core pkg-config libpcre3-dev openjdk-6-jre-lib") + if host == self.fedora_host: + depends = ( " autoconf openssl-devel expat-devel libpcap-devel " + " ecryptfs-utils-devel libxml2-devel automake gawk " + " gcc gcc-c++ git pcre-devel make ") + else: # UBUNTU + # NOTE THAT INSTALLATION MIGHT FAIL IF openjdk-6-jdk is not installed + depends = ( "libpcap0.8-dev openjdk-6-jdk ant1.8 autoconf " + "libssl-dev libexpat-dev libpcap-dev libecryptfs0 libxml2-utils auto" + "make gawk gcc g++ git-core pkg-config libpcre3-dev openjdk-6-jre-lib") + + ec.set (ccnd1, "depends", depends) ec.set (ccnd1, "sources", "http://www.ccnx.org/releases/ccnx-0.7.2.tar.gz") ec.set (ccnd1, "build", "tar zxf ${SRC}/ccnx-0.7.2.tar.gz && " "cd ccnx-0.7.2 && " @@ -341,7 +352,10 @@ class LinuxNS3DceApplicationTest(unittest.TestCase): ec.shutdown() def test_dce_ping_fedora(self): - self.t_dce_ping(self.fedora_host, self.fedora_user, self.fedora_identity) + self.t_dce_ping(self.fedora_host, self.fedora_user, self.fedora_identity) + + def test_dce_ping_ubuntu(self): + self.t_dce_ping(self.ubuntu_host, self.ubuntu_user, self.ubuntu_identity) def test_dce_ping_local(self): self.t_dce_ping("localhost") @@ -349,6 +363,9 @@ class LinuxNS3DceApplicationTest(unittest.TestCase): def test_dce_ccn_fedora(self): self.t_dce_ccn(self.fedora_host, self.fedora_user, self.fedora_identity) + def test_dce_ccn_ubuntu(self): + self.t_dce_ccn(self.ubuntu_host, self.ubuntu_user, self.ubuntu_identity) + def test_dce_ccn_local(self): self.t_dce_ccn("localhost") diff --git a/test/resources/linux/ns3/ns3dceping.py b/test/resources/linux/ns3/ns3dceping.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ns3/ns3simulation.py b/test/resources/linux/ns3/ns3simulation.py old mode 100644 new mode 100755 diff --git a/test/resources/linux/ns3/serialization.py b/test/resources/linux/ns3/serialization.py new file mode 100755 index 00000000..fbf665f6 --- /dev/null +++ b/test/resources/linux/ns3/serialization.py @@ -0,0 +1,483 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController +from nepi.execution.trace import TraceAttr + +from test_utils import skipIfNotAlive + +import os +import shutil +import time +import tempfile +import unittest + +def add_ns3_node(ec, simu): + node = ec.register_resource("ns3::Node") + ec.register_connection(node, simu) + + ipv4 = ec.register_resource("ns3::Ipv4L3Protocol") + ec.register_connection(node, ipv4) + + arp = ec.register_resource("ns3::ArpL3Protocol") + ec.register_connection(node, arp) + + icmp = ec.register_resource("ns3::Icmpv4L4Protocol") + ec.register_connection(node, icmp) + + udp = ec.register_resource("ns3::UdpL4Protocol") + ec.register_connection(node, udp) + + return node + +def add_point2point_device(ec, node, ip, prefix): + dev = ec.register_resource("ns3::PointToPointNetDevice") + ec.set(dev, "ip", ip) + ec.set(dev, "prefix", prefix) + ec.register_connection(node, dev) + + queue = ec.register_resource("ns3::DropTailQueue") + ec.register_connection(dev, queue) + + return dev + +def add_csma_device(ec, node, ip, prefix): + dev = ec.register_resource("ns3::CsmaNetDevice") + ec.set(dev, "ip", ip) + ec.set(dev, "prefix", prefix) + ec.register_connection(node, dev) + + queue = ec.register_resource("ns3::DropTailQueue") + ec.register_connection(dev, queue) + + return dev + +def add_wifi_device(ec, node, ip, prefix, + access_point = False): + dev = ec.register_resource("ns3::WifiNetDevice") + ec.set(dev, "ip", ip) + ec.set(dev, "prefix", prefix) + ec.register_connection(node, dev) + + phy = ec.register_resource("ns3::YansWifiPhy") + ec.set(phy, "Standard", "WIFI_PHY_STANDARD_80211a") + ec.register_connection(dev, phy) + + error = ec.register_resource("ns3::NistErrorRateModel") + ec.register_connection(phy, error) + + manager = ec.register_resource("ns3::ArfWifiManager") + ec.register_connection(dev, manager) + + if access_point: + mac = ec.register_resource("ns3::ApWifiMac") + else: + mac = ec.register_resource("ns3::StaWifiMac") + + ec.set(mac, "Standard", "WIFI_PHY_STANDARD_80211a") + ec.register_connection(dev, mac) + + return dev, phy + +def add_random_mobility(ec, node, x, y, z, speed, bounds_width, + bounds_height): + position = "%d:%d:%d" % (x, y, z) + bounds = "0|%d|0|%d" % (bounds_width, bounds_height) + speed = "ns3::UniformRandomVariable[Min=%d|Max=%s]" % (speed, speed) + pause = "ns3::ConstantRandomVariable[Constant=1.0]" + + mobility = ec.register_resource("ns3::RandomDirection2dMobilityModel") + ec.set(mobility, "Position", position) + ec.set(mobility, "Bounds", bounds) + ec.set(mobility, "Speed", speed) + ec.set(mobility, "Pause", pause) + ec.register_connection(node, mobility) + return mobility + +def add_constant_mobility(ec, node, x, y, z): + mobility = ec.register_resource("ns3::ConstantPositionMobilityModel") + position = "%d:%d:%d" % (x, y, z) + ec.set(mobility, "Position", position) + ec.register_connection(node, mobility) + return mobility + +def add_wifi_channel(ec): + channel = ec.register_resource("ns3::YansWifiChannel") + delay = ec.register_resource("ns3::ConstantSpeedPropagationDelayModel") + ec.register_connection(channel, delay) + + loss = ec.register_resource("ns3::LogDistancePropagationLossModel") + ec.register_connection(channel, loss) + + return channel + +class LinuxNS3SimulationSerializationTest(unittest.TestCase): + def setUp(self): + #self.fedora_host = "nepi2.pl.sophia.inria.fr" + self.fedora_host = "planetlab1.informatik.uni-erlangen.de" + self.fedora_user = "inria_nepi" + self.fedora_identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME']) + + @skipIfNotAlive + def t_wifi_serialize(self, host, user = None, identity = None): + bounds_width = bounds_height = 200 + x = y = 100 + speed = 1 + + dirpath = tempfile.mkdtemp() + + ec = ExperimentController(exp_id = "test-ns3-wifi-ping") + + node = ec.register_resource("LinuxNode") + if host == "localhost": + ec.set(node, "hostname", "localhost") + else: + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "identity", identity) + + ec.set(node, "cleanProcesses", True) + #ec.set(node, "cleanHome", True) + + simu = ec.register_resource("LinuxNS3Simulation") + ec.set(simu, "verbose", True) + ec.register_connection(simu, node) + + nsnode1 = add_ns3_node(ec, simu) + dev1, phy1 = add_wifi_device(ec, nsnode1, "10.0.0.1", "24", access_point = True) + mobility1 = add_constant_mobility(ec, nsnode1, x, y, 0) + + nsnode2 = add_ns3_node(ec, simu) + dev2, phy2 = add_wifi_device(ec, nsnode2, "10.0.0.2", "24", access_point = False) + mobility1 = add_constant_mobility(ec, nsnode2, x, y, 0) + #mobility2 = add_random_mobility(ec, nsnode2, x, y, 0, speed, bounds_width, bounds_height) + + # Create channel + chan = add_wifi_channel(ec) + ec.register_connection(chan, phy1) + ec.register_connection(chan, phy2) + + ### create pinger + ping = ec.register_resource("ns3::V4Ping") + ec.set (ping, "Remote", "10.0.0.1") + ec.set (ping, "Interval", "1s") + ec.set (ping, "Verbose", True) + ec.set (ping, "StartTime", "1s") + ec.set (ping, "StopTime", "21s") + ec.register_connection(ping, nsnode2) + + filepath = ec.save(dirpath) + print filepath + + ec.deploy() + + ec.wait_finished([ping]) + + stdout = ec.trace(simu, "stdout") + + expected = "20 packets transmitted, 20 received, 0% packet loss" + self.assertTrue(stdout.find(expected) > -1) + + ec.shutdown() + + # Load serialized experiment + ec2 = ExperimentController.load(filepath) + + ec2.deploy() + + ec2.wait_finished([ping]) + + self.assertEquals(len(ec.resources), len(ec2.resources)) + + stdout = ec2.trace(simu, "stdout") + + expected = "20 packets transmitted, 20 received, 0% packet loss" + self.assertTrue(stdout.find(expected) > -1) + + ec2.shutdown() + + shutil.rmtree(dirpath) + + @skipIfNotAlive + def t_routing_serialize(self, host, user = None, identity = None): + """ + network topology: + n4 + | + n1 -- p2p -- n2 -- csma -- n5 -- p2p -- n6 + | | + ping n6 n3 + + + """ + dirpath = tempfile.mkdtemp() + + ec = ExperimentController(exp_id = "test-ns3-routes") + + node = ec.register_resource("LinuxNode") + if host == "localhost": + ec.set(node, "hostname", host) + else: + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "identity", identity) + + ec.set(node, "cleanProcesses", True) + #ec.set(node, "cleanHome", True) + + simu = ec.register_resource("LinuxNS3Simulation") + ec.set(simu, "verbose", True) + ec.register_connection(simu, node) + + nsnode1 = add_ns3_node(ec, simu) + p2p12 = add_point2point_device(ec, nsnode1, "10.0.0.1", "30") + + nsnode2 = add_ns3_node(ec, simu) + p2p21 = add_point2point_device(ec, nsnode2, "10.0.0.2", "30") + csma2 = add_csma_device(ec, nsnode2, "10.0.1.1", "24") + + nsnode3 = add_ns3_node(ec, simu) + csma3 = add_csma_device(ec, nsnode3, "10.0.1.2", "24") + + nsnode4 = add_ns3_node(ec, simu) + csma4 = add_csma_device(ec, nsnode4, "10.0.1.3", "24") + + nsnode5 = add_ns3_node(ec, simu) + p2p56 = add_point2point_device(ec, nsnode5, "10.0.2.1", "30") + csma5 = add_csma_device(ec, nsnode5, "10.0.1.4", "24") + + nsnode6 = add_ns3_node(ec, simu) + p2p65 = add_point2point_device(ec, nsnode6, "10.0.2.2", "30") + + # P2P chan1 + p2p_chan1 = ec.register_resource("ns3::PointToPointChannel") + ec.set(p2p_chan1, "Delay", "0s") + ec.register_connection(p2p_chan1, p2p12) + ec.register_connection(p2p_chan1, p2p21) + + # CSMA chan + csma_chan = ec.register_resource("ns3::CsmaChannel") + ec.set(csma_chan, "Delay", "0s") + ec.register_connection(csma_chan, csma2) + ec.register_connection(csma_chan, csma3) + ec.register_connection(csma_chan, csma4) + ec.register_connection(csma_chan, csma5) + + # P2P chan2 + p2p_chan2 = ec.register_resource("ns3::PointToPointChannel") + ec.set(p2p_chan2, "Delay", "0s") + ec.register_connection(p2p_chan2, p2p56) + ec.register_connection(p2p_chan2, p2p65) + + # Add routes - n1 - n6 + r1 = ec.register_resource("ns3::Route") + ec.set(r1, "network", "10.0.2.0") + ec.set(r1, "prefix", "30") + ec.set(r1, "nexthop", "10.0.0.2") + ec.register_connection(r1, nsnode1) + + # Add routes - n2 - n6 + r2 = ec.register_resource("ns3::Route") + ec.set(r2, "network", "10.0.2.0") + ec.set(r2, "prefix", "30") + ec.set(r2, "nexthop", "10.0.1.4") + ec.register_connection(r2, nsnode2) + + # Add routes - n5 - n1 + r5 = ec.register_resource("ns3::Route") + ec.set(r5, "network", "10.0.0.0") + ec.set(r5, "prefix", "30") + ec.set(r5, "nexthop", "10.0.1.1") + ec.register_connection(r5, nsnode5) + + # Add routes - n6 - n1 + r6 = ec.register_resource("ns3::Route") + ec.set(r6, "network", "10.0.0.0") + ec.set(r6, "prefix", "30") + ec.set(r6, "nexthop", "10.0.2.1") + ec.register_connection(r6, nsnode6) + + ### create pinger + ping = ec.register_resource("ns3::V4Ping") + ec.set (ping, "Remote", "10.0.2.2") + ec.set (ping, "Interval", "1s") + ec.set (ping, "Verbose", True) + ec.set (ping, "StartTime", "1s") + ec.set (ping, "StopTime", "21s") + ec.register_connection(ping, nsnode1) + + filepath = ec.save(dirpath) + print filepath + + ec.deploy() + + ec.wait_finished([ping]) + + stdout = ec.trace(simu, "stdout") + + expected = "20 packets transmitted, 20 received, 0% packet loss" + self.assertTrue(stdout.find(expected) > -1) + + ec.shutdown() + + # Load serialized experiment + ec2 = ExperimentController.load(filepath) + + ec2.deploy() + + ec2.wait_finished([ping]) + + self.assertEquals(len(ec.resources), len(ec2.resources)) + + stdout = ec2.trace(simu, "stdout") + + expected = "20 packets transmitted, 20 received, 0% packet loss" + self.assertTrue(stdout.find(expected) > -1) + + ec2.shutdown() + + shutil.rmtree(dirpath) + + @skipIfNotAlive + def t_dce_serialize(self, host, user = None, identity = None): + dirpath = tempfile.mkdtemp() + + ec = ExperimentController(exp_id = "test-ns3-dce") + + node = ec.register_resource("LinuxNode") + if host == "localhost": + ec.set(node, "hostname", host) + else: + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "identity", identity) + + ec.set(node, "cleanProcesses", True) + #ec.set(node, "cleanHome", True) + + simu = ec.register_resource("LinuxNS3Simulation") + ec.set(simu, "verbose", True) + ec.register_connection(simu, node) + + nsnode1 = add_ns3_node(ec, simu) + p2p1 = add_point2point_device(ec, nsnode1, "10.0.0.1", "30") + ec.set(p2p1, "DataRate", "5Mbps") + + nsnode2 = add_ns3_node(ec, simu) + p2p2 = add_point2point_device(ec, nsnode2, "10.0.0.2", "30") + ec.set(p2p2, "DataRate", "5Mbps") + + # Create channel + chan = ec.register_resource("ns3::PointToPointChannel") + ec.set(chan, "Delay", "2ms") + + ec.register_connection(chan, p2p1) + ec.register_connection(chan, p2p2) + + ### create applications + udp_perf = ec.register_resource("ns3::DceApplication") + ec.set (udp_perf, "binary", "udp-perf") + ec.set (udp_perf, "stackSize", 1<<20) + ec.set (udp_perf, "arguments", "--duration=10;--nodes=2") + ec.set (udp_perf, "StartTime", "1s") + ec.set (udp_perf, "StopTime", "20s") + ec.register_connection(udp_perf, nsnode1) + + udp_perf_client = ec.register_resource("ns3::DceApplication") + ec.set (udp_perf_client, "binary", "udp-perf") + ec.set (udp_perf_client, "stackSize", 1<<20) + ec.set (udp_perf_client, "arguments", "--client;--nodes=2;--host=10.0.0.1;--duration=10") + ec.set (udp_perf_client, "StartTime", "2s") + ec.set (udp_perf_client, "StopTime", "20s") + ec.register_connection(udp_perf_client, nsnode2) + + filepath = ec.save(dirpath) + + ec.deploy() + + ec.wait_finished([udp_perf_client]) + + # Give time to flush the streams + import time + time.sleep(5) + + expected = "udp-perf --duration=10 --nodes=2" + cmdline = ec.trace(udp_perf, "cmdline") + self.assertTrue(cmdline.find(expected) > -1, cmdline) + + expected = "Start Time: NS3 Time: 1s (" + status = ec.trace(udp_perf, "status") + self.assertTrue(status.find(expected) > -1, status) + + expected = "received=1500 bytes, 1 reads (@1500 bytes) 1500" + stdout = ec.trace(udp_perf, "stdout") + self.assertTrue(stdout.find(expected) > -1, stdout) + + ec.shutdown() + + # Load serialized experiment + ec2 = ExperimentController.load(filepath) + + ec2.deploy() + ec2.wait_finished([udp_perf_client]) + + # Give time to flush the streams + time.sleep(5) + + self.assertEquals(len(ec.resources), len(ec2.resources)) + + expected = "udp-perf --duration=10 --nodes=2" + cmdline = ec2.trace(udp_perf, "cmdline") + self.assertTrue(cmdline.find(expected) > -1, cmdline) + + expected = "Start Time: NS3 Time: 1s (" + status = ec2.trace(udp_perf, "status") + self.assertTrue(status.find(expected) > -1, status) + + expected = "received=1500 bytes, 1 reads (@1500 bytes) 1500" + stdout = ec2.trace(udp_perf, "stdout") + self.assertTrue(stdout.find(expected) > -1, stdout) + + ec2.shutdown() + + shutil.rmtree(dirpath) + + def test_wifi_serialize_fedora(self): + self.t_wifi_serialize(self.fedora_host, self.fedora_user, self.fedora_identity) + + def test_wifi_serialize_local(self): + self.t_wifi_serialize("localhost") + + def test_routing_serialize_fedora(self): + self.t_routing_serialize(self.fedora_host, self.fedora_user, self.fedora_identity) + + def test_routing_serialize_local(self): + self.t_routing_serialize("localhost") + + def test_dce_serialize_fedora(self): + self.t_dce_serialize(self.fedora_host, self.fedora_user, self.fedora_identity) + + def test_dce_serialize_local(self): + self.t_dce_serialize("localhost") + + +if __name__ == '__main__': + unittest.main() + diff --git a/test/resources/linux/serialization.py b/test/resources/linux/serialization.py new file mode 100755 index 00000000..c133ab36 --- /dev/null +++ b/test/resources/linux/serialization.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController +from nepi.execution.resource import ResourceState, ResourceAction +from nepi.execution.trace import TraceAttr + +from test_utils import skipIfNotAlive, skipInteractive + +import os +import shutil +import time +import tempfile +import unittest + +class LinuxSerializationTestCase(unittest.TestCase): + def setUp(self): + self.fedora_host = "nepi2.pl.sophia.inria.fr" + self.fedora_user = "inria_nepi" + + self.ubuntu_host = "roseval.pl.sophia.inria.fr" + self.ubuntu_user = "inria_nepi" + + self.target = "nepi5.pl.sophia.inria.fr" + + @skipIfNotAlive + def t_condition_serialize(self, host, user, depends): + + dirpath = tempfile.mkdtemp() + + ec = ExperimentController(exp_id="test-condition-serial") + + node = ec.register_resource("LinuxNode") + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "cleanHome", True) + ec.set(node, "cleanProcesses", True) + + server = ec.register_resource("LinuxApplication") + cmd = "echo 'HOLA' | nc -l 3333" + ec.set(server, "command", cmd) + ec.set(server, "depends", depends) + ec.register_connection(server, node) + + client = ec.register_resource("LinuxApplication") + cmd = "nc 127.0.0.1 3333" + ec.set(client, "command", cmd) + ec.register_connection(client, node) + + ec.register_condition(client, ResourceAction.START, server, ResourceState.STARTED) + + apps = [client, server] + + filepath = ec.save(dirpath) + + ec.deploy() + + ec.wait_finished(apps) + + self.assertTrue(ec.state(node) == ResourceState.STARTED) + self.assertTrue(ec.state(server) == ResourceState.STOPPED) + self.assertTrue(ec.state(client) == ResourceState.STOPPED) + + stdout = ec.trace(client, "stdout") + self.assertTrue(stdout.strip() == "HOLA") + + ec.shutdown() + + # Load serialized experiment + ec2 = ExperimentController.load(filepath) + + ec2.deploy() + ec2.wait_finished(apps) + + self.assertEquals(len(ec.resources), len(ec2.resources)) + + self.assertTrue(ec2.state(node) == ResourceState.STARTED) + self.assertTrue(ec2.state(server) == ResourceState.STOPPED) + self.assertTrue(ec2.state(client) == ResourceState.STOPPED) + + stdout = ec2.trace(client, "stdout") + + self.assertTrue(stdout.strip() == "HOLA") + + ec2.shutdown() + + shutil.rmtree(dirpath) + + def test_condition_serialize_fedora(self): + self.t_condition_serialize(self.fedora_host, self.fedora_user, "nc") + + def test_condition_serialize_ubuntu(self): + self.t_condition_serialize(self.ubuntu_host, self.ubuntu_user, "netcat") + +if __name__ == '__main__': + unittest.main() + diff --git a/test/resources/omf/omf6_vlc_wrong_critical.py b/test/resources/omf/omf6_vlc_wrong_critical.py old mode 100644 new mode 100755 diff --git a/test/resources/omf/omf6_vlc_wrong_non_critical.py b/test/resources/omf/omf6_vlc_wrong_non_critical.py old mode 100644 new mode 100755 diff --git a/test/resources/omf/set_hook.py b/test/resources/omf/set_hook.py old mode 100644 new mode 100755 diff --git a/test/util/parallel.py b/test/util/parallel.py old mode 100644 new mode 100755 diff --git a/test/util/parser.py b/test/util/parser.py deleted file mode 100755 index e9fce6b2..00000000 --- a/test/util/parser.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -# -# NEPI, a framework to manage network experiments -# Copyright (C) 2013 INRIA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Author: Alina Quereilhac - - -from nepi.design.box import Box -from nepi.util.parser import XMLParser - -import unittest - -class BoxDesignTestCase(unittest.TestCase): - def test_to_xml(self): - node1 = Box() - node2 = Box() - - node1.label = "node1" - node2.label = "node2" - - node1.connect(node2) - - node1.a.dog = "cat" - node1.a.one = "two" - node1.a.t = "q" - - node1.c.node2.a.sky = "sea" - node2.a.bee = "honey" - - node1.tadd("unooo") - node2.tadd("dosss") - - parser = XMLParser() - xml = parser.to_xml(node1) - - node = parser.from_xml(xml) - xml2 = parser.to_xml(node) - - self.assertEquals(xml, xml2) - -if __name__ == '__main__': - unittest.main() - diff --git a/test/util/plot.py b/test/util/plot.py deleted file mode 100755 index 38001de2..00000000 --- a/test/util/plot.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -# NEPI, a framework to manage network experiments -# Copyright (C) 2013 INRIA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Author: Alina Quereilhac - - -from nepi.design.box import Box -from nepi.util.plot import Plotter - -import subprocess -import unittest - -class BoxPlotTestCase(unittest.TestCase): - def xtest_plot(self): - """ XXX: This test is interactive, it will open an evince instance, - so it should not run automatically """ - node1 = Box(label="node1") - ping1 = Box(label="ping") - mobility1 = Box(label="mob1") - node2 = Box(label="node2") - mobility2 = Box(label="mob2") - iface1 = Box(label="iface1") - iface2 = Box(label="iface2") - channel = Box(label="chan") - - node1.connect(ping1) - node1.connect(mobility1) - node1.connect(iface1) - channel.connect(iface1) - channel.connect(iface2) - node2.connect(iface2) - node2.connect(mobility2) - - plotter = Plotter(node1) - fname = plotter.plot() - subprocess.call(["dot", "-Tps", fname, "-o", "%s.ps"%fname]) - subprocess.call(["evince","%s.ps"%fname]) - -if __name__ == '__main__': - unittest.main() - diff --git a/test/util/plotter.py b/test/util/plotter.py new file mode 100755 index 00000000..4e1a553e --- /dev/null +++ b/test/util/plotter.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController +from nepi.execution.resource import ResourceManager, ResourceState, \ + clsinit_copy, ResourceAction, ResourceFactory +from nepi.util.plotter import PFormats + +import os +import tempfile +import time +import unittest + +reschedule_delay = "0.5s" +deploy_time = 0 +run_time = 0 + +class Link(ResourceManager): + _rtype = "dummy::Link" + def do_deploy(self): + time.sleep(deploy_time) + super(Link, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Interface(ResourceManager): + _rtype = "dummy::Interface" + + def do_deploy(self): + node = self.get_connected(Node.get_rtype())[0] + link = self.get_connected(Link.get_rtype())[0] + + if node.state < ResourceState.READY or \ + link.state < ResourceState.READY: + self.ec.schedule(reschedule_delay, self.deploy) + self.logger.debug(" -------- RESCHEDULING ------- ") + else: + time.sleep(deploy_time) + super(Interface, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Node(ResourceManager): + _rtype = "dummy::Node" + + def do_deploy(self): + self.logger.debug(" -------- DO_DEPLOY ------- ") + time.sleep(deploy_time) + super(Node, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Application(ResourceManager): + _rtype = "dummy::Application" + + def do_deploy(self): + node = self.get_connected(Node.get_rtype())[0] + + if node.state < ResourceState.READY: + self.ec.schedule(reschedule_delay, self.deploy) + self.logger.debug(" -------- RESCHEDULING ------- ") + else: + time.sleep(deploy_time) + super(Application, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + + def do_start(self): + super(Application, self).do_start() + time.sleep(run_time) + self.ec.schedule("0s", self.stop) + +ResourceFactory.register_type(Application) +ResourceFactory.register_type(Node) +ResourceFactory.register_type(Interface) +ResourceFactory.register_type(Link) + +class PlotterTestCase(unittest.TestCase): + def test_serialize(self): + node_count = 4 + app_count = 2 + + ec = ExperimentController(exp_id = "plotter-test") + + # Add simulated nodes and applications + nodes = list() + apps = list() + ifaces = list() + + for i in xrange(node_count): + node = ec.register_resource("dummy::Node") + nodes.append(node) + + iface = ec.register_resource("dummy::Interface") + ec.register_connection(node, iface) + ifaces.append(iface) + + for i in xrange(app_count): + app = ec.register_resource("dummy::Application") + ec.register_connection(node, app) + apps.append(app) + + link = ec.register_resource("dummy::Link") + + for iface in ifaces: + ec.register_connection(link, iface) + + fpath = ec.plot(persist = True) + statinfo = os.stat(fpath) + size = statinfo.st_size + self.assertTrue(size > 0) + self.assertTrue(fpath.endswith(".png")) + + fpath = ec.plot(persist = True, format = PFormats.DOT) + statinfo = os.stat(fpath) + size = statinfo.st_size + self.assertTrue(size > 0) + self.assertTrue(fpath.endswith(".dot")) + + print fpath + + +if __name__ == '__main__': + unittest.main() + diff --git a/test/util/serializer.py b/test/util/serializer.py new file mode 100755 index 00000000..3896ea2e --- /dev/null +++ b/test/util/serializer.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController +from nepi.execution.resource import ResourceManager, ResourceState, \ + clsinit_copy, ResourceAction, ResourceFactory + +import os +import tempfile +import time +import shutil +import unittest + +reschedule_delay = "0.5s" +deploy_time = 0 +run_time = 0 + +class Link(ResourceManager): + _rtype = "dummy::Link" + def do_deploy(self): + time.sleep(deploy_time) + super(Link, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Interface(ResourceManager): + _rtype = "dummy::Interface" + + def do_deploy(self): + node = self.get_connected(Node.get_rtype())[0] + link = self.get_connected(Link.get_rtype())[0] + + if node.state < ResourceState.READY or \ + link.state < ResourceState.READY: + self.ec.schedule(reschedule_delay, self.deploy) + self.logger.debug(" -------- RESCHEDULING ------- ") + else: + time.sleep(deploy_time) + super(Interface, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Node(ResourceManager): + _rtype = "dummy::Node" + + def do_deploy(self): + self.logger.debug(" -------- DO_DEPLOY ------- ") + time.sleep(deploy_time) + super(Node, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + +class Application(ResourceManager): + _rtype = "dummy::Application" + + def do_deploy(self): + node = self.get_connected(Node.get_rtype())[0] + + if node.state < ResourceState.READY: + self.ec.schedule(reschedule_delay, self.deploy) + self.logger.debug(" -------- RESCHEDULING ------- ") + else: + time.sleep(deploy_time) + super(Application, self).do_deploy() + self.logger.debug(" -------- DEPLOYED ------- ") + + def do_start(self): + super(Application, self).do_start() + time.sleep(run_time) + self.ec.schedule("0s", self.stop) + +ResourceFactory.register_type(Application) +ResourceFactory.register_type(Node) +ResourceFactory.register_type(Interface) +ResourceFactory.register_type(Link) + +class SerializerTestCase(unittest.TestCase): + def test_serialize(self): + node_count = 4 + app_count = 2 + + dirpath = tempfile.mkdtemp() + + ec = ExperimentController(exp_id = "serialize-test") + + # Add simulated nodes and applications + nodes = list() + apps = list() + ifaces = list() + + for i in xrange(node_count): + node = ec.register_resource("dummy::Node") + nodes.append(node) + + iface = ec.register_resource("dummy::Interface") + ec.register_connection(node, iface) + ifaces.append(iface) + + for i in xrange(app_count): + app = ec.register_resource("dummy::Application") + ec.register_connection(node, app) + apps.append(app) + + link = ec.register_resource("dummy::Link") + + for iface in ifaces: + ec.register_connection(link, iface) + + filepath = ec.save(dirpath) + + ec.deploy() + + # Wait until nodes and apps are deployed + ec.wait_finished(apps) + + # Do the experiment controller shutdown + ec.shutdown() + + # Load serialized experiment + ec2 = ExperimentController.load(filepath) + apps = ec2.get_resources_by_type("dummy::Application") + ec2.deploy() + ec2.wait_finished(apps) + ec2.shutdown() + + self.assertEquals(len(ec.resources), len(ec2.resources)) + + shutil.rmtree(dirpath) + +if __name__ == '__main__': + unittest.main() +