/usr/lib/python2.7/dist-packages/roslaunch/launch.py is in python-roslaunch 1.11.16-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 | # Software License Agreement (BSD License)
#
# Copyright (c) 2008, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Revision $Id$
from __future__ import print_function
"""
Top-level implementation of launching processes. Coordinates
lower-level libraries.
"""
import os
import logging
import subprocess
import sys
import time
import traceback
import rosgraph
import rosgraph.names
import rosgraph.network
from roslaunch.core import *
#from roslaunch.core import setup_env
from roslaunch.nodeprocess import create_master_process, create_node_process
from roslaunch.pmon import start_process_monitor, ProcessListener
from roslaunch.rlutil import update_terminal_name
from rosmaster.master_api import NUM_WORKERS
_TIMEOUT_MASTER_START = 10.0 #seconds
_TIMEOUT_MASTER_STOP = 10.0 #seconds
_ID = '/roslaunch'
class RLTestTimeoutException(RLException): pass
def validate_master_launch(m, is_core, is_rostest=False):
"""
Validate the configuration of a master we are about to launch. Ths
validation already assumes that no existing master is running at
this configuration and merely checks configuration for a new
launch.
"""
# Before starting a master, we do some sanity check on the
# master configuration. There are two ways the user starts:
# roscore or roslaunch. If the user types roscore, we always
# will start a core if possible, but we warn about potential
# misconfigurations. If the user types roslaunch, we will
# auto-start a new master if it is achievable, i.e. if the
# master is local.
if not rosgraph.network.is_local_address(m.get_host()):
# The network configuration says that the intended
# hostname is not local, so...
if is_core:
# ...user is expecting to start a new master. Thus, we
# only warn if network configuration appears
# non-local.
try:
reverse_ips = [host[4][0] for host in socket.getaddrinfo(m.get_host(), 0, 0, 0, socket.SOL_TCP)]
local_addrs = rosgraph.network.get_local_addresses()
printerrlog("""WARNING: IP addresses %s for local hostname '%s' do not appear to match
any local IP address (%s). Your ROS nodes may fail to communicate.
Please use ROS_IP to set the correct IP address to use."""%(','.join(reverse_ips), m.get_host(), ','.join(local_addrs)))
except:
printerrlog("""WARNING: local hostname '%s' does not map to an IP address.
Your ROS nodes may fail to communicate.
Please use ROS_IP to set the correct IP address to use."""%(m.get_host()))
else:
# ... the remote master cannot be contacted, so we fail. #3097
raise RLException("ERROR: unable to contact ROS master at [%s]"%(m.uri))
if is_core and not is_rostest:
# User wants to start a master, and our configuration does
# point to the local host, and we're not running as rostest.
env_uri = rosgraph.get_master_uri()
env_host, env_port = rosgraph.network.parse_http_host_and_port(env_uri)
if not rosgraph.network.is_local_address(env_host):
# The ROS_MASTER_URI points to a different machine, warn user
printerrlog("WARNING: ROS_MASTER_URI [%s] host is not set to this machine"%(env_uri))
elif env_port != m.get_port():
# The ROS_MASTER_URI points to a master on a different port, warn user
printerrlog("WARNING: ROS_MASTER_URI port [%s] does not match this roscore [%s]"%(env_port, m.get_port()))
def _all_namespace_parents(p):
splits = [s for s in p.split(rosgraph.names.SEP) if s]
curr =rosgraph.names.SEP
parents = [curr]
for s in splits[:-1]:
next_ = curr + s + rosgraph.names.SEP
parents.append(next_)
curr = next_
return parents
def _unify_clear_params(params):
"""
Reduce clear_params such that only the highest-level namespaces
are represented for overlapping namespaces. e.g. if both /foo/ and
/foo/bar are present, return just /foo/.
@param params: paremter namees
@type params: [str]
@return: unified parameters
@rtype: [str]
"""
# note: this is a quick-and-dirty implementation
# canonicalize parameters
canon_params = []
for p in params:
if not p[-1] == rosgraph.names.SEP:
p += rosgraph.names.SEP
if not p in canon_params:
canon_params.append(p)
# reduce operation
clear_params = canon_params[:]
for p in canon_params:
for parent in _all_namespace_parents(p):
if parent in canon_params and p in clear_params and p != '/':
clear_params.remove(p)
return clear_params
def _hostname_to_rosname(hostname):
"""
Utility function to strip illegal characters from hostname. This
is implemented to be correct, not efficient."""
# prepend 'host_', which guarantees leading alpha character
fixed = 'host_'
# simplify comparison by making name lowercase
hostname = hostname.lower()
for c in hostname:
# only allow alphanumeric and numeral
if (c >= 'a' and c <= 'z') or \
(c >= '0' and c <= '9'):
fixed+=c
else:
fixed+='_'
return fixed
class _ROSLaunchListeners(ProcessListener):
"""
Helper class to manage distributing process events. It functions as
a higher level aggregator of events. In particular, events about
remote and local processes require different mechanisms for being
caught and reported.
"""
def __init__(self):
self.process_listeners = []
def add_process_listener(self, l):
"""
Add listener to list of listeners. Not threadsafe.
@param l: listener
@type l: L{ProcessListener}
"""
self.process_listeners.append(l)
def process_died(self, process_name, exit_code):
"""
ProcessListener callback
"""
for l in self.process_listeners:
try:
l.process_died(process_name, exit_code)
except Exception as e:
logging.getLogger('roslaunch').error(traceback.format_exc())
class ROSLaunchListener(object):
"""
Listener interface for events related to ROSLaunch.
ROSLaunchListener is currently identical to the lower-level
L{roslaunch.pmon.ProcessListener} interface, but is separate for
architectural reasons. The lower-level
L{roslaunch.pmon.ProcessMonitor} does not provide events about
remotely running processes.
"""
def process_died(self, process_name, exit_code):
"""
Notifies listener that process has died. This callback only
occurs for processes that die during normal process monitor
execution -- processes that are forcibly killed during
L{roslaunch.pmon.ProcessMonitor} shutdown are not reported.
@param process_name: name of process
@type process_name: str
@param exit_code int: exit code of process. If None, it means
that L{roslaunch.pmon.ProcessMonitor} was unable to determine an exit code.
@type exit_code: int
"""
pass
class ROSLaunchRunner(object):
"""
Runs a roslaunch. The normal sequence of API calls is L{launch()}
followed by L{spin()}. An external thread can call L{stop()}; otherwise
the runner will block until an exit signal. Another usage is to
call L{launch()} followed by repeated calls to L{spin_once()}. This usage
allows the main thread to continue to do work while processes are
monitored.
"""
def __init__(self, run_id, config, server_uri=None, pmon=None, is_core=False, remote_runner=None, is_child=False, is_rostest=False, num_workers=NUM_WORKERS, timeout=None):
"""
@param run_id: /run_id for this launch. If the core is not
running, this value will be used to initialize /run_id. If
the core is already running, this value will be checked
against the value stored on the core. L{ROSLaunchRunner} will
fail during L{launch()} if they do not match.
@type run_id: str
@param config: roslauch instance to run
@type config: L{ROSLaunchConfig}
@param server_uri: XML-RPC URI of roslaunch server.
@type server_uri: str
@param pmon: optionally override the process
monitor the runner uses for starting and tracking processes
@type pmon: L{ProcessMonitor}
@param is_core: if True, this runner is a roscore
instance. This affects the error behavior if a master is
already running -- aborts if is_core is True and a core is
detected.
@type is_core: bool
@param remote_runner: remote roslaunch process runner
@param is_rostest: if True, this runner is a rostest
instance. This affects certain validation checks.
@type is_rostest: bool
@param num_workers: If this is the core, the number of worker-threads to use.
@type num_workers: int
@param timeout: If this is the core, the socket-timeout to use.
@type timeout: Float or None
"""
if run_id is None:
raise RLException("run_id is None")
self.run_id = run_id
# In the future we should can separate the notion of a core
# config vs. the config being launched. In that way, we can
# start to migrate to a model where a config is a parameter to
# a launch() method, rather than a central thing to the
# runner.
self.config = config
self.server_uri = server_uri
self.is_child = is_child
self.is_core = is_core
self.is_rostest = is_rostest
self.num_workers = num_workers
self.timeout = timeout
self.logger = logging.getLogger('roslaunch')
self.pm = pmon or start_process_monitor()
# wire in ProcessMonitor events to our listeners
# aggregator. We similarly wire in the remote events when we
# create the remote runner.
self.listeners = _ROSLaunchListeners()
if self.pm is None:
raise RLException("unable to initialize roslaunch process monitor")
if self.pm.is_shutdown:
raise RLException("bad roslaunch process monitor initialization: process monitor is already dead")
self.pm.add_process_listener(self.listeners)
self.remote_runner = remote_runner
def add_process_listener(self, l):
"""
Add listener to list of listeners. Not threadsafe. Must be
called before processes started.
@param l: listener
@type l: L{ProcessListener}
"""
self.listeners.add_process_listener(l)
def _load_parameters(self):
"""
Load parameters onto the parameter server
"""
self.logger.info("load_parameters starting ...")
config = self.config
param_server = config.master.get()
p = None
try:
# multi-call style xmlrpc
param_server_multi = config.master.get_multi()
# clear specified parameter namespaces
# #2468 unify clear params to prevent error
for p in _unify_clear_params(config.clear_params):
if param_server.hasParam(_ID, p)[2]:
#printlog("deleting parameter [%s]"%p)
param_server_multi.deleteParam(_ID, p)
r = param_server_multi()
for code, msg, _ in r:
if code != 1:
raise RLException("Failed to clear parameter: %s"%(msg))
# multi-call objects are not reusable
param_server_multi = config.master.get_multi()
for p in config.params.values():
# suppressing this as it causes too much spam
#printlog("setting parameter [%s]"%p.key)
param_server_multi.setParam(_ID, p.key, p.value)
r = param_server_multi()
for code, msg, _ in r:
if code != 1:
raise RLException("Failed to set parameter: %s"%(msg))
except RLException:
raise
except Exception as e:
printerrlog("load_parameters: unable to set parameters (last param was [%s]): %s"%(p,e))
raise #re-raise as this is fatal
self.logger.info("... load_parameters complete")
def _launch_nodes(self):
"""
Launch all the declared nodes/master
@return: two lists of node names where the first
is the nodes that successfully launched and the second is the
nodes that failed to launch.
@rtype: [[str], [str]]
"""
config = self.config
succeeded = []
failed = []
self.logger.info("launch_nodes: launching local nodes ...")
local_nodes = config.nodes
# don't launch remote nodes
local_nodes = [n for n in config.nodes if is_machine_local(n.machine)]
for node in local_nodes:
proc, success = self.launch_node(node)
if success:
succeeded.append(str(proc))
else:
failed.append(str(proc))
if self.remote_runner:
self.logger.info("launch_nodes: launching remote nodes ...")
r_succ, r_fail = self.remote_runner.launch_remote_nodes()
succeeded.extend(r_succ)
failed.extend(r_fail)
self.logger.info("... launch_nodes complete")
return succeeded, failed
def _launch_master(self):
"""
Launches master if requested.
@raise RLException: if master launch fails
"""
m = self.config.master
is_running = m.is_running()
if self.is_core and is_running:
raise RLException("roscore cannot run as another roscore/master is already running. \nPlease kill other roscore/master processes before relaunching.\nThe ROS_MASTER_URI is %s"%(m.uri))
if not is_running:
validate_master_launch(m, self.is_core, self.is_rostest)
printlog("auto-starting new master")
p = create_master_process(self.run_id, m.type, get_ros_root(), m.get_port(), self.num_workers, self.timeout)
self.pm.register_core_proc(p)
success = p.start()
if not success:
raise RLException("ERROR: unable to auto-start master process")
timeout_t = time.time() + _TIMEOUT_MASTER_START
while not m.is_running() and time.time() < timeout_t:
time.sleep(0.1)
if not m.is_running():
raise RLException("ERROR: could not contact master [%s]"%m.uri)
printlog_bold("ROS_MASTER_URI=%s"%m.uri)
# TODO: although this dependency doesn't cause anything bad,
# it's still odd for launch to know about console stuff. This
# really should be an event.
update_terminal_name(m.uri)
# Param Server config params
param_server = m.get()
# #773: unique run ID
self._check_and_set_run_id(param_server, self.run_id)
if self.server_uri:
# store parent XML-RPC URI on param server
# - param name is the /roslaunch/hostname:port so that multiple roslaunches can store at once
hostname, port = rosgraph.network.parse_http_host_and_port(self.server_uri)
hostname = _hostname_to_rosname(hostname)
self.logger.info("setting /roslaunch/uris/%s__%s' to %s"%(hostname, port, self.server_uri))
param_server.setParam(_ID, '/roslaunch/uris/%s__%s'%(hostname, port),self.server_uri)
def _check_and_set_run_id(self, param_server, run_id):
"""
Initialize self.run_id to existing value or setup parameter
server with /run_id set to default_run_id
@param default_run_id: run_id to use if value is not set
@type default_run_id: str
@param param_server: parameter server proxy
@type param_server: xmlrpclib.ServerProxy
"""
code, _, val = param_server.hasParam(_ID, '/run_id')
if code == 1 and not val:
printlog_bold("setting /run_id to %s"%run_id)
param_server.setParam('/roslaunch', '/run_id', run_id)
else:
# verify that the run_id we have been set to matches what's on the parameter server
code, _, val = param_server.getParam('/roslaunch', '/run_id')
if code != 1:
#could only happen in a bad race condition with
#someone else restarting core
raise RLException("ERROR: unable to retrieve /run_id from parameter server")
if run_id != val:
raise RLException("run_id on parameter server does not match declared run_id: %s vs %s"%(val, run_id))
#self.run_id = val
#printlog_bold("/run_id is %s"%self.run_id)
def _launch_executable(self, e):
"""
Launch a single L{Executable} object. Blocks until executable finishes.
@param e: Executable
@type e: L{Executable}
@raise RLException: if exectuable fails. Failure includes non-zero exit code.
"""
try:
#kwc: I'm still debating whether shell=True is proper
cmd = e.command
cmd = "%s %s"%(cmd, ' '.join(e.args))
print("running %s" % cmd)
local_machine = self.config.machines['']
env = setup_env(None, local_machine, self.config.master.uri)
retcode = subprocess.call(cmd, shell=True, env=env)
if retcode < 0:
raise RLException("command [%s] failed with exit code %s"%(cmd, retcode))
except OSError as e:
raise RLException("command [%s] failed: %s"%(cmd, e))
#TODO: _launch_run_executables, _launch_teardown_executables
#TODO: define and implement behavior for remote launch
def _launch_setup_executables(self):
"""
@raise RLException: if exectuable fails. Failure includes non-zero exit code.
"""
exes = [e for e in self.config.executables if e.phase == PHASE_SETUP]
for e in exes:
self._launch_executable(e)
def _launch_core_nodes(self):
"""
launch any core services that are not already running. master must
be already running
@raise RLException: if core launches fail
"""
config = self.config
master = config.master.get()
tolaunch = []
for node in config.nodes_core:
node_name = rosgraph.names.ns_join(node.namespace, node.name)
code, msg, _ = master.lookupNode(_ID, node_name)
if code == -1:
tolaunch.append(node)
elif code == 1:
print("core service [%s] found" % node_name)
else:
print("WARN: master is not behaving well (unexpected return value when looking up node)", file=sys.stderr)
self.logger.error("ERROR: master return [%s][%s] on lookupNode call"%(code,msg))
for node in tolaunch:
node_name = rosgraph.names.ns_join(node.namespace, node.name)
name, success = self.launch_node(node, core=True)
if success:
print("started core service [%s]" % node_name)
else:
raise RLException("failed to start core service [%s]"%node_name)
def launch_node(self, node, core=False):
"""
Launch a single node locally. Remote launching is handled separately by the remote module.
If node name is not assigned, one will be created for it.
@param node Node: node to launch
@param core bool: if True, core node
@return obj, bool: Process handle, successful launch. If success, return actual Process instance. Otherwise return name.
"""
self.logger.info("... preparing to launch node of type [%s/%s]", node.package, node.type)
# TODO: should this always override, per spec?. I added this
# so that this api can be called w/o having to go through an
# extra assign machines step.
if node.machine is None:
node.machine = self.config.machines['']
if node.name is None:
node.name = rosgraph.names.anonymous_name(node.type)
master = self.config.master
import roslaunch.node_args
try:
process = create_node_process(self.run_id, node, master.uri)
except roslaunch.node_args.NodeParamsException as e:
self.logger.error(e)
if node.package == 'rosout' and node.type == 'rosout':
printerrlog("\n\n\nERROR: rosout is not built. Please run 'rosmake rosout'\n\n\n")
else:
printerrlog("ERROR: cannot launch node of type [%s/%s]: %s"%(node.package, node.type, str(e)))
if node.name:
return node.name, False
else:
return "%s/%s"%(node.package,node.type), False
self.logger.info("... created process [%s]", process.name)
if core:
self.pm.register_core_proc(process)
else:
self.pm.register(process)
node.process_name = process.name #store this in the node object for easy reference
self.logger.info("... registered process [%s]", process.name)
# note: this may raise FatalProcessLaunch, which aborts the entire launch
success = process.start()
if not success:
if node.machine.name:
printerrlog("launch of %s/%s on %s failed"%(node.package, node.type, node.machine.name))
else:
printerrlog("local launch of %s/%s failed"%(node.package, node.type))
else:
self.logger.info("... successfully launched [%s]", process.name)
return process, success
def is_node_running(self, node):
"""
Check for running node process.
@param node Node: node object to check
@return bool: True if process associated with node is running (launched && !dead)
"""
#process_name is not set until node is launched.
return node.process_name and self.pm.has_process(node.process_name)
def spin_once(self):
"""
Same as spin() but only does one cycle. must be run from the main thread.
"""
if not self.pm:
return False
return self.pm.mainthread_spin_once()
def spin(self):
"""
spin() must be run from the main thread. spin() is very
important for roslaunch as it picks up jobs that the process
monitor need to be run in the main thread.
"""
self.logger.info("spin")
# #556: if we're just setting parameters and aren't launching
# any processes, exit.
if not self.pm or not self.pm.get_active_names():
printlog_bold("No processes to monitor")
self.stop()
return # no processes
self.pm.mainthread_spin()
#self.pm.join()
self.logger.info("process monitor is done spinning, initiating full shutdown")
self.stop()
printlog_bold("done")
def stop(self):
"""
Stop the launch and all associated processes. not thread-safe.
"""
self.logger.info("runner.stop()")
if self.pm is not None:
printlog("shutting down processing monitor...")
self.logger.info("shutting down processing monitor %s"%self.pm)
self.pm.shutdown()
self.pm.join()
self.pm = None
printlog("... shutting down processing monitor complete")
else:
self.logger.info("... roslaunch runner has already been stopped")
def _setup(self):
"""
Setup the state of the ROS network, including the parameter
server state and core services
"""
# this may have already been done, but do just in case
self.config.assign_machines()
if self.remote_runner:
# hook in our listener aggregator
self.remote_runner.add_process_listener(self.listeners)
# start up the core: master + core nodes defined in core.xml
self._launch_master()
self._launch_core_nodes()
# run exectuables marked as setup period. this will block
# until these executables exit. setup executable have to run
# *before* parameters are uploaded so that commands like
# rosparam delete can execute.
self._launch_setup_executables()
# no parameters for a child process
if not self.is_child:
self._load_parameters()
def launch(self):
"""
Run the launch. Depending on usage, caller should call
spin_once or spin as appropriate after launch().
@return ([str], [str]): tuple containing list of nodes that
successfully launches and list of nodes that failed to launch
@rtype: ([str], [str])
@raise RLException: if launch fails (e.g. run_id parameter does
not match ID on parameter server)
"""
try:
self._setup()
succeeded, failed = self._launch_nodes()
return succeeded, failed
except KeyboardInterrupt:
self.stop()
raise
def run_test(self, test):
"""
Run the test node. Blocks until completion or timeout.
@param test: test node to run
@type test: Test
@raise RLTestTimeoutException: if test fails to launch or test times out
"""
self.logger.info("... preparing to run test [%s] of type [%s/%s]", test.test_name, test.package, test.type)
proc_h, success = self.launch_node(test)
if not success:
raise RLException("test [%s] failed to launch"%test.test_name)
#poll until test terminates or alloted time exceed
timeout_t = time.time() + test.time_limit
pm = self.pm
while pm.mainthread_spin_once() and self.is_node_running(test):
#test fails on timeout
if time.time() > timeout_t:
raise RLTestTimeoutException(
"max time [%ss] allotted for test [%s] of type [%s/%s]" %
(test.time_limit, test.test_name, test.package, test.type))
time.sleep(0.1)
# NOTE: the mainly exists to prevent implicit circular dependency as
# the runner needs to invoke methods on the remote API, which depends
# on launch.
class ROSRemoteRunnerIF(object):
"""
API for remote running
"""
def __init__(self):
pass
def setup(self):
pass
def add_process_listener(self, l):
"""
Listen to events about remote processes dying. Not
threadsafe. Must be called before processes started.
@param l: listener
@type l: L{ProcessListener}
"""
pass
def launch_remote_nodes(self):
"""
Contact each child to launch remote nodes
@return: succeeded, failed
@rtype: [str], [str]
"""
pass
|