applied the except and raise fixers to the master branch to close the gap with py3
[nepi.git] / src / nepi / resources / ns3 / ns3server.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 version 2 as
7 #    published by the Free Software Foundation;
8 #
9 #    This program is distributed in the hope that it will be useful,
10 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #    GNU General Public License for more details.
13 #
14 #    You should have received a copy of the GNU General Public License
15 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
18
19 import base64
20 import cPickle
21 import errno
22 import logging
23 import os
24 import socket
25 import sys
26
27 from optparse import OptionParser, SUPPRESS_HELP
28
29 from ns3wrapper import NS3Wrapper
30
31 class NS3WrapperMessage:
32     CREATE = "CREATE"
33     FACTORY = "FACTORY"
34     INVOKE = "INVOKE"
35     SET = "SET"
36     GET = "GET"
37     FLUSH = "FLUSH"
38     START = "START"
39     STOP = "STOP"
40     SHUTDOWN = "SHUTDOWN"
41
42 def handle_message(ns3_wrapper, msg_type, args, kwargs):
43     if msg_type == NS3WrapperMessage.SHUTDOWN:
44         ns3_wrapper.shutdown()
45
46         return "BYEBYE"
47     
48     if msg_type == NS3WrapperMessage.STOP:
49         time = kwargs.get("time")
50
51         ns3_wrapper.stop(time=time)
52
53         return "STOPPED"
54
55     if msg_type == NS3WrapperMessage.START:
56         ns3_wrapper.start()
57
58         return "STARTED"
59
60     if msg_type == NS3WrapperMessage.CREATE:
61         clazzname = args.pop(0)
62         
63         return ns3_wrapper.create(clazzname, *args)
64         
65     if msg_type == NS3WrapperMessage.FACTORY:
66         type_name = args.pop(0)
67
68         return ns3_wrapper.factory(type_name, **kwargs)
69        
70     if msg_type == NS3WrapperMessage.INVOKE:
71         uuid = args.pop(0)
72         operation = args.pop(0)
73    
74         return ns3_wrapper.invoke(uuid, operation, *args, **kwargs)
75
76     if msg_type == NS3WrapperMessage.GET:
77         uuid = args.pop(0)
78         name = args.pop(0)
79
80         return ns3_wrapper.get(uuid, name)
81         
82     if msg_type == NS3WrapperMessage.SET:
83         uuid = args.pop(0)
84         name = args.pop(0)
85         value = args.pop(0)
86
87         return ns3_wrapper.set(uuid, name, value)
88
89     if msg_type == NS3WrapperMessage.FLUSH:
90         # Forces flushing output and error streams.
91         # NS-3 output will stay unflushed until the program exits or 
92         # explicit invocation flush is done
93         sys.stdout.flush()
94         sys.stderr.flush()
95
96         ns3_wrapper.logger.debug("FLUSHED") 
97         
98         return "FLUSHED"
99
100 def open_socket(socket_name):
101     sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
102     sock.bind(socket_name)
103     return sock
104
105 def close_socket(sock):
106     try:
107         sock.close()
108     except:
109         pass
110
111 def recv_msg(conn):
112     msg = []
113     chunk = ''
114
115     while '\n' not in chunk:
116         try:
117             chunk = conn.recv(1024)
118         except (OSError, socket.error) as e:
119             if e[0] != errno.EINTR:
120                 raise
121             # Ignore eintr errors
122             continue
123
124         if chunk:
125             msg.append(chunk)
126         else:
127             # empty chunk = EOF
128             break
129  
130     msg = ''.join(msg).strip()
131
132     # The message is formatted as follows:
133     #   MESSAGE_TYPE|args|kwargs
134     #
135     #   where MESSAGE_TYPE, args and kwargs are pickld and enoded in base64
136
137     def decode(item):
138         item = base64.b64decode(item).rstrip()
139         return cPickle.loads(item)
140
141     decoded = map(decode, msg.split("|"))
142
143     # decoded message
144     dmsg_type = decoded.pop(0)
145     dargs = list(decoded.pop(0)) # transforming touple into list
146     dkwargs = decoded.pop(0)
147
148     return (dmsg_type, dargs, dkwargs)
149
150 def send_reply(conn, reply):
151     encoded = base64.b64encode(cPickle.dumps(reply))
152     conn.send("%s\n" % encoded)
153
154 def get_options():
155     usage = ("usage: %prog -S <socket-name> -L <ns-log>  -D <enable-dump> -v ")
156     
157     parser = OptionParser(usage = usage)
158
159     parser.add_option("-S", "--socket-name", dest="socket_name",
160         help = "Name for the unix socket used to interact with this process", 
161         default = "tap.sock", type="str")
162
163     parser.add_option("-L", "--ns-log", dest="ns_log",
164         help = "NS_LOG environmental variable to be set", 
165         default = "", type="str")
166
167     parser.add_option("-D", "--enable-dump", dest="enable_dump",
168         help = "Enable dumping the remote executed ns-3 commands to a script "
169             "in order to later reproduce and debug the experiment",
170         action = "store_true",
171         default = False)
172
173     parser.add_option("-v", "--verbose",
174         help="Print debug output",
175         action="store_true", 
176         dest="verbose", default=False)
177
178     (options, args) = parser.parse_args()
179     
180     return (options.socket_name, options.verbose, options.ns_log,
181             options.enable_dump)
182
183 def run_server(socket_name, level = logging.INFO, ns_log = None, 
184         enable_dump = False):
185
186     # Sets NS_LOG environmental variable for NS debugging
187     if ns_log:
188         os.environ["NS_LOG"] = ns_log
189
190     ###### ns-3 wrapper instantiation
191
192     ns3_wrapper = NS3Wrapper(loglevel=level, enable_dump = enable_dump)
193     
194     ns3_wrapper.logger.info("STARTING...")
195
196     # create unix socket to receive instructions
197     sock = open_socket(socket_name)
198     sock.listen(0)
199
200     # wait for messages to arrive and process them
201     stop = False
202
203     while not stop:
204         conn, addr = sock.accept()
205         conn.settimeout(30)
206
207         try:
208             (msg_type, args, kwargs) = recv_msg(conn)
209         except socket.timeout as e:
210             # Ingore time-out
211             close_socket(conn)
212             continue
213
214         if not msg_type:
215             # Ignore - connection lost
216             close_socket(conn)
217             continue
218
219         if msg_type == NS3WrapperMessage.SHUTDOWN:
220            stop = True
221   
222         try:
223             reply = handle_message(ns3_wrapper, msg_type, args, kwargs)  
224         except:
225             import traceback
226             err = traceback.format_exc()
227             ns3_wrapper.logger.error(err) 
228             close_socket(conn)
229             raise
230
231         try:
232             send_reply(conn, reply)
233         except socket.error:
234             import traceback
235             err = traceback.format_exc()
236             ns3_wrapper.logger.error(err) 
237             close_socket(conn)
238             raise
239         
240         close_socket(conn)
241
242     close_socket(sock)
243
244     ns3_wrapper.logger.info("EXITING...")
245
246 if __name__ == '__main__':
247             
248     (socket_name, verbose, ns_log, enable_dump) = get_options()
249
250     ## configure logging
251     FORMAT = "%(asctime)s %(name)s %(levelname)-4s %(message)s"
252     level = logging.DEBUG if verbose else logging.INFO
253
254     logging.basicConfig(format = FORMAT, level = level)
255
256     ## Run the server
257     run_server(socket_name, level, ns_log, enable_dump)
258