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