Package roslaunch :: Module server
[frames] | no frames]

Source Code for Module roslaunch.server

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id: server.py 13499 2011-03-15 18:19:56Z kwc $ 
 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  #For using Log message level constants 
 70  from rosgraph_msgs.msg import Log 
 71   
 72  # interface class so that we don't have circular deps 
73 -class ChildROSLaunchProcess(Process):
74 """ 75 API for remote roslaunch processes 76 """
77 - def __init__(self, name, args, env):
78 super(ChildROSLaunchProcess, self).__init__('roslaunch', name, args, env, False) 79 self.uri = None
80
81 - def set_uri(self, uri):
82 self.uri = uri
83
84 -class ROSLaunchBaseHandler(xmlrpc.XmlRpcHandler):
85 """ 86 Common XML-RPC API for the roslaunch server and child node 87 """
88 - def __init__(self, pm):
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 #TODO: kill process, restart (with optional prefix). list active, list dead. CPU usage 95
96 - def list_processes(self):
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
107 - def process_info(self, process_name):
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
118 - def get_pid(self):
119 """ 120 @return: code, msg, pid 121 @rtype: int, str, int 122 """ 123 124 pid = os.getpid() 125 return 1, str(pid), pid
126
127 - def get_node_names(self):
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
136 - def _shutdown(self, reason):
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 # Uses camel-case network-API naming conventions
146 -class ROSLaunchParentHandler(ROSLaunchBaseHandler):
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
164 - def register(self, client, uri):
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
182 - def list_children(self):
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
189 - def process_died(self, process_name, exit_code):
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 #hack due to the fact that we only have one INFO level 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 # can't trust the logging system at this point, so just dump to screen 227 traceback.print_exc() 228 return 1, '', 1
229
230 -class ROSLaunchChildHandler(ROSLaunchBaseHandler):
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 # parse the URI to make sure it's valid 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
260 - def _shutdown(self, reason):
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
271 - def shutdown(self):
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 # check environment settings in config 315 rosconfig.validate() 316 317 # won't actually do anything other than local, but still required 318 rosconfig.assign_machines() 319 320 try: 321 # roslaunch clients try to behave like normal roslaunches as much as possible. It's 322 # mainly the responsibility of the roslaunch server to not give us any XML that might 323 # cause conflict (e.g. master tags, param tags, etc...). 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 # enable the process monitor to exit of all processes die 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 #seconds 335
336 -class ROSLaunchNode(xmlrpc.XmlRpcNode):
337 """ 338 Base XML-RPC server for roslaunch parent/child processes 339 """ 340
341 - def __init__(self, handler):
342 """ 343 @param handler: xmlrpc api handler 344 @type handler: L{ROSLaunchBaseHandler} 345 """ 346 super(ROSLaunchNode, self).__init__(0, handler)
347
348 - def start(self):
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 # wait for node thread to initialize 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 # Make sure our xmlrpc server is actually up. We've seen very 366 # odd cases where remote nodes are unable to contact the 367 # server but have been unable to prove this is the cause. 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 # presumably this can occur if we call in a small time 377 # interval between the server socket port being 378 # assigned and the XMLRPC server initializing, but it 379 # is highly unlikely and unconfirmed 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
403 - def run(self):
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
415 -class ROSLaunchParentNode(ROSLaunchNode):
416 """ 417 XML-RPC server for parent roslaunch. 418 """ 419
420 - def __init__(self, rosconfig, pm):
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 = {} #{ child-name : ChildROSLaunchProcess}. 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
436 - def add_child(self, name, p):
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
446 - def add_process_listener(self, l):
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
455 -class _ProcessListenerForwarder(ProcessListener):
456 """ 457 Simple listener that forwards ProcessListener events to a roslaunch server 458 """
459 - def __init__(self, server):
460 self.server = server
461 - def process_died(self, process_name, exit_code):
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
467 -class ROSLaunchChildNode(ROSLaunchNode):
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
493 - def _register_with_server(self):
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 # fail 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 # register printlog handler so messages are funneled to remote 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 # register process listener to forward process death events to main server 520 self.pm.add_process_listener(_ProcessListenerForwarder(server)) 521
522 - def start(self):
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