1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 """
36 XML-RPC servers for parent and children
37
38 Following typical roslib-based XMLRPC code, code is divided into:
39
40 a) Handlers: these actually define and execute the XML-RPC API
41 b) Nodes: these run the XML-RPC server
42
43 In this code you'll find 'Parent' and 'Child' code. The parent node
44 is the original roslaunch process. The child nodes are the child
45 processes it launches in order to handle remote launching (or
46 execution as a different user).
47 """
48
49 import logging
50 import os
51 import socket
52 import sys
53 import time
54 import traceback
55 import urlparse
56 import xmlrpclib
57
58 import roslib.network as network
59 import roslib.xmlrpc as xmlrpc
60
61 import roslaunch.config
62 from roslaunch.pmon import ProcessListener, Process
63 import roslaunch.xmlloader
64
65 from roslaunch.launch import ROSLaunchRunner
66 from roslaunch.core import RLException, \
67 add_printlog_handler, add_printerrlog_handler, printlog, printerrlog, printlog_bold
68
69
70 from rosgraph_msgs.msg import Log
71
72
74 """
75 API for remote roslaunch processes
76 """
80
83
85 """
86 Common XML-RPC API for the roslaunch server and child node
87 """
89 self.pm = pm
90 self.logger = logging.getLogger('roslaunch.server')
91 if self.pm is None:
92 raise RLException("cannot create xmlrpc handler: pm is not initialized")
93
94
95
97 """
98 @return: code, msg, process list.
99 Process list is two lists, where first list of
100 active process names along with the number of times that
101 process has been spawned. Second list contains dead process
102 names and their spawn count.
103 @rtype: int, str, [[(str, int),], [(str,int),]]
104 """
105 return 1, "processes on parent machine", self.pm.get_process_names_with_spawn_count()
106
108 """
109 @return: dictionary of metadata about process. Keys vary by implementation
110 @rtype: int, str, dict
111 """
112 p = self.pm.get_process(process_name)
113 if p is None:
114 return -1, "no process by that name", {}
115 else:
116 return 1, "process info", p.get_info()
117
119 """
120 @return: code, msg, pid
121 @rtype: int, str, int
122 """
123
124 pid = os.getpid()
125 return 1, str(pid), pid
126
128 """
129 @return: code, msg, list of node names
130 @rtype: int, str, [str]
131 """
132 if self.pm is None:
133 return 0, "uninitialized", []
134 return 1, "node names", self.pm.get_active_names()
135
137 """
138 xmlrpc.XmlRpcHandler API: inform handler of shutdown
139 @param reason: human-readable shutdown reason
140 @type reason: str
141 """
142 return 1, '', 1
143
144
145
147 """
148 XML-RPC API for the roslaunch server node
149 """
150
151 - def __init__(self, pm, child_processes, listeners):
152 """
153 @param child_processes: Map of remote processes so that server can update processes
154 with information as children register. Handler will not modify
155 keys.
156 @type child_processes: {name : ChildROSLaunchProcess}.
157 @param listeners [ProcessListener]: list of
158 listeners to notify when process_died events occur.
159 """
160 super(ROSLaunchParentHandler, self).__init__(pm)
161 self.child_processes = child_processes
162 self.listeners = listeners
163
165 """
166 Registration callback from newly launched roslaunch clients
167 @param client: name of client
168 @type client: str
169 @param uri: XML-RPC URI of client
170 @type uri: str
171 @return: code, msg, ignore
172 @rtype: int, str, int
173 """
174 if client not in self.child_processes:
175 self.logger.error("Unknown child [%s] registered with server", client)
176 return -1, "unknown child [%s]"%client, 0
177 else:
178 self.logger.info("child [%s] registered with server, uri[%s]", client, uri)
179 self.child_processes[client].set_uri(uri)
180 return 1, "registered", 1
181
183 """
184 List the roslaunch child processes.
185 @return int, str, [str]: code, msg, list of the roslaunch children URIS
186 """
187 return 1, 'roslaunch children', [v.uri for v in self.child_processes.itervalues() if v.uri is not None]
188
190 """
191 Inform roslaunch server that a remote process has died
192 @param process_name: name of process that died
193 @type process_name: str
194 @param exit_code: exit code of remote process
195 @type exit_code: int
196 @return: code, msg, ignore
197 @rtype: int, str, int
198 """
199 for l in self.listeners:
200 try:
201 l.process_died(process_name, exit_code)
202 except:
203 self.logger.error(traceback.format_exc())
204 return 1, '', 0
205
206 - def log(self, client, level, message):
207 """
208 Report a log message to the server
209 @param client: name of client
210 @type client: str
211 @param level: log level (uses rosgraph_msgs.msg.Log levels)
212 @type level: int
213 @param message: message to log
214 @type message: str
215 """
216 try:
217 if level >= Log.ERROR:
218 printerrlog("[%s]: %s"%(client, message))
219 else:
220
221 if 'started with pid' in message:
222 printlog_bold("[%s]: %s"%(client, message))
223 else:
224 printlog("[%s]: %s"%(client, message))
225 except:
226
227 traceback.print_exc()
228 return 1, '', 1
229
231 """
232 XML-RPC API implementation for child roslaunches
233 NOTE: the client handler runs a process monitor so that
234 it can track processes across requests
235 """
236
237 - def __init__(self, run_id, name, server_uri, pm):
238 """
239 @param server_uri: XML-RPC URI of server
240 @type server_uri: str
241 @param pm: process monitor to use
242 @type pm: L{ProcessMonitor}
243 @raise RLException: If parameters are invalid
244 """
245 super(ROSLaunchChildHandler, self).__init__(pm)
246 if server_uri is None:
247 raise RLException("server_uri is not initialized")
248 self.run_id = run_id
249
250
251 _, urlport = network.parse_http_host_and_port(server_uri)
252 if urlport <= 0:
253 raise RLException("ERROR: roslaunch server URI is not a valid XML-RPC URI. Value is [%s]"%m.uri)
254
255 self.name = name
256 self.pm = pm
257 self.server_uri = server_uri
258 self.server = xmlrpclib.ServerProxy(server_uri)
259
261 """
262 xmlrpc.XmlRpcHandler API: inform handler of shutdown
263 @param reason: human-readable shutdown reason
264 @type reason: str
265 """
266 if self.pm is not None:
267 self.pm.shutdown()
268 self.pm.join()
269 self.pm = None
270
272 """
273 @return: code, msg, ignore
274 @rtype: int, str, int
275 """
276 self._shutdown("external call")
277 return 1, "success", 1
278
279 - def _log(self, level, message):
280 """
281 log message to log file and roslaunch server
282 @param level: log level
283 @type level: int
284 @param message: message to log
285 @type message: str
286 """
287 try:
288 if self.logger is not None:
289 self.logger.debug(message)
290 if self.server is not None:
291 self.server.log(str(self.name), level, str(message))
292 except:
293 self.logger.error(traceback.format_exc())
294
295 - def launch(self, launch_xml):
296 """
297 Launch the roslaunch XML file. Because this is a child
298 roslaunch, it will not set parameters nor manipulate the
299 master. Call blocks until launch is complete
300 @param xml: roslaunch XML file to launch
301 @type xml: str
302 @return: code, msg, [ [ successful launches], [failed launches] ]
303 @rtype: int, str, [ [str], [str] ]
304 """
305 if self.pm is None:
306 return 0, "uninitialized", -1
307
308 rosconfig = roslaunch.config.ROSLaunchConfig()
309 try:
310 roslaunch.xmlloader.XmlLoader().load_string(launch_xml, rosconfig)
311 except roslaunch.xmlloader.XmlParseException, e:
312 return -1, "ERROR: %s"%e, [[], []]
313
314
315 rosconfig.validate()
316
317
318 rosconfig.assign_machines()
319
320 try:
321
322
323
324 self._log(Log.INFO, "launching nodes...")
325 runner = ROSLaunchRunner(self.run_id, rosconfig, server_uri=self.server_uri, pmon=self.pm)
326 succeeded, failed = runner.launch()
327 self._log(Log.INFO, "... done launching nodes")
328
329 self.pm.registrations_complete()
330 return 1, "launched", [ succeeded, failed ]
331 except Exception, e:
332 return 0, "ERROR: %s"%traceback.format_exc(), [[], []]
333
334 _STARTUP_TIMEOUT = 5.0
335
337 """
338 Base XML-RPC server for roslaunch parent/child processes
339 """
340
342 """
343 @param handler: xmlrpc api handler
344 @type handler: L{ROSLaunchBaseHandler}
345 """
346 super(ROSLaunchNode, self).__init__(0, handler)
347
349 """
350 Startup roslaunch server XML-RPC services
351 @raise RLException: if server fails to start
352 """
353 logger = logging.getLogger('roslaunch.server')
354 logger.info("starting roslaunch XML-RPC server")
355 super(ROSLaunchNode, self).start()
356
357
358 timeout_t = time.time() + _STARTUP_TIMEOUT
359 logger.info("waiting for roslaunch XML-RPC server to initialize")
360 while not self.uri and time.time() < timeout_t:
361 time.sleep(0.01)
362 if not self.uri:
363 raise RLException("XML-RPC initialization failed")
364
365
366
367
368 server_up = False
369 while not server_up and time.time() < timeout_t:
370 try:
371 code, msg, val = xmlrpclib.ServerProxy(self.uri).get_pid()
372 if val != os.getpid():
373 raise RLException("Server at [%s] did not respond with correct PID. There appears to be something wrong with the networking configuration"%self.uri)
374 server_up = True
375 except IOError:
376
377
378
379
380 time.sleep(0.1)
381 except socket.error, (errno, msg):
382 if errno == 113:
383 p = urlparse.urlparse(self.uri)
384 raise RLException("Unable to contact the address [%s], which should be local.\nThis is generally caused by:\n * bad local network configuration\n * bad ROS_IP environment variable\n * bad ROS_HOSTNAME environment variable\nCan you ping %s?"%(self.uri, p.hostname))
385 else:
386 time.sleep(0.1)
387 if not server_up:
388 p = urlparse.urlparse(self.uri)
389 raise RLException("""Unable to contact my own server at [%s].
390 This usually means that the network is not configured properly.
391
392 A common cause is that the machine cannot ping itself. Please check
393 for errors by running:
394
395 \tping %s
396
397 For more tips, please see
398
399 \thttp://www.ros.org/wiki/ROS/NetworkSetup
400 """%(self.uri, p.hostname))
401 printlog_bold("started roslaunch server %s"%(self.uri))
402
404 """
405 run() should not be called by higher-level code. ROSLaunchNode
406 overrides underlying xmlrpc.XmlRpcNode implementation in order
407 to log errors.
408 """
409 try:
410 super(ROSLaunchNode, self).run()
411 except:
412 logging.getLogger("roslaunch.remote").error(traceback.format_exc())
413 print >> sys.stderr, "ERROR: failed to launch XML-RPC server for roslaunch"
414
416 """
417 XML-RPC server for parent roslaunch.
418 """
419
421 """
422 @param config: ROSConfig launch configuration
423 @type config: L{ROSConfig}
424 @param pm: process monitor
425 @type pm: L{ProcessMonitor}
426 """
427 self.rosconfig = rosconfig
428 self.listeners = []
429 self.child_processes = {}
430
431 if pm is None:
432 raise RLException("cannot create parent node: pm is not initialized")
433 handler = ROSLaunchParentHandler(pm, self.child_processes, self.listeners)
434 super(ROSLaunchParentNode, self).__init__(handler)
435
437 """
438 @param name: child roslaunch's name. NOTE: \a name is not
439 the same as the machine config key.
440 @type name: str
441 @param p: process handle of child
442 @type p: L{Process}
443 """
444 self.child_processes[name] = p
445
447 """
448 Listen to events about remote processes dying. Not
449 threadsafe. Must be called before processes started.
450 @param l: Process listener
451 @type l: L{ProcessListener}
452 """
453 self.listeners.append(l)
454
456 """
457 Simple listener that forwards ProcessListener events to a roslaunch server
458 """
462 try:
463 self.server.process_died(process_name, exit_code)
464 except Exception, e:
465 logging.getLogger("roslaunch.remote").error(traceback.format_exc())
466
468 """
469 XML-RPC server for roslaunch child processes
470 """
471
472 - def __init__(self, run_id, name, server_uri, pm):
473 """
474 ## Startup roslaunch remote client XML-RPC services. Blocks until shutdown
475 ## @param name: name of remote client
476 ## @type name: str
477 ## @param server_uri: XML-RPC URI of roslaunch server
478 ## @type server_uri: str
479 ## @return: XML-RPC URI
480 ## @rtype: str
481 """
482 self.logger = logging.getLogger("roslaunch.server")
483 self.run_id = run_id
484 self.name = name
485 self.server_uri = server_uri
486 self.pm = pm
487
488 if self.pm is None:
489 raise RLException("cannot create child node: pm is not initialized")
490 handler = ROSLaunchChildHandler(self.run_id, self.name, self.server_uri, self.pm)
491 super(ROSLaunchChildNode, self).__init__(handler)
492
494 """
495 Register child node with server
496 """
497 name = self.name
498 self.logger.info("attempting to register with roslaunch parent [%s]"%self.server_uri)
499 try:
500 server = xmlrpclib.ServerProxy(self.server_uri)
501 code, msg, _ = server.register(name, self.uri)
502 if code != 1:
503 raise RLException("unable to register with roslaunch server: %s"%msg)
504 except Exception, e:
505 self.logger.error("Exception while registering with roslaunch parent [%s]: %s"%(self.server_uri, traceback.format_exc(e)))
506
507 raise RLException("Exception while registering with roslaunch parent [%s]: %s"%(self.server_uri, traceback.format_exc(e)))
508
509 self.logger.debug("child registered with server")
510
511
512 def serverlog(msg):
513 server.log(name, Log.INFO, msg)
514 def servererrlog(msg):
515 server.log(name, Log.ERROR, msg)
516 add_printlog_handler(serverlog)
517 add_printerrlog_handler(servererrlog)
518
519
520 self.pm.add_process_listener(_ProcessListenerForwarder(server))
521
523 """
524 Initialize child. Must be called before run
525 """
526 self.logger.info("starting roslaunch child process [%s], server URI is [%s]", self.name, self.server_uri)
527 super(ROSLaunchChildNode, self).start()
528 self._register_with_server()
529