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