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 Top-level implementation of launching processes. Coordinates
37 lower-level libraries.
38 """
39
40 import os
41 import logging
42 import subprocess
43 import sys
44 import time
45 import traceback
46
47 import rosgraph
48 import rosgraph.names
49 import rosgraph.network
50
51 from roslaunch.core import *
52
53 from roslaunch.nodeprocess import create_master_process, create_node_process
54 from roslaunch.pmon import start_process_monitor, ProcessListener
55
56 from roslaunch.rlutil import update_terminal_name
57
58 _TIMEOUT_MASTER_START = 10.0
59 _TIMEOUT_MASTER_STOP = 10.0
60
61 _ID = '/roslaunch'
62
64
66 """
67 Validate the configuration of a master we are about to launch. Ths
68 validation already assumes that no existing master is running at
69 this configuration and merely checks configuration for a new
70 launch.
71 """
72
73
74
75
76
77
78
79 if not rosgraph.network.is_local_address(m.get_host()):
80
81
82 if is_core:
83
84
85
86 try:
87 reverse_ips = [host[4][0] for host in socket.getaddrinfo(m.get_host(), 0, 0, 0, socket.SOL_TCP)]
88 local_addrs = rosgraph.network.get_local_addresses()
89 printerrlog("""WARNING: IP addresses %s for local hostname '%s' do not appear to match
90 any local IP address (%s). Your ROS nodes may fail to communicate.
91
92 Please use ROS_IP to set the correct IP address to use."""%(','.join(reverse_ips), m.get_host(), ','.join(local_addrs)))
93 except:
94 printerrlog("""WARNING: local hostname '%s' does not map to an IP address.
95 Your ROS nodes may fail to communicate.
96
97 Please use ROS_IP to set the correct IP address to use."""%(m.get_host()))
98
99 else:
100
101 raise RLException("ERROR: unable to contact ROS master at [%s]"%(m.uri))
102
103 if is_core and not is_rostest:
104
105
106 env_uri = rosgraph.get_master_uri()
107 env_host, env_port = rosgraph.network.parse_http_host_and_port(env_uri)
108
109 if not rosgraph.network.is_local_address(env_host):
110
111 printerrlog("WARNING: ROS_MASTER_URI [%s] host is not set to this machine"%(env_uri))
112 elif env_port != m.get_port():
113
114 printerrlog("WARNING: ROS_MASTER_URI port [%s] does not match this roscore [%s]"%(env_port, m.get_port()))
115
117 splits = [s for s in p.split(rosgraph.names.SEP) if s]
118 curr =rosgraph.names.SEP
119 parents = [curr]
120 for s in splits[:-1]:
121 next_ = curr + s + rosgraph.names.SEP
122 parents.append(next_)
123 curr = next_
124 return parents
125
127 """
128 Reduce clear_params such that only the highest-level namespaces
129 are represented for overlapping namespaces. e.g. if both /foo/ and
130 /foo/bar are present, return just /foo/.
131
132 @param params: paremter namees
133 @type params: [str]
134 @return: unified parameters
135 @rtype: [str]
136 """
137
138
139
140 canon_params = []
141 for p in params:
142 if not p[-1] == rosgraph.names.SEP:
143 p += rosgraph.names.SEP
144 if not p in canon_params:
145 canon_params.append(p)
146
147 clear_params = canon_params[:]
148 for p in canon_params:
149 for parent in _all_namespace_parents(p):
150 if parent in canon_params and p in clear_params and p != '/':
151 clear_params.remove(p)
152 return clear_params
153
155 """
156 Utility function to strip illegal characters from hostname. This
157 is implemented to be correct, not efficient."""
158
159
160 fixed = 'host_'
161
162 hostname = hostname.lower()
163 for c in hostname:
164
165 if (c >= 'a' and c <= 'z') or \
166 (c >= '0' and c <= '9'):
167 fixed+=c
168 else:
169 fixed+='_'
170 return fixed
171
173 """
174 Helper class to manage distributing process events. It functions as
175 a higher level aggregator of events. In particular, events about
176 remote and local processes require different mechanisms for being
177 caught and reported.
178 """
180 self.process_listeners = []
181
183 """
184 Add listener to list of listeners. Not threadsafe.
185 @param l: listener
186 @type l: L{ProcessListener}
187 """
188 self.process_listeners.append(l)
189
191 """
192 ProcessListener callback
193 """
194 for l in self.process_listeners:
195 try:
196 l.process_died(process_name, exit_code)
197 except Exception as e:
198 logging.getLogger('roslaunch').error(traceback.format_exc())
199
201 """
202 Listener interface for events related to ROSLaunch.
203 ROSLaunchListener is currently identical to the lower-level
204 L{roslaunch.pmon.ProcessListener} interface, but is separate for
205 architectural reasons. The lower-level
206 L{roslaunch.pmon.ProcessMonitor} does not provide events about
207 remotely running processes.
208 """
209
211 """
212 Notifies listener that process has died. This callback only
213 occurs for processes that die during normal process monitor
214 execution -- processes that are forcibly killed during
215 L{roslaunch.pmon.ProcessMonitor} shutdown are not reported.
216 @param process_name: name of process
217 @type process_name: str
218 @param exit_code int: exit code of process. If None, it means
219 that L{roslaunch.pmon.ProcessMonitor} was unable to determine an exit code.
220 @type exit_code: int
221 """
222 pass
223
225 """
226 Runs a roslaunch. The normal sequence of API calls is L{launch()}
227 followed by L{spin()}. An external thread can call L{stop()}; otherwise
228 the runner will block until an exit signal. Another usage is to
229 call L{launch()} followed by repeated calls to L{spin_once()}. This usage
230 allows the main thread to continue to do work while processes are
231 monitored.
232 """
233
234 - def __init__(self, run_id, config, server_uri=None, pmon=None, is_core=False, remote_runner=None, is_child=False, is_rostest=False):
235 """
236 @param run_id: /run_id for this launch. If the core is not
237 running, this value will be used to initialize /run_id. If
238 the core is already running, this value will be checked
239 against the value stored on the core. L{ROSLaunchRunner} will
240 fail during L{launch()} if they do not match.
241 @type run_id: str
242 @param config: roslauch instance to run
243 @type config: L{ROSLaunchConfig}
244 @param server_uri: XML-RPC URI of roslaunch server.
245 @type server_uri: str
246 @param pmon: optionally override the process
247 monitor the runner uses for starting and tracking processes
248 @type pmon: L{ProcessMonitor}
249
250 @param is_core: if True, this runner is a roscore
251 instance. This affects the error behavior if a master is
252 already running -- aborts if is_core is True and a core is
253 detected.
254 @type is_core: bool
255 @param remote_runner: remote roslaunch process runner
256 @param is_rostest: if True, this runner is a rostest
257 instance. This affects certain validation checks.
258 @type is_rostest: bool
259 """
260 if run_id is None:
261 raise RLException("run_id is None")
262 self.run_id = run_id
263
264
265
266
267
268
269 self.config = config
270 self.server_uri = server_uri
271 self.is_child = is_child
272 self.is_core = is_core
273 self.is_rostest = is_rostest
274 self.logger = logging.getLogger('roslaunch')
275 self.pm = pmon or start_process_monitor()
276
277
278
279
280 self.listeners = _ROSLaunchListeners()
281 if self.pm is None:
282 raise RLException("unable to initialize roslaunch process monitor")
283 if self.pm.is_shutdown:
284 raise RLException("bad roslaunch process monitor initialization: process monitor is already dead")
285
286 self.pm.add_process_listener(self.listeners)
287
288 self.remote_runner = remote_runner
289
291 """
292 Add listener to list of listeners. Not threadsafe. Must be
293 called before processes started.
294 @param l: listener
295 @type l: L{ProcessListener}
296 """
297 self.listeners.add_process_listener(l)
298
300 """
301 Load parameters onto the parameter server
302 """
303 self.logger.info("load_parameters starting ...")
304 config = self.config
305 param_server = config.master.get()
306 p = None
307 try:
308
309 param_server_multi = config.master.get_multi()
310
311
312
313 for p in _unify_clear_params(config.clear_params):
314 if param_server.hasParam(_ID, p)[2]:
315
316 param_server_multi.deleteParam(_ID, p)
317 r = param_server_multi()
318 for code, msg, _ in r:
319 if code != 1:
320 raise RLException("Failed to clear parameter: %s"%(msg))
321
322
323 param_server_multi = config.master.get_multi()
324 for p in config.params.itervalues():
325
326
327 param_server_multi.setParam(_ID, p.key, p.value)
328 r = param_server_multi()
329 for code, msg, _ in r:
330 if code != 1:
331 raise RLException("Failed to set parameter: %s"%(msg))
332 except RLException:
333 raise
334 except Exception as e:
335 printerrlog("load_parameters: unable to set parameters (last param was [%s]): %s"%(p,e))
336 raise
337 self.logger.info("... load_parameters complete")
338
340 """
341 Launch all the declared nodes/master
342 @return: two lists of node names where the first
343 is the nodes that successfully launched and the second is the
344 nodes that failed to launch.
345 @rtype: [[str], [str]]
346 """
347 config = self.config
348 succeeded = []
349 failed = []
350 self.logger.info("launch_nodes: launching local nodes ...")
351 local_nodes = config.nodes
352
353
354 local_nodes = [n for n in config.nodes if is_machine_local(n.machine)]
355
356 for node in local_nodes:
357 proc, success = self.launch_node(node)
358 if success:
359 succeeded.append(str(proc))
360 else:
361 failed.append(str(proc))
362
363 if self.remote_runner:
364 self.logger.info("launch_nodes: launching remote nodes ...")
365 r_succ, r_fail = self.remote_runner.launch_remote_nodes()
366 succeeded.extend(r_succ)
367 failed.extend(r_fail)
368
369 self.logger.info("... launch_nodes complete")
370 return succeeded, failed
371
373 """
374 Launches master if requested.
375 @raise RLException: if master launch fails
376 """
377 m = self.config.master
378 is_running = m.is_running()
379
380 if self.is_core and is_running:
381 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))
382
383 if not is_running:
384 validate_master_launch(m, self.is_core, self.is_rostest)
385
386 printlog("auto-starting new master")
387 p = create_master_process(self.run_id, m.type, get_ros_root(), m.get_port())
388 self.pm.register_core_proc(p)
389 success = p.start()
390 if not success:
391 raise RLException("ERROR: unable to auto-start master process")
392 timeout_t = time.time() + _TIMEOUT_MASTER_START
393 while not m.is_running() and time.time() < timeout_t:
394 time.sleep(0.1)
395
396 if not m.is_running():
397 raise RLException("ERROR: could not contact master [%s]"%m.uri)
398
399 printlog_bold("ROS_MASTER_URI=%s"%m.uri)
400
401
402
403 update_terminal_name(m.uri)
404
405
406 param_server = m.get()
407
408
409 self._check_and_set_run_id(param_server, self.run_id)
410
411 if self.server_uri:
412
413
414 hostname, port = rosgraph.network.parse_http_host_and_port(self.server_uri)
415 hostname = _hostname_to_rosname(hostname)
416 self.logger.info("setting /roslaunch/uris/%s__%s' to %s"%(hostname, port, self.server_uri))
417 param_server.setParam(_ID, '/roslaunch/uris/%s__%s'%(hostname, port),self.server_uri)
418
420 """
421 Initialize self.run_id to existing value or setup parameter
422 server with /run_id set to default_run_id
423 @param default_run_id: run_id to use if value is not set
424 @type default_run_id: str
425 @param param_server: parameter server proxy
426 @type param_server: xmlrpclib.ServerProxy
427 """
428 code, _, val = param_server.hasParam(_ID, '/run_id')
429 if code == 1 and not val:
430 printlog_bold("setting /run_id to %s"%run_id)
431 param_server.setParam('/roslaunch', '/run_id', run_id)
432 else:
433
434 code, _, val = param_server.getParam('/roslaunch', '/run_id')
435 if code != 1:
436
437
438 raise RLException("ERROR: unable to retrieve /run_id from parameter server")
439 if run_id != val:
440 raise RLException("run_id on parameter server does not match declared run_id: %s vs %s"%(val, run_id))
441
442
443
445 """
446 Launch a single L{Executable} object. Blocks until executable finishes.
447 @param e: Executable
448 @type e: L{Executable}
449 @raise RLException: if exectuable fails. Failure includes non-zero exit code.
450 """
451 try:
452
453 cmd = e.command
454 cmd = "%s %s"%(cmd, ' '.join(e.args))
455 print "running %s"%cmd
456 local_machine = self.config.machines['']
457 env = setup_env(None, local_machine, self.config.master.uri)
458 retcode = subprocess.call(cmd, shell=True, env=env)
459 if retcode < 0:
460 raise RLException("command [%s] failed with exit code %s"%(cmd, retcode))
461 except OSError as e:
462 raise RLException("command [%s] failed: %s"%(cmd, e))
463
464
465
467 """
468 @raise RLException: if exectuable fails. Failure includes non-zero exit code.
469 """
470 exes = [e for e in self.config.executables if e.phase == PHASE_SETUP]
471 for e in exes:
472 self._launch_executable(e)
473
475 """
476 launch any core services that are not already running. master must
477 be already running
478 @raise RLException: if core launches fail
479 """
480 config = self.config
481 master = config.master.get()
482 tolaunch = []
483 for node in config.nodes_core:
484 node_name = rosgraph.names.ns_join(node.namespace, node.name)
485 code, msg, _ = master.lookupNode(_ID, node_name)
486 if code == -1:
487 tolaunch.append(node)
488 elif code == 1:
489 print "core service [%s] found"%node_name
490 else:
491 print >> sys.stderr, "WARN: master is not behaving well (unexpected return value when looking up node)"
492 self.logger.error("ERROR: master return [%s][%s] on lookupNode call"%(code,msg))
493
494 for node in tolaunch:
495 node_name = rosgraph.names.ns_join(node.namespace, node.name)
496 name, success = self.launch_node(node, core=True)
497 if success:
498 print "started core service [%s]"%node_name
499 else:
500 raise RLException("failed to start core service [%s]"%node_name)
501
503 """
504 Launch a single node locally. Remote launching is handled separately by the remote module.
505 If node name is not assigned, one will be created for it.
506
507 @param node Node: node to launch
508 @param core bool: if True, core node
509 @return obj, bool: Process handle, successful launch. If success, return actual Process instance. Otherwise return name.
510 """
511 self.logger.info("... preparing to launch node of type [%s/%s]", node.package, node.type)
512
513
514
515
516 if node.machine is None:
517 node.machine = self.config.machines['']
518 if node.name is None:
519 node.name = rosgraph.names.anonymous_name(node.type)
520
521 master = self.config.master
522 import roslaunch.node_args
523 try:
524 process = create_node_process(self.run_id, node, master.uri)
525 except roslaunch.node_args.NodeParamsException as e:
526 self.logger.error(e)
527 if node.package == 'rosout' and node.type == 'rosout':
528 printerrlog("\n\n\nERROR: rosout is not built. Please run 'rosmake rosout'\n\n\n")
529 else:
530 printerrlog("ERROR: cannot launch node of type [%s/%s]: %s"%(node.package, node.type, str(e)))
531 if node.name:
532 return node.name, False
533 else:
534 return "%s/%s"%(node.package,node.type), False
535
536 self.logger.info("... created process [%s]", process.name)
537 if core:
538 self.pm.register_core_proc(process)
539 else:
540 self.pm.register(process)
541 node.process_name = process.name
542 self.logger.info("... registered process [%s]", process.name)
543
544
545 success = process.start()
546 if not success:
547 if node.machine.name:
548 printerrlog("launch of %s/%s on %s failed"%(node.package, node.type, node.machine.name))
549 else:
550 printerrlog("local launch of %s/%s failed"%(node.package, node.type))
551 else:
552 self.logger.info("... successfully launched [%s]", process.name)
553 return process, success
554
556 """
557 Check for running node process.
558 @param node Node: node object to check
559 @return bool: True if process associated with node is running (launched && !dead)
560 """
561
562 return node.process_name and self.pm.has_process(node.process_name)
563
565 """
566 Same as spin() but only does one cycle. must be run from the main thread.
567 """
568 if not self.pm:
569 return False
570 return self.pm.mainthread_spin_once()
571
573 """
574 spin() must be run from the main thread. spin() is very
575 important for roslaunch as it picks up jobs that the process
576 monitor need to be run in the main thread.
577 """
578 self.logger.info("spin")
579
580
581
582 if not self.pm or not self.pm.get_active_names():
583 printlog_bold("No processes to monitor")
584 self.stop()
585 return
586 self.pm.mainthread_spin()
587
588 self.logger.info("process monitor is done spinning, initiating full shutdown")
589 self.stop()
590 printlog_bold("done")
591
593 """
594 Stop the launch and all associated processes. not thread-safe.
595 """
596 self.logger.info("runner.stop()")
597 if self.pm is not None:
598 printlog("shutting down processing monitor...")
599 self.logger.info("shutting down processing monitor %s"%self.pm)
600 self.pm.shutdown()
601 self.pm.join()
602 self.pm = None
603 printlog("... shutting down processing monitor complete")
604 else:
605 self.logger.info("... roslaunch runner has already been stopped")
606
608 """
609 Setup the state of the ROS network, including the parameter
610 server state and core services
611 """
612
613 self.config.assign_machines()
614
615 if self.remote_runner:
616
617 self.remote_runner.add_process_listener(self.listeners)
618
619
620 self._launch_master()
621 self._launch_core_nodes()
622
623
624
625
626
627 self._launch_setup_executables()
628
629
630 if not self.is_child:
631 self._load_parameters()
632
634 """
635 Run the launch. Depending on usage, caller should call
636 spin_once or spin as appropriate after launch().
637 @return ([str], [str]): tuple containing list of nodes that
638 successfully launches and list of nodes that failed to launch
639 @rtype: ([str], [str])
640 @raise RLException: if launch fails (e.g. run_id parameter does
641 not match ID on parameter server)
642 """
643 try:
644 self._setup()
645 succeeded, failed = self._launch_nodes()
646 return succeeded, failed
647 except KeyboardInterrupt:
648 self.stop()
649 raise
650
652 """
653 Run the test node. Blocks until completion or timeout.
654 @param test: test node to run
655 @type test: Test
656 @raise RLTestTimeoutException: if test fails to launch or test times out
657 """
658 self.logger.info("... preparing to run test [%s] of type [%s/%s]", test.test_name, test.package, test.type)
659 proc_h, success = self.launch_node(test)
660 if not success:
661 raise RLException("test [%s] failed to launch"%test.test_name)
662
663
664 timeout_t = time.time() + test.time_limit
665 pm = self.pm
666 while pm.mainthread_spin_once() and self.is_node_running(test):
667
668 if time.time() > timeout_t:
669 raise RLTestTimeoutException("test max time allotted")
670 time.sleep(0.1)
671
672
673
674
675
677 """
678 API for remote running
679 """
685 """
686 Listen to events about remote processes dying. Not
687 threadsafe. Must be called before processes started.
688 @param l: listener
689 @type l: L{ProcessListener}
690 """
691 pass
692
694 """
695 Contact each child to launch remote nodes
696 @return: succeeded, failed
697 @rtype: [str], [str]
698 """
699 pass
700