From 22d54534e6b8cf228522e618076f643d65e246d5 Mon Sep 17 00:00:00 2001 From: Alina Quereilhac <alina.quereilhac@inria.fr> Date: Thu, 30 Jan 2014 19:07:47 +0100 Subject: [PATCH] Scripts to automate creation of ns-3 RMs from metadata --- src/nepi/execution/attribute.py | 2 + src/nepi/resources/linux/ns3/simulator.py | 284 ++++++++++++++++++ src/nepi/resources/ns3/ns3_base.py | 72 +++++ src/nepi/resources/ns3/ns3wrapper.py | 129 ++++---- src/nepi/resources/ns3/rm_creator.py | 162 ++++++++++ .../ns3/templates/attribute_template.txt | 10 + .../resources/ns3/templates/rm_template.txt | 46 +++ .../ns3/templates/trace_template.txt | 4 + 8 files changed, 643 insertions(+), 66 deletions(-) create mode 100644 src/nepi/resources/linux/ns3/simulator.py create mode 100644 src/nepi/resources/ns3/ns3_base.py create mode 100644 src/nepi/resources/ns3/rm_creator.py create mode 100644 src/nepi/resources/ns3/templates/attribute_template.txt create mode 100644 src/nepi/resources/ns3/templates/rm_template.txt create mode 100644 src/nepi/resources/ns3/templates/trace_template.txt diff --git a/src/nepi/execution/attribute.py b/src/nepi/execution/attribute.py index e9f4c54a..a82274a0 100644 --- a/src/nepi/execution/attribute.py +++ b/src/nepi/execution/attribute.py @@ -37,8 +37,10 @@ class Flags: # Attribute is not modifiable by the user during runtime ExecReadOnly = 0x02 # Attribute is an access credential + # TODO REMOVE!!! Credential = 0x04 # Attribute is a filter used to discover resources + # TODO REMOVE!!! Filter = 0x08 class Attribute(object): diff --git a/src/nepi/resources/linux/ns3/simulator.py b/src/nepi/resources/linux/ns3/simulator.py new file mode 100644 index 00000000..82d4bca6 --- /dev/null +++ b/src/nepi/resources/linux/ns3/simulator.py @@ -0,0 +1,284 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>. +# +# Author: Alina Quereilhac <alina.quereilhac@inria.fr> + +from nepi.execution.attribute import Attribute, Flags, Types +from nepi.execution.trace import Trace, TraceAttr +from nepi.execution.resource import ResourceManager, clsinit_copy, \ + ResourceState, reschedule_delay +from nepi.resources.linux.application import LinuxApplication +from nepi.resources.linux.node import OSType +from nepi.util.timefuncs import tnow, tdiffsec +from nepi.resources.ns3.simulator import NS3Simulator + +import os + +@clsinit_copy +class LinuxNS3Simulator(LinuxApplication, NS3Simulator): + _rtype = "LinuxSimulator" + + @classmethod + def _register_attributes(cls): + max_rte = Attribute("maxRteMicrosec", + "Sets the CCND_MAX_RTE_MICROSEC environmental variable. ", + flags = Flags.ExecReadOnly) + + keystore = Attribute("keyStoreDirectory", + "Sets the CCND_KEYSTORE_DIRECTORY environmental variable. ", + flags = Flags.ExecReadOnly) + + cls._register_attribute(debug) + cls._register_attribute(port) + + @classmethod + def _register_traces(cls): + log = Trace("log", "CCND log output") + status = Trace("status", "ccndstatus output") + + cls._register_trace(log) + cls._register_trace(status) + + def __init__(self, ec, guid): + super(LinuxCCND, self).__init__(ec, guid) + self._home = "ccnd-%s" % self.guid + self._version = "ccnx" + + @property + def version(self): + return self._version + + @property + def path(self): + return "PATH=$PATH:${BIN}/%s/" % self.version + + def do_deploy(self): + if not self.node or self.node.state < ResourceState.READY: + self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state ) + + # ccnd needs to wait until node is deployed and running + self.ec.schedule(reschedule_delay, self.deploy) + else: + if not self.get("command"): + self.set("command", self._start_command) + + if not self.get("depends"): + self.set("depends", self._dependencies) + + if not self.get("sources"): + self.set("sources", self._sources) + + sources = self.get("sources") + source = sources.split(" ")[0] + basename = os.path.basename(source) + self._version = ( basename.strip().replace(".tar.gz", "") + .replace(".tar","") + .replace(".gz","") + .replace(".zip","") ) + + if not self.get("build"): + self.set("build", self._build) + + if not self.get("install"): + self.set("install", self._install) + + if not self.get("env"): + self.set("env", self._environment) + + command = self.get("command") + + self.info("Deploying command '%s' " % command) + + self.do_discover() + self.do_provision() + + self.debug("----- READY ---- ") + self.set_ready() + + def upload_start_command(self): + command = self.get("command") + env = self.get("env") + + # We want to make sure the ccnd is running + # before the experiment starts. + # Run the command as a bash script in background, + # in the host ( but wait until the command has + # finished to continue ) + env = self.replace_paths(env) + command = self.replace_paths(command) + + shfile = os.path.join(self.app_home, "start.sh") + self.node.run_and_wait(command, self.run_home, + shfile = shfile, + overwrite = False, + env = env, + raise_on_error = True) + + def do_start(self): + if self.state == ResourceState.READY: + command = self.get("command") + self.info("Starting command '%s'" % command) + + self.set_started() + else: + msg = " Failed to execute command '%s'" % command + self.error(msg, out, err) + raise RuntimeError, msg + + def do_stop(self): + command = self.get('command') or '' + + if self.state == ResourceState.STARTED: + self.info("Stopping command '%s'" % command) + + command = "ccndstop" + env = self.get("env") + + # replace application specific paths in the command + command = self.replace_paths(command) + env = env and self.replace_paths(env) + + # Upload the command to a file, and execute asynchronously + shfile = os.path.join(self.app_home, "stop.sh") + self.node.run_and_wait(command, self.run_home, + shfile = shfile, + overwrite = False, + env = env, + pidfile = "ccndstop_pidfile", + ecodefile = "ccndstop_exitcode", + stdout = "ccndstop_stdout", + stderr = "ccndstop_stderr") + + self.set_stopped() + + @property + def state(self): + # First check if the ccnd has failed + state_check_delay = 0.5 + if self._state == ResourceState.STARTED and \ + tdiffsec(tnow(), self._last_state_check) > state_check_delay: + (out, err), proc = self._ccndstatus() + + retcode = proc.poll() + + if retcode == 1 and err.find("No such file or directory") > -1: + # ccnd is not running (socket not found) + self.set_stopped() + elif retcode: + # other errors ... + msg = " Failed to execute command '%s'" % self.get("command") + self.error(msg, out, err) + self.fail() + + self._last_state_check = tnow() + + return self._state + + def _ccndstatus(self): + env = self.get('env') or "" + environ = self.node.format_environment(env, inline = True) + command = environ + " ccndstatus" + command = self.replace_paths(command) + + return self.node.execute(command) + + @property + def _start_command(self): + return "ccndstart" + + @property + def _dependencies(self): + if self.node.use_rpm: + return ( " autoconf openssl-devel expat-devel libpcap-devel " + " ecryptfs-utils-devel libxml2-devel automake gawk " + " gcc gcc-c++ git pcre-devel make ") + elif self.node.use_deb: + return ( " autoconf libssl-dev libexpat-dev libpcap-dev " + " libecryptfs0 libxml2-utils automake gawk gcc g++ " + " git-core pkg-config libpcre3-dev make ") + return "" + + @property + def _sources(self): + return "http://www.ccnx.org/releases/ccnx-0.7.2.tar.gz" + + @property + def _build(self): + sources = self.get("sources").split(" ")[0] + sources = os.path.basename(sources) + + return ( + # Evaluate if ccnx binaries are already installed + " ( " + " test -f ${BIN}/%(version)s/ccnd && " + " echo 'binaries found, nothing to do' " + " ) || ( " + # If not, untar and build + " ( " + " mkdir -p ${SRC}/%(version)s && " + " tar xf ${SRC}/%(sources)s --strip-components=1 -C ${SRC}/%(version)s " + " ) && " + "cd ${SRC}/%(version)s && " + # Just execute and silence warnings... + " ( ./configure && make ) " + " )") % ({ 'sources': sources, + 'version': self.version + }) + + @property + def _install(self): + return ( + # Evaluate if ccnx binaries are already installed + " ( " + " test -f ${BIN}/%(version)s/ccnd && " + " echo 'binaries found, nothing to do' " + " ) || ( " + # If not, install + " mkdir -p ${BIN}/%(version)s && " + " mv ${SRC}/%(version)s/bin/* ${BIN}/%(version)s/ " + " )" + ) % ({ 'version': self.version + }) + + @property + def _environment(self): + envs = dict({ + "debug": "CCND_DEBUG", + "port": "CCN_LOCAL_PORT", + "sockname" : "CCN_LOCAL_SOCKNAME", + "capacity" : "CCND_CAP", + "mtu" : "CCND_MTU", + "dataPauseMicrosec" : "CCND_DATA_PAUSE_MICROSEC", + "defaultTimeToStale" : "CCND_DEFAULT_TIME_TO_STALE", + "maxTimeToStale" : "CCND_MAX_TIME_TO_STALE", + "maxRteMicrosec" : "CCND_MAX_RTE_MICROSEC", + "keyStoreDirectory" : "CCND_KEYSTORE_DIRECTORY", + "listenOn" : "CCND_LISTEN_ON", + "autoreg" : "CCND_AUTOREG", + "prefix" : "CCND_PREFIX", + }) + + env = self.path + env += " ".join(map(lambda k: "%s=%s" % (envs.get(k), str(self.get(k))) \ + if self.get(k) else "", envs.keys())) + + return env + + def valid_connection(self, guid): + # TODO: Validate! + return True + diff --git a/src/nepi/resources/ns3/ns3_base.py b/src/nepi/resources/ns3/ns3_base.py new file mode 100644 index 00000000..c829a4f5 --- /dev/null +++ b/src/nepi/resources/ns3/ns3_base.py @@ -0,0 +1,72 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>. +# +# Author: Alina Quereilhac <alina.quereilhac@inria.fr> + +from nepi.execution.resource import ResourceManager, clsinit_copy, \ + ResourceState, reschedule_delay + +from nepi.resources.ns3.simulator import NS3Simulator + +@clsinit_copy +class NS3Base(ResourceManager): + _rtype = "NS3Base" + + @property + def simulator(self): + simulator = self.get_connected(NS3Simulator.get_rtype()) + if simulator: return simulator[0] + return None + + def do_deploy(self): + if not self.simulator or self.simulator.state < ResourceState.READY: + self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.simulator.state ) + + # ccnd needs to wait until node is deployed and running + self.ec.schedule(reschedule_delay, self.deploy) + else: + # TODO: CREATE AND CONFIGURE NS-3 C++ OBJECT + self.do_discover() + self.do_provision() + + self.debug("----- READY ---- ") + self.set_ready() + + def do_start(self): + if self.state == ResourceState.READY: + ## TODO!!! + self.info("Starting ...") + + self.set_started() + else: + msg = " Failed " + self.error(msg, out, err) + raise RuntimeError, msg + + def do_stop(self): + if self.state == ResourceState.STARTED: + self.info("Stopping command '%s'" % command) + ## TODO!!! + + self.set_stopped() + + @property + def state(self): + # First check if the ccnd has failed + # TODO!!! + return self._state + diff --git a/src/nepi/resources/ns3/ns3wrapper.py b/src/nepi/resources/ns3/ns3wrapper.py index b8fa5bc2..f6c063bd 100644 --- a/src/nepi/resources/ns3/ns3wrapper.py +++ b/src/nepi/resources/ns3/ns3wrapper.py @@ -30,6 +30,66 @@ import uuid SINGLETON = "singleton::" +def load_ns3_module(): + import ctypes + import imp + import re + import pkgutil + + bindings = os.environ.get("NS3BINDINGS") + libdir = os.environ.get("NS3LIBRARIES") + + # Load the ns-3 modules shared libraries + if libdir: + files = os.listdir(libdir) + regex = re.compile("(.*\.so)$") + libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m] + + libscp = list(libs) + while len(libs) > 0: + for lib in libs: + libfile = os.path.join(libdir, lib) + try: + ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL) + libs.remove(lib) + except: + pass + + # if did not load any libraries in the last iteration break + # to prevent infinit loop + if len(libscp) == len(libs): + raise RuntimeError("Imposible to load shared libraries %s" % str(libs)) + libscp = list(libs) + + # import the python bindings for the ns-3 modules + if bindings: + sys.path.append(bindings) + + # create a module to add all ns3 classes + ns3mod = imp.new_module("ns3") + sys.modules["ns3"] = ns3mod + + # retrieve all ns3 classes and add them to the ns3 module + import ns + + for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__): + fullmodname = "ns.%s" % modname + module = __import__(fullmodname, globals(), locals(), ['*']) + + for sattr in dir(module): + if sattr.startswith("_"): + continue + + attr = getattr(module, sattr) + + # netanim.Config and lte.Config singleton overrides ns3::Config + if sattr == "Config" and modname in ['netanim', 'lte']: + sattr = "%s.%s" % (modname, sattr) + + setattr(ns3mod, sattr, attr) + + return ns3mod + class NS3Wrapper(object): def __init__(self, homedir = None): super(NS3Wrapper, self).__init__() @@ -68,12 +128,12 @@ class NS3Wrapper(object): # Python module to refernce all ns-3 classes and types self._ns3 = None - - # Load ns-3 shared libraries and import modules - self._load_ns3_module() @property def ns3(self): + if not self._ns3: + self._ns3 = load_ns3_module() + return self._ns3 @property @@ -337,67 +397,4 @@ class NS3Wrapper(object): str(arg).startswith(SINGLETON) else arg for arg in realargs] return realargs - - def _load_ns3_module(self): - if self.ns3: - return - - import ctypes - import imp - import re - import pkgutil - - bindings = os.environ.get("NS3BINDINGS") - libdir = os.environ.get("NS3LIBRARIES") - - # Load the ns-3 modules shared libraries - if libdir: - files = os.listdir(libdir) - regex = re.compile("(.*\.so)$") - libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m] - - libscp = list(libs) - while len(libs) > 0: - for lib in libs: - libfile = os.path.join(libdir, lib) - try: - ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL) - libs.remove(lib) - except: - pass - - # if did not load any libraries in the last iteration break - # to prevent infinit loop - if len(libscp) == len(libs): - raise RuntimeError("Imposible to load shared libraries %s" % str(libs)) - libscp = list(libs) - - # import the python bindings for the ns-3 modules - if bindings: - sys.path.append(bindings) - - # create a module to add all ns3 classes - ns3mod = imp.new_module("ns3") - sys.modules["ns3"] = ns3mod - - # retrieve all ns3 classes and add them to the ns3 module - import ns - - for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__): - fullmodname = "ns.%s" % modname - module = __import__(fullmodname, globals(), locals(), ['*']) - - for sattr in dir(module): - if sattr.startswith("_"): - continue - - attr = getattr(module, sattr) - - # netanim.Config and lte.Config singleton overrides ns3::Config - if sattr == "Config" and modname in ['netanim', 'lte']: - sattr = "%s.%s" % (modname, sattr) - - setattr(ns3mod, sattr, attr) - - self._ns3 = ns3mod diff --git a/src/nepi/resources/ns3/rm_creator.py b/src/nepi/resources/ns3/rm_creator.py new file mode 100644 index 00000000..3b3cb8d0 --- /dev/null +++ b/src/nepi/resources/ns3/rm_creator.py @@ -0,0 +1,162 @@ +# +# 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 <http://www.gnu.org/licenses/>. +# +# Author: Alina Quereilhac <alina.quereilhac@inria.fr> + +from nepi.resources.ns3.ns3wrapper import load_ns3_module + +import os +import re + +def dump_ns3_rms(): + ns3 = load_ns3_module() + + type_id = ns3.TypeId() + + tid_count = type_id.GetRegisteredN() + base = type_id.LookupByName("ns3::Object") + + # Create a .py file using the ns-3 RM template for each ns-3 TypeId + for i in xrange(tid_count): + tid = type_id.GetRegistered(i) + + if tid.MustHideFromDocumentation() or \ + not tid.HasConstructor() or \ + not tid.IsChildOf(base): + continue + + attributes = template_attributes(ns3, tid) + traces = template_traces(ns3, tid) + ptid = tid + while ptid.HasParent(): + ptid = ptid.GetParent() + attributes += template_attributes(ns3, ptid) + traces += template_traces(ns3, ptid) + + attributes = "\n" + attributes if attributes else "pass" + traces = "\n" + traces if traces else "pass" + + rtype = tid.GetName() + category = tid.GetGroupName() + classname = rtype.replace("ns3::", "NS3").replace("::","") + uncamm_rtype = re.sub('([a-z])([A-Z])', r'\1-\2', rtype).lower() + short_rtype = uncamm_rtype.replace("::","-") + + d = os.path.dirname(os.path.realpath(__file__)) + ftemp = open(os.path.join(d, "templates", "rm_template.txt"), "r") + template = ftemp.read() + ftemp.close() + + template = template. \ + replace("<CLASS_NAME>", classname). \ + replace("<RTYPE>", rtype). \ + replace("<ATTRIBUTES>", attributes). \ + replace("<TRACES>", traces). \ + replace("<SHORT-RTYPE>", short_rtype) + + fname = uncamm_rtype.replace('ns3::', ''). \ + replace('::', ''). \ + replace("-","_").lower() + ".py" + + f = open(os.path.join(d, fname), "w") + print os.path.join(d, fname) + print template + #f.write(template) + f.close() + +def template_attributes(ns3, tid): + d = os.path.dirname(os.path.realpath(__file__)) + ftemp = open(os.path.join(d, "templates", "attribute_template.txt"), "r") + template = ftemp.read() + ftemp.close() + + attributes = "" + + attr_count = tid.GetAttributeN() + for i in xrange(attr_count): + attr_info = tid.GetAttribute(i) + if not attr_info.accessor.HasGetter(): + continue + + attr_flags = "None" + flags = attr_info.flags + if (flags & ns3.TypeId.ATTR_SET) != ns3.TypeId.ATTR_SET: + attr_flags = "Types.ExecReadOnly" + + attr_name = attr_info.name + checker = attr_info.checker + attr_help = attr_info.help + value = attr_info.initialValue + attr_value = value.SerializeToString(checker) + attr_allowed = "None" + attr_range = "None" + attr_type = "Types.STRING" + + if isinstance(value, ns3.ObjectVectorValue): + continue + elif isinstance(value, ns3.PointerValue): + continue + elif isinstance(value, ns3.WaypointValue): + continue + elif isinstance(value, ns3.BooleanValue): + attr_type = "Types.BOOL" + attr_value = "True" if attr_value == "true" else "False" + elif isinstance(value, ns3.EnumValue): + attr_type = "Types.ENUM" + attr_allowed = "[%s]"% checker.GetUnderlyingTypeInformation().replace("|", ",") + elif isinstance(value, ns3.DoubleValue): + attr_type = "Types.DOUBLE" + # TODO: range + elif isinstance(value, ns3.UintegerValue): + attr_type = "Types.INTEGER" + # TODO: range + + attr_id = attr_name.lower() + attributes += template.replace("<ATTR_ID>", attr_id) \ + .replace("<ATTR_NAME>", attr_name) \ + .replace("<ATTR_HELP>", attr_help) \ + .replace("<ATTR_TYPE>", attr_type) \ + .replace("<ATTR_DEFAULT>", attr_value) \ + .replace("<ATTR_ALLOWED>", attr_allowed) \ + .replace("<ATTR_RANGE>", attr_range) \ + .replace("<ATTR_FLAGS>", attr_flags) + + return attributes + +def template_traces(ns3, tid): + d = os.path.dirname(os.path.realpath(__file__)) + ftemp = open(os.path.join(d, "templates", "trace_template.txt"), "r") + template = ftemp.read() + ftemp.close() + + traces = "" + + trace_count = tid.GetTraceSourceN() + for i in xrange(trace_count): + trace_info = tid.GetTraceSource(i) + trace_name = trace_info.name + trace_help = trace_info.help + + trace_id = trace_name.lower() + traces += template.replace("<TRACE_ID>", trace_id) \ + .replace("<TRACE_NAME>", trace_name) \ + .replace("<TRACE_HELP>", trace_help) + + return traces + +if __name__ == "__main__": + dump_ns3_rms() diff --git a/src/nepi/resources/ns3/templates/attribute_template.txt b/src/nepi/resources/ns3/templates/attribute_template.txt new file mode 100644 index 00000000..f34daa30 --- /dev/null +++ b/src/nepi/resources/ns3/templates/attribute_template.txt @@ -0,0 +1,10 @@ + <ATTR_ID> = Attribute("<ATTR_NAME>", + "<ATTR_HELP>", + type = <ATTR_TYPE>, + default = <ATTR_DEFAULT>, + allowed = <ATTR_ALLOWED>, + range = <ATTR_RANGE>, + flags = <ATTR_FLAGS>) + + cls._register_attribute(<ATTR_ID>) + diff --git a/src/nepi/resources/ns3/templates/rm_template.txt b/src/nepi/resources/ns3/templates/rm_template.txt new file mode 100644 index 00000000..08447c8f --- /dev/null +++ b/src/nepi/resources/ns3/templates/rm_template.txt @@ -0,0 +1,46 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>. +# + +from nepi.execution.attribute import Attribute, Flags, Types +from nepi.execution.trace import Trace, TraceAttr +from nepi.execution.resource import ResourceManager, clsinit_copy, \ + ResourceState, reschedule_delay +from nepi.resources.ns3.ns3_base import NS3Base + +@clsinit_copy +class <CLASS_NAME>(NS3Base): + _rtype = "<RTYPE>" + + @classmethod + def _register_attributes(cls): + <ATTRIBUTES> + + @classmethod + def _register_traces(cls): + <TRACES> + + def __init__(self, ec, guid): + super(<CLASS_NAME>, self).__init__(ec, guid) + self._home = "<SHORT-RTYPE>-%s" % self.guid + # TODO! + self._version = None + + @property + def version(self): + return self._version + diff --git a/src/nepi/resources/ns3/templates/trace_template.txt b/src/nepi/resources/ns3/templates/trace_template.txt new file mode 100644 index 00000000..9cedb1ed --- /dev/null +++ b/src/nepi/resources/ns3/templates/trace_template.txt @@ -0,0 +1,4 @@ + <TRACE_ID> = Trace("<TRACE_NAME>", "<TRACE_HELP>") + + cls._register_trace(<TRACE_ID>) + -- 2.47.0