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 import os
34 import shlex
35 import subprocess
36 import threading
37
38 import rospy
39
40 import node_manager_fkie as nm
41 from detailed_msg_box import DetailedError
45
47 ''' '''
48
50 Exception.__init__(self)
51 self.choices = choices
52 self.error = error
53
55 return "ScreenSelectionRequest from " + self.choices + "::" + repr(self.error)
56
59 '''
60 The class to handle the running screen sessions and create new sessions on
61 start of the ROS nodes.
62 '''
63
64 LOG_PATH = os.environ.get('ROS_LOG_DIR') if os.environ.get('ROS_LOG_DIR') else os.path.join(os.path.expanduser('~'), '.ros/log/')
65 SCREEN = "/usr/bin/screen"
66 SLASH_SEP = '_'
67
68 @classmethod
70 '''
71 Creates a name for the screen session. All slash separators are replaced by
72 L{SLASH_SEP}
73 @param node: the name of the node
74 @type node: C{str}
75 @return: name for the screen session.
76 @rtype: C{str}
77 '''
78
79
80 node_name = str(node).replace('/',cls.SLASH_SEP) if not node is None else ''
81
82 return node_name
83
84 @classmethod
86 '''
87 Splits the screen session name into PID and session name generated by
88 L{createSessionName()}.
89 @param session: the screen session name
90 @type session: C{str}
91 @return: PID, session name generated by L{createSessionName()}. Not presented
92 values are coded as empty strings. Not valid session names have an empty
93 PID string.
94 @rtype: C{str, str}
95 '''
96 result = session.split('.', 1)
97 if len(result) != 2:
98 return '', ''
99 pid = result[0]
100 node = result[1]
101
102
103 return pid, node
104
105 @classmethod
107 '''
108 Tests for whether the L{SCREEN} binary exists and raise an exception if not.
109 @raise ScreenHandlerException: if the screen binary not exists.
110 '''
111 if not os.path.isfile(cls.SCREEN):
112 raise ScreenHandlerException(''.join([cls.SCREEN, " is missing"]))
113
114 @classmethod
116 '''
117 Generates a log file name for the screen session.
118 @param session: the name of the screen session
119 @type session: C{str}
120 @return: the log file name
121 @rtype: C{str}
122 '''
123 if not session is None:
124 return os.path.join(cls.LOG_PATH, session+'.log')
125 elif not node is None:
126 return os.path.join(cls.LOG_PATH, cls.createSessionName(node)+'.log')
127 else:
128 return os.path.join(cls.LOG_PATH, 'unknown.log')
129
130 @classmethod
132 '''
133 Generates a log file name of the ROS log.
134 @param node: the name of the node
135 @type node: C{str}
136 @return: the ROS log file name
137 @rtype: C{str}
138 @todo: get the run_id from the ROS parameter server and search in this log folder
139 for the log file (handle the node started using a launch file).
140 '''
141 if not node is None:
142 return os.path.join(cls.LOG_PATH, node.strip(rospy.names.SEP).replace(rospy.names.SEP,'_')+'.log')
143 else:
144 return ''
145
146 @classmethod
148 '''
149 Generates a configuration file name for the screen session.
150 @param session: the name of the screen session
151 @type session: C{str}
152 @return: the configuration file name
153 @rtype: C{str}
154 '''
155 if not session is None:
156 return os.path.join(cls.LOG_PATH, session+'.conf')
157 elif not node is None:
158 return os.path.join(cls.LOG_PATH, cls.createSessionName(node)+'.conf')
159 else:
160 return os.path.join(cls.LOG_PATH, 'unknown.conf')
161
162 @classmethod
164 '''
165 Generates a PID file name for the screen session.
166 @param session: the name of the screen session
167 @type session: C{str}
168 @return: the PID file name
169 @rtype: C{str}
170 '''
171 if not session is None:
172 return os.path.join(cls.LOG_PATH, session+'.pid')
173 elif not node is None:
174 return os.path.join(cls.LOG_PATH, cls.createSessionName(node)+'.pid')
175 else:
176 return os.path.join(cls.LOG_PATH, 'unknown.pid')
177
178 @classmethod
179 - def getActiveScreens(cls, host, session='', auto_pw_request=True, user=None, pwd=None):
180 '''
181 Returns the list with all compatible screen names. If the session is set to
182 an empty string all screens will be returned.
183 @param host: the host name or IP to search for the screen session.
184 @type host: C{str}
185 @param session: the name or the suffix of the screen session
186 @type session: C{str} (Default: C{''})
187 @return: the list with session names
188 @rtype: C{[str(session name), ...]}
189 @raise Exception: on errors while resolving host
190 @see: L{node_manager_fkie.is_local()}
191 '''
192 try:
193 return cls._getActiveScreens(host, session, auto_pw_request, user, pwd)
194 except nm.AuthenticationRequest as e:
195 raise nm.InteractionNeededError(e, cls.getActiveScreens, (host, session, auto_pw_request))
196
197 @classmethod
198 - def _getActiveScreens(cls, host, session='', auto_pw_request=True, user=None, pwd=None):
199 '''
200 Returns the list with all compatible screen names. If the session is set to
201 an empty string all screens will be returned.
202 @param host: the host name or IP to search for the screen session.
203 @type host: C{str}
204 @param session: the name or the suffix of the screen session
205 @type session: C{str} (Default: C{''})
206 @return: the list with session names
207 @rtype: C{[str(session name), ...]}
208 @raise Exception: on errors while resolving host
209 @see: L{node_manager_fkie.is_local()}
210 '''
211 output = None
212 result = []
213 if nm.is_local(host):
214 out, out_err = cls.getLocalOutput([cls.SCREEN, '-ls'])
215 output = out
216 else:
217 output, error, ok = nm.ssh().ssh_exec(host, [cls.SCREEN, ' -ls'], user, pwd, auto_pw_request)
218 if output:
219 splits = output.split()
220 for i in splits:
221 if i.count('.') > 0 and i.endswith(session) and i.find('._') >= 0:
222 result.append(i)
223 return result
224
225
226 @classmethod
228 '''
229 Open the screen output in a new terminal.
230 @param host: the host name or ip where the screen is running.
231 @type host: C{str}
232 @param screen_name: the name of the screen to show
233 @type screen_name: C{str}
234 @param nodename: the name of the node is used for the title of the terminal
235 @type nodename: C{str}
236 @raise Exception: on errors while resolving host
237 @see: L{node_manager_fkie.is_local()}
238 '''
239
240
241 title_opt = ' '.join(['"SCREEN', nodename, 'on', host, '"'])
242 if nm.is_local(host):
243 cmd = nm.settings().terminal_cmd([cls.SCREEN, '-x', screen_name], title_opt)
244 rospy.loginfo("Open screen terminal: %s", cmd)
245 ps = subprocess.Popen(shlex.split(cmd))
246
247 thread = threading.Thread(target=ps.wait)
248 thread.setDaemon(True)
249 thread.start()
250 else:
251 ps = nm.ssh().ssh_x11_exec(host, [cls.SCREEN, '-x', screen_name], title_opt, user)
252 rospy.loginfo("Open remote screen terminal: %s", ps)
253
254 thread = threading.Thread(target=ps.wait)
255 thread.setDaemon(True)
256 thread.start()
257
258 @classmethod
259 - def openScreen(cls, node, host, auto_item_request=False, user=None, pw=None, items=[]):
260 '''
261 Searches for the screen associated with the given node and open the screen
262 output in a new terminal.
263 @param node: the name of the node those screen output to show
264 @type node: C{str}
265 @param host: the host name or ip where the screen is running
266 @type host: C{str}
267 @raise Exception: on errors while resolving host
268 @see: L{openScreenTerminal()} or L{_getActiveScreens()}
269 '''
270 if node is None or len(node) == 0:
271 return False
272 try:
273 if items:
274 for item in items:
275
276 cls.openScreenTerminal(host, item, node, user)
277 else:
278
279 screens = cls._getActiveScreens(host, cls.createSessionName(node), auto_item_request, user, pw)
280 if len(screens) == 1:
281 cls.openScreenTerminal(host, screens[0], node, user)
282 else:
283
284 choices = {}
285 for s in screens:
286 pid, session_name = cls.splitSessionName(s)
287 choices[''.join([session_name, ' [', pid, ']'])] = s
288
289 if len(choices) > 0:
290 if len(choices) == 1:
291 cls.openScreenTerminal(host, choices[0], node, user)
292 elif auto_item_request:
293 from select_dialog import SelectDialog
294 items, ok = SelectDialog.getValue('Show screen', '', choices.keys(), False)
295 for item in items:
296
297 cls.openScreenTerminal(host, choices[item], node, user)
298 else:
299 raise ScreenSelectionRequest(choices, 'Show screen')
300 else:
301 raise DetailedError('Show screen', ''.join(['No screen for ', node, ' on ', host, ' found!']))
302 return len(screens) > 0
303 except nm.AuthenticationRequest as e:
304 raise nm.InteractionNeededError(e, cls.openScreen, (node, host, auto_item_request))
305 except ScreenSelectionRequest as e:
306 raise nm.InteractionNeededError(e, cls.openScreen, (node, host, auto_item_request, user, pw))
307
308
309 @classmethod
310 - def killScreens(cls, node, host, auto_ok_request=True, user=None, pw=None):
311 '''
312 Searches for the screen associated with the given node and kill this screens.
313 @param node: the name of the node those screen output to show
314 @type node: C{str}
315 @param host: the host name or ip where the screen is running
316 @type host: C{str}
317 '''
318 if node is None or len(node) == 0:
319 return False
320 try:
321
322 screens = cls.getActiveScreens(host, cls.createSessionName(node), user=user)
323 if screens:
324 do_kill = True
325 if auto_ok_request:
326 from python_qt_binding import QtGui
327 result = QtGui.QMessageBox.question(None, "Kill SCREENs?", '\n'.join(screens), QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Ok)
328 if result & QtGui.QMessageBox.Ok:
329 do_kill = True
330 if do_kill:
331 for s in screens:
332 pid, sep, name = s.partition('.')
333 if pid:
334 try:
335 nm.starter()._kill_wo(host, int(pid), auto_ok_request, user, pw)
336 except:
337 import traceback
338 rospy.logwarn("Error while kill screen (PID: %s) on host '%s': %s", str(pid), str(host), traceback.format_exc())
339 if nm.is_local(host):
340 ps = subprocess.Popen([cls.SCREEN, '-wipe'])
341
342 thread = threading.Thread(target=ps.wait)
343 thread.setDaemon(True)
344 thread.start()
345 else:
346 output, error, ok = nm.ssh().ssh_exec(host, [cls.SCREEN, '-wipe'])
347 except nm.AuthenticationRequest as e:
348 raise nm.InteractionNeededError(e, cls.killScreens, (node, host, auto_ok_request))
349
350 @classmethod
352 '''
353 This method is used to read the output of the command executed in a terminal.
354 @param cmd: the command to execute in a terminal
355 @type cmd: C{str}
356 @return: the output generated by the execution of the command.
357 @rtype: C{(output, err)}
358 '''
359 ps = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
360 result = ps.stdout.read()
361 result_err = ps.stderr.read()
362
363 thread = threading.Thread(target=ps.wait)
364 thread.setDaemon(True)
365 thread.start()
366 return result, result_err
367
368 @classmethod
370 '''
371 Generates a configuration file and return the command prefix to start the given node
372 in a screen terminal.
373 @param node: the name of the node
374 @type node: C{str}
375 @return: the command prefix
376 @rtype: C{str}
377 '''
378 f = open(cls.getScreenCfgFile(node=node), 'w')
379 f.write(''.join(["logfile ", cls.getScreenLogFile(node=node), "\n"]))
380 f.write("logfile flush 0\n")
381 f.write("defscrollback 10000\n")
382 ld_library_path = os.getenv('LD_LIBRARY_PATH', '')
383 if ld_library_path:
384 f.write(' '.join(['setenv', 'LD_LIBRARY_PATH', ld_library_path, "\n"]))
385 ros_etc_dir = os.getenv('ROS_ETC_DIR', '')
386 if ros_etc_dir:
387 f.write(' '.join(['setenv', 'ROS_ETC_DIR', ros_etc_dir, "\n"]))
388 f.close()
389 return ' '.join([cls.SCREEN, '-c', cls.getScreenCfgFile(node=node), '-L', '-dmS', cls.createSessionName(node=node)])
390