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 43 -class ScreenHandlerException(Exception):
44 pass
45
46 -class ScreenSelectionRequest(Exception):
47 ''' ''' 48
49 - def __init__(self, choices, error):
50 Exception.__init__(self) 51 self.choices = choices 52 self.error = error
53
54 - def __str__(self):
55 return "ScreenSelectionRequest from " + self.choices + "::" + repr(self.error)
56
57 58 -class ScreenHandler(object):
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
69 - def createSessionName(cls, node=None):
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 # package_name = str(package) if not package is None else '' 79 # lanchfile_name = str(launchfile).replace('.launch', '') if not launchfile is None else '' 80 node_name = str(node).replace('/',cls.SLASH_SEP) if not node is None else '' 81 # result = ''.join([node_name, '.', package_name, '.', lanchfile_name]) 82 return node_name
83 84 @classmethod
85 - def splitSessionName(cls, session):
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]#.replace(cls.SLASH_SEP, '/') 101 # package = result[2] 102 # launch = ''.join([result[3], '.launch']) if len(result[2]) > 0 else result[2] 103 return pid, node#, package, launch
104 105 @classmethod
106 - def testScreen(cls):
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
115 - def getScreenLogFile(cls, session=None, node=None):
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
131 - def getROSLogFile(cls, node):
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
147 - def getScreenCfgFile(cls, session=None, node=None):
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
163 - def getScreenPidFile(cls, session=None, node=None):
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
227 - def openScreenTerminal(cls, host, screen_name, nodename, user=None):
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 #create a title of the terminal 240 # pid, session_name = cls.splitSessionName(screen_name) 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 # wait for process to avoid 'defunct' processes 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 # wait for process to avoid 'defunct' processes 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 #open the selected screen 276 cls.openScreenTerminal(host, item, node, user) 277 else: 278 # get the available screens 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 # create a list to let the user make a choice, which screen must be open 284 choices = {} 285 for s in screens: 286 pid, session_name = cls.splitSessionName(s) 287 choices[''.join([session_name, ' [', pid, ']'])] = s 288 # Open selection 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 #open the selected screen 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 # get the available screens 322 screens = cls.getActiveScreens(host, cls.createSessionName(node), user=user) #user=user, pwd=pwd 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 # wait for process to avoid 'defunct' processes 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
351 - def getLocalOutput(cls, cmd):
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 # wait for process to avoid 'defunct' processes 363 thread = threading.Thread(target=ps.wait) 364 thread.setDaemon(True) 365 thread.start() 366 return result, result_err
367 368 @classmethod
369 - def getSceenCmd(cls, node):
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