ns-3 test passing
[nepi.git] / src / nepi / resources / linux / ns3 / ns3simulation.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2014 INRIA
4 #
5 #    This program is free software: you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License as published by
7 #    the Free Software Foundation, either version 3 of the License, or
8 #    (at your option) any later version.
9 #
10 #    This program is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #    GNU General Public License for more details.
14 #
15 #    You should have received a copy of the GNU General Public License
16 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.trace import Trace, TraceAttr
22 from nepi.execution.resource import ResourceManager, clsinit_copy, \
23         ResourceState, reschedule_delay
24 from nepi.resources.linux.application import LinuxApplication
25 from nepi.util.timefuncs import tnow, tdiffsec
26 from nepi.resources.ns3.ns3simulation import NS3Simulation
27 from nepi.resources.ns3.ns3wrapper import SIMULATOR_UUID, GLOBAL_VALUE_UUID
28 from nepi.resources.linux.ns3.ns3client import LinuxNS3Client
29
30 import os
31 import time
32
33 @clsinit_copy
34 class LinuxNS3Simulation(LinuxApplication, NS3Simulation):
35     _rtype = "LinuxNS3Simulation"
36
37     @classmethod
38     def _register_attributes(cls):
39         impl_type = Attribute("simulatorImplementationType",
40                 "The object class to use as the simulator implementation",
41             allowed = ["ns3::DefaultSimulatorImpl", "ns3::RealtimeSimulatorImpl"],
42             default = "ns3::DefaultSimulatorImpl",
43             type = Types.Enumerate,
44             flags = Flags.Design)
45
46         sched_type = Attribute("schedulerType",
47                 "The object class to use as the scheduler implementation",
48                 allowed = ["ns3::MapScheduler",
49                             "ns3::ListScheduler",
50                             "ns3::HeapScheduler",
51                             "ns3::MapScheduler",
52                             "ns3::CalendarScheduler"
53                     ],
54             default = "ns3::MapScheduler",
55             type = Types.Enumerate,
56             flags = Flags.Design)
57
58         check_sum = Attribute("checksumEnabled",
59                 "A global switch to enable all checksums for all protocols",
60             default = False,
61             type = Types.Bool,
62             flags = Flags.Design)
63
64         ns_log = Attribute("nsLog",
65             "NS_LOG environment variable. " \
66                     " Will only generate output if ns-3 is compiled in DEBUG mode. ",
67             flags = Flags.Design)
68
69         verbose = Attribute("verbose",
70             "True to output debugging info from the ns3 client-server communication",
71             type = Types.Bool,
72             flags = Flags.Design)
73
74         cls._register_attribute(impl_type)
75         cls._register_attribute(sched_type)
76         cls._register_attribute(check_sum)
77         cls._register_attribute(ns_log)
78         cls._register_attribute(verbose)
79
80     def __init__(self, ec, guid):
81         LinuxApplication.__init__(self, ec, guid)
82         NS3Simulation.__init__(self)
83
84         self._client = None
85         self._home = "ns3-simu-%s" % self.guid
86         self._socket_name = "ns3simu-%s" % os.urandom(8).encode('hex')
87
88     @property
89     def socket_name(self):
90         return self._socket_name
91
92     @property
93     def remote_socket(self):
94         return os.path.join(self.run_home, self.socket_name)
95
96     @property
97     def local_socket(self):
98         if self.node.get('hostname') in ['localhost', '127.0.0.01']:
99             return self.remote_socket
100
101         return os.path.join("/", "tmp", self.socket_name)
102
103     def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
104         self._client.flush() 
105         return LinuxApplication.trace(self, name, attr, block, offset)
106
107     def upload_sources(self):
108         self.node.mkdir(os.path.join(self.node.src_dir, "ns3wrapper"))
109
110         # upload ns3 wrapper python script
111         ns3_wrapper = os.path.join(os.path.dirname(__file__), "..", "..", "ns3", 
112                 "ns3wrapper.py")
113
114         self.node.upload(ns3_wrapper,
115                 os.path.join(self.node.src_dir, "ns3wrapper", "ns3wrapper.py"),
116                 overwrite = False)
117
118         # upload ns3_server python script
119         ns3_server = os.path.join(os.path.dirname(__file__), "..", "..", "ns3",
120                 "ns3server.py")
121
122         self.node.upload(ns3_server,
123                 os.path.join(self.node.src_dir, "ns3wrapper", "ns3server.py"),
124                 overwrite = False)
125
126         if self.node.use_rpm:
127             # upload pygccxml sources
128             pygccxml_tar = os.path.join(os.path.dirname(__file__), "dependencies",
129                     "%s.tar.gz" % self.pygccxml_version)
130
131             self.node.upload(pygccxml_tar,
132                     os.path.join(self.node.src_dir, "%s.tar.gz" % self.pygccxml_version),
133                     overwrite = False)
134
135     def upload_start_command(self):
136         command = self.get("command")
137         env = self.get("env")
138
139         # We want to make sure the ccnd is running
140         # before the experiment starts.
141         # Run the command as a bash script in background,
142         # in the host ( but wait until the command has
143         # finished to continue )
144         env = self.replace_paths(env)
145         command = self.replace_paths(command)
146
147         shfile = os.path.join(self.app_home, "start.sh")
148         self.node.upload_command(command, 
149                     shfile = shfile,
150                     env = env,
151                     overwrite = True)
152
153         # Run the ns3wrapper 
154         self._run_in_background()
155
156     def configure(self):
157         if self._attrs.get("simulatorImplementationType").has_changed():
158             simu_type = self.get("simulatorImplementationType")
159             stype = self.create("StringValue", simu_type)
160             self.invoke(GLOBAL_VALUE_UUID, "Bind", "SimulatorImplementationType", stype)
161
162         if self._attrs.get("checksumEnabled").has_changed():
163             check_sum = self.get("checksumEnabled")
164             btrue = self.create("BooleanValue", check_sum)    
165             self.invoke(GLOBAL_VALUE_UUID, "Bind", "ChecksumEnabled", btrue)
166         
167         if self._attrs.get("schedulerType").has_changed():
168             sched_type = self.get("schedulerType")
169             stype = self.create("StringValue", sched_type)
170             self.invoke(GLOBAL_VALUE_UUID, "Bind", "SchedulerType", btrue)
171         
172     def do_deploy(self):
173         if not self.node or self.node.state < ResourceState.READY:
174             self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state )
175             
176             # ccnd needs to wait until node is deployed and running
177             self.ec.schedule(reschedule_delay, self.deploy)
178         else:
179             if not self.get("command"):
180                 self.set("command", self._start_command)
181             
182             if not self.get("depends"):
183                 self.set("depends", self._dependencies)
184
185             if not self.get("build"):
186                 self.set("build", self._build)
187
188             if not self.get("install"):
189                 self.set("install", self._install)
190
191             if not self.get("env"):
192                 self.set("env", self._environment)
193
194             self.do_discover()
195             self.do_provision()
196
197             # Create client
198             self._client = LinuxNS3Client(self)
199            
200             # Wait until local socket is created
201             for i in [1, 5, 15, 30, 60]:
202                 if os.path.exists(self.local_socket):
203                     break
204                 time.sleep(i)
205
206             if not os.path.exists(self.local_socket):
207                 raise RuntimeError("Problem starting socat")
208
209             self.configure()
210             
211             self.set_ready()
212
213     def do_start(self):
214         """ Starts simulation execution
215
216         """
217         self.info("Starting")
218
219         if self.state == ResourceState.READY:
220             self._client.start() 
221
222             self.set_started()
223         else:
224             msg = " Failed to execute command '%s'" % command
225             self.error(msg, out, err)
226             raise RuntimeError, msg
227
228     def do_stop(self):
229         """ Stops simulation execution
230
231         """
232         if self.state == ResourceState.STARTED:
233             self._client.stop() 
234             self.set_stopped()
235
236     def do_release(self):
237         self.info("Releasing resource")
238
239         tear_down = self.get("tearDown")
240         if tear_down:
241             self.node.execute(tear_down)
242
243         self.do_stop()
244         self._client.shutdown()
245         LinuxApplication.do_stop(self)
246         
247         super(LinuxApplication, self).do_release()
248
249     @property
250     def _start_command(self):
251         command = [] 
252         command.append("PYTHONPATH=$PYTHONPATH:${SRC}/ns3wrapper/")
253
254         command.append("python ${SRC}/ns3wrapper/ns3server.py -S %s" % self.remote_socket )
255
256         ns_log = self.get("nsLog")
257         if ns_log:
258             command.append("-L %s" % ns_log)
259
260         if self.get("verbose"):
261             command.append("-v")
262
263         command = " ".join(command)
264         return command
265
266     @property
267     def _dependencies(self):
268         if self.node.use_rpm:
269             return ( " gcc gcc-c++ python python-devel mercurial bzr tcpdump socat gccxml")
270         elif self.node.use_deb:
271             return ( " gcc g++ python python-dev mercurial bzr tcpdump socat gccxml python-pygccxml")
272         return ""
273
274     @property
275     def ns3_repo(self):
276        return "http://code.nsnam.org"
277
278     @property
279     def ns3_version(self):
280        return "ns-3.19"
281
282     @property
283     def pybindgen_version(self):
284        return "834"
285
286     @property
287     def pygccxml_version(self):
288        return "pygccxml-1.0.0"
289
290     @property
291     def _build(self):
292         return (
293                 # Test if ns-3 is alredy installed
294                 " ( "
295                 " (( "
296                 "  ( test -d ${SRC}/%(ns3_version)s ) || (test -d ${NS3BINDINGS:='None'} && test -d ${NS3LIBRARIES:='None'}) ) && "
297                 "  echo 'binaries found, nothing to do' )"
298                 " ) "
299                 "  || " 
300                 # If not, install ns-3 and its dependencies
301                 " (   "
302                 # Install pygccxml
303                 "   (   "
304                 "     ( "
305                 "       python -c 'import pygccxml' && "
306                 "       echo 'pygccxml not found' "
307                 "     ) "
308                 "      || "
309                 "     ( "
310                 "       tar xf ${SRC}/%(pygccxml_version)s.tar.gz -C ${SRC} && "
311                 "       cd ${SRC}/%(pygccxml_version)s && "
312                 "       sudo -S python setup.py install "
313                 "     ) "
314                 "   ) " 
315                 # Install pybindgen
316                 "  && "
317                 "   (   "
318                 "     ( "
319                 "       test -d ${BIN}/pybindgen && "
320                 "       echo 'binaries found, nothing to do' "
321                 "     ) "
322                 "      || "
323                 # If not, clone and build
324                 "      ( cd ${SRC} && "
325                 "        bzr checkout lp:pybindgen -r %(pybindgen_version)s && "
326                 "        cd ${SRC}/pybindgen && "
327                 "        ./waf configure && "
328                 "        ./waf "
329                 "      ) "
330                 "   ) " 
331                "  && "
332                 # Clone and build ns-3
333                 "  ( "
334                 "    hg clone %(ns3_repo)s/%(ns3_version)s ${SRC}/%(ns3_version)s && "
335                 "    cd ${SRC}/%(ns3_version)s && "
336                 "    ./waf configure -d optimized  && "
337                 "    ./waf "
338                 "   ) "
339                 " ) "
340              ) % ({ 
341                     'ns3_repo':  self.ns3_repo,       
342                     'ns3_version': self.ns3_version,
343                     'pybindgen_version': self.pybindgen_version,
344                     'pygccxml_version': self.pygccxml_version
345                  })
346
347     @property
348     def _install(self):
349         return (
350                  # Test if ns-3 is alredy cloned
351                 " ( "
352                 "  ( ( (test -d ${BIN}/%(ns3_version)s/build ) || "
353                 "    (test -d ${NS3BINDINGS:='None'} && test -d ${NS3LIBRARIES:='None'}) ) && "
354                 "    echo 'binaries found, nothing to do' )"
355                 " ) "
356                 " ||" 
357                 " (   "
358                  # If not, copy ns-3 build to bin
359                 "  mkdir -p ${BIN}/%(ns3_version)s && "
360                 "  mv ${SRC}/%(ns3_version)s/build ${BIN}/%(ns3_version)s/build "
361                 " )"
362              ) % ({ 
363                     'ns3_version': self.ns3_version
364                  })
365
366     @property
367     def _environment(self):
368         env = []
369         env.append("NS3BINDINGS=${NS3BINDINGS:=${BIN}/%(ns3_version)s/build/bindings/python/}" % ({ 
370                     'ns3_version': self.ns3_version,
371                  }))
372         env.append("NS3LIBRARIES=${NS3LIBRARIES:=${BIN}/%(ns3_version)s/build/}" % ({ 
373                     'ns3_version': self.ns3_version,
374                  }))
375
376         return " ".join(env) 
377
378     def valid_connection(self, guid):
379         # TODO: Validate!
380         return True
381