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