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$ 
 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  #For using Log message level constants 
 78  from rosgraph_msgs.msg import Log 
 79   
 80  # interface class so that we don't have circular deps 
81 -class ChildROSLaunchProcess(Process):
82 """ 83 API for remote roslaunch processes 84 """
85 - def __init__(self, name, args, env):
86 super(ChildROSLaunchProcess, self).__init__('roslaunch', name, args, env, False) 87 self.uri = None
88
89 - def set_uri(self, uri):
90 self.uri = uri
91
92 -class ROSLaunchBaseHandler(xmlrpc.XmlRpcHandler):
93 """ 94 Common XML-RPC API for the roslaunch server and child node 95 """
96 - def __init__(self, pm):
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 #TODO: kill process, restart (with optional prefix). list active, list dead. CPU usage 103
104 - def list_processes(self):
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
115 - def process_info(self, process_name):
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
126 - def get_pid(self):
127 """ 128 @return: code, msg, pid 129 @rtype: int, str, int 130 """ 131 132 pid = os.getpid() 133 return 1, str(pid), pid
134
135 - def get_node_names(self):
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
144 - def _shutdown(self, reason):
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 # Uses camel-case network-API naming conventions
154 -class ROSLaunchParentHandler(ROSLaunchBaseHandler):
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
172 - def register(self, client, uri):
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
190 - def list_children(self):
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
197 - def process_died(self, process_name, exit_code):
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 #hack due to the fact that we only have one INFO level 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 # can't trust the logging system at this point, so just dump to screen 235 traceback.print_exc() 236 return 1, '', 1
237
238 -class ROSLaunchChildHandler(ROSLaunchBaseHandler):
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 # parse the URI to make sure it's valid 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
268 - def _shutdown(self, reason):
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
279 - def shutdown(self):
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 # won't actually do anything other than local, but still required 323 rosconfig.assign_machines() 324 325 try: 326 # roslaunch clients try to behave like normal roslaunches as much as possible. It's 327 # mainly the responsibility of the roslaunch server to not give us any XML that might 328 # cause conflict (e.g. master tags, param tags, etc...). 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 # enable the process monitor to exit of all processes die 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 #seconds 340
341 -class ROSLaunchNode(xmlrpc.XmlRpcNode):
342 """ 343 Base XML-RPC server for roslaunch parent/child processes 344 """ 345
346 - def __init__(self, handler):
347 """ 348 @param handler: xmlrpc api handler 349 @type handler: L{ROSLaunchBaseHandler} 350 """ 351 super(ROSLaunchNode, self).__init__(0, handler)
352
353 - def start(self):
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 # wait for node thread to initialize 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 # Make sure our xmlrpc server is actually up. We've seen very 371 # odd cases where remote nodes are unable to contact the 372 # server but have been unable to prove this is the cause. 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 # presumably this can occur if we call in a small time 382 # interval between the server socket port being 383 # assigned and the XMLRPC server initializing, but it 384 # is highly unlikely and unconfirmed 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
408 - def run(self):
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
420 -class ROSLaunchParentNode(ROSLaunchNode):
421 """ 422 XML-RPC server for parent roslaunch. 423 """ 424
425 - def __init__(self, rosconfig, pm):
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 = {} #{ child-name : ChildROSLaunchProcess}. 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
441 - def add_child(self, name, p):
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
451 - def add_process_listener(self, l):
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
460 -class _ProcessListenerForwarder(ProcessListener):
461 """ 462 Simple listener that forwards ProcessListener events to a roslaunch server 463 """
464 - def __init__(self, server):
465 self.server = server
466 - def process_died(self, process_name, exit_code):
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
472 -class ROSLaunchChildNode(ROSLaunchNode):
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
498 - def _register_with_server(self):
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 # fail 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 # register printlog handler so messages are funneled to remote 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 # register process listener to forward process death events to main server 525 self.pm.add_process_listener(_ProcessListenerForwarder(server)) 526
527 - def start(self):
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