Package node_manager_fkie :: Module screen_handler
[frames] | no frames]

Source Code for Module node_manager_fkie.screen_handler

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Fraunhofer nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 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 
43 44 -class ScreenHandlerException(Exception):
45 pass
46
47 -class ScreenSelectionRequest(Exception):
48 ''' ''' 49
50 - def __init__(self, choices, error):
51 Exception.__init__(self) 52 self.choices = choices 53 self.error = error
54
55 - def __str__(self):
56 return "ScreenSelectionRequest from " + self.choices + "::" + repr(self.error)
57
58 59 -class ScreenHandler(object):
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
70 - def createSessionName(cls, node=None):
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 # package_name = str(package) if not package is None else '' 80 # lanchfile_name = str(launchfile).replace('.launch', '') if not launchfile is None else '' 81 node_name = str(node).replace('/',cls.SLASH_SEP) if not node is None else '' 82 # result = ''.join([node_name, '.', package_name, '.', lanchfile_name]) 83 return node_name
84 85 @classmethod
86 - def splitSessionName(cls, session):
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]#.replace(cls.SLASH_SEP, '/') 102 # package = result[2] 103 # launch = ''.join([result[3], '.launch']) if len(result[2]) > 0 else result[2] 104 return pid, node#, package, launch
105 106 @classmethod
107 - def testScreen(cls):
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
116 - def getScreenLogFile(cls, session=None, node=None):
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
132 - def getROSLogFile(cls, node):
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
148 - def getScreenCfgFile(cls, session=None, node=None):
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
164 - def getScreenPidFile(cls, session=None, node=None):
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
229 - def openScreenTerminal(cls, host, screen_name, nodename, user=None):
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 #create a title of the terminal 242 # pid, session_name = cls.splitSessionName(screen_name) 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 #open the selected screen 270 cls.openScreenTerminal(host, item, node, user) 271 else: 272 # get the available screens 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 # create a list to let the user make a choice, which screen must be open 278 choices = {} 279 for s in screens: 280 pid, session_name = cls.splitSessionName(s) 281 choices[''.join([session_name, ' [', pid, ']'])] = s 282 # Open selection 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 #open the selected screen 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 # get the available screens 316 screens = cls.getActiveScreens(host, cls.createSessionName(node), user=user) #user=user, pwd=pwd 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 # output, error, ok = nm.ssh().ssh_exec(host, [cls.SCREEN, '-wipe']) 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
342 - def getLocalOutput(cls, cmd):
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
355 - def getSceenCmd(cls, node):
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