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   
 37  import rospy 
 38   
 39  from detailed_msg_box import DetailedError 
 40  from supervised_popen import SupervisedPopen 
 41  import node_manager_fkie as nm 
42 43 44 -class ScreenHandlerException(Exception):
45 pass
46
47 48 -class ScreenSelectionRequest(Exception):
49 ''' ''' 50
51 - def __init__(self, choices, error):
52 Exception.__init__(self) 53 self.choices = choices 54 self.error = error
55
56 - def __str__(self):
57 return "ScreenSelectionRequest from " + self.choices + "::" + repr(self.error)
58
59 60 -class ScreenHandler(object):
61 ''' 62 The class to handle the running screen sessions and create new sessions on 63 start of the ROS nodes. 64 ''' 65 66 LOG_PATH = os.environ.get('ROS_LOG_DIR') if os.environ.get('ROS_LOG_DIR') else os.path.join(os.path.expanduser('~'), '.ros/log/') 67 SCREEN = "/usr/bin/screen" 68 SLASH_SEP = '_' 69 70 @classmethod
71 - def createSessionName(cls, node=None):
72 ''' 73 Creates a name for the screen session. All slash separators are replaced by 74 L{SLASH_SEP} 75 @param node: the name of the node 76 @type node: C{str} 77 @return: name for the screen session. 78 @rtype: C{str} 79 ''' 80 # package_name = str(package) if not package is None else '' 81 # lanchfile_name = str(launchfile).replace('.launch', '') if not launchfile is None else '' 82 node_name = str(node).replace('/', cls.SLASH_SEP) if node is not None else '' 83 # result = ''.join([node_name, '.', package_name, '.', lanchfile_name]) 84 return node_name
85 86 @classmethod
87 - def splitSessionName(cls, session):
88 ''' 89 Splits the screen session name into PID and session name generated by 90 L{createSessionName()}. 91 @param session: the screen session name 92 @type session: C{str} 93 @return: PID, session name generated by L{createSessionName()}. Not presented 94 values are coded as empty strings. Not valid session names have an empty 95 PID string. 96 @rtype: C{str, str} 97 ''' 98 result = session.split('.', 1) 99 if len(result) != 2: 100 return '', '' 101 pid = result[0] 102 node = result[1] # .replace(cls.SLASH_SEP, '/') 103 # package = result[2] 104 # launch = ''.join([result[3], '.launch']) if len(result[2]) > 0 else result[2] 105 return pid, node # , package, launch
106 107 @classmethod
108 - def testScreen(cls):
109 ''' 110 Tests for whether the L{SCREEN} binary exists and raise an exception if not. 111 @raise ScreenHandlerException: if the screen binary not exists. 112 ''' 113 if not os.path.isfile(cls.SCREEN): 114 raise ScreenHandlerException(''.join([cls.SCREEN, " is missing"]))
115 116 @classmethod
117 - def getScreenLogFile(cls, session=None, node=None):
118 ''' 119 Generates a log file name for the screen session. 120 @param session: the name of the screen session 121 @type session: C{str} 122 @return: the log file name 123 @rtype: C{str} 124 ''' 125 if session is not None: 126 return os.path.join(cls.LOG_PATH, session + '.log') 127 elif node is not None: 128 return os.path.join(cls.LOG_PATH, cls.createSessionName(node) + '.log') 129 else: 130 return os.path.join(cls.LOG_PATH, 'unknown.log')
131 132 @classmethod
133 - def getROSLogFile(cls, node):
134 ''' 135 Generates a log file name of the ROS log. 136 @param node: the name of the node 137 @type node: C{str} 138 @return: the ROS log file name 139 @rtype: C{str} 140 @todo: get the run_id from the ROS parameter server and search in this log folder 141 for the log file (handle the node started using a launch file). 142 ''' 143 if node is not None: 144 return os.path.join(cls.LOG_PATH, node.strip(rospy.names.SEP).replace(rospy.names.SEP, '_') + '.log') 145 else: 146 return ''
147 148 @classmethod
149 - def getScreenCfgFile(cls, session=None, node=None):
150 ''' 151 Generates a configuration file name for the screen session. 152 @param session: the name of the screen session 153 @type session: C{str} 154 @return: the configuration file name 155 @rtype: C{str} 156 ''' 157 if session is not None: 158 return os.path.join(cls.LOG_PATH, session + '.conf') 159 elif node is not None: 160 return os.path.join(cls.LOG_PATH, cls.createSessionName(node) + '.conf') 161 else: 162 return os.path.join(cls.LOG_PATH, 'unknown.conf')
163 164 @classmethod
165 - def getScreenPidFile(cls, session=None, node=None):
166 ''' 167 Generates a PID file name for the screen session. 168 @param session: the name of the screen session 169 @type session: C{str} 170 @return: the PID file name 171 @rtype: C{str} 172 ''' 173 if session is not None: 174 return os.path.join(cls.LOG_PATH, session + '.pid') 175 elif node is not None: 176 return os.path.join(cls.LOG_PATH, cls.createSessionName(node) + '.pid') 177 else: 178 return os.path.join(cls.LOG_PATH, 'unknown.pid')
179 180 @classmethod
181 - def getActiveScreens(cls, host, session='', auto_pw_request=True, user=None, pwd=None):
182 ''' 183 Returns the list with all compatible screen names. If the session is set to 184 an empty string all screens will be returned. 185 @param host: the host name or IP to search for the screen session. 186 @type host: C{str} 187 @param session: the name or the suffix of the screen session 188 @type session: C{str} (Default: C{''}) 189 @return: the list with session names 190 @rtype: C{[str(session name), ...]} 191 @raise Exception: on errors while resolving host 192 @see: L{node_manager_fkie.is_local()} 193 ''' 194 try: 195 return cls._getActiveScreens(host, session, auto_pw_request, user, pwd) 196 except nm.AuthenticationRequest as e: 197 raise nm.InteractionNeededError(e, cls.getActiveScreens, (host, session, auto_pw_request))
198 199 @classmethod
200 - def _getActiveScreens(cls, host, session='', auto_pw_request=True, user=None, pwd=None):
201 ''' 202 Returns the list with all compatible screen names. If the session is set to 203 an empty string all screens will be returned. 204 @param host: the host name or IP to search for the screen session. 205 @type host: C{str} 206 @param session: the name or the suffix of the screen session 207 @type session: C{str} (Default: C{''}) 208 @return: the list with session names 209 @rtype: C{[str(session name), ...]} 210 @raise Exception: on errors while resolving host 211 @see: L{node_manager_fkie.is_local()} 212 ''' 213 output = None 214 result = [] 215 if nm.is_local(host): 216 output = cls.getLocalOutput([cls.SCREEN, '-ls']) 217 else: 218 _, stdout, _, _ = nm.ssh().ssh_exec(host, [cls.SCREEN, ' -ls'], user, pwd, auto_pw_request, close_stdin=True, close_stderr=True) 219 output = stdout.read() 220 stdout.close() 221 if output: 222 splits = output.split() 223 for i in splits: 224 if i.count('.') > 0 and i.endswith(session) and i.find('._') >= 0: 225 result.append(i) 226 return result
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), object_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 @classmethod
303 - def killScreens(cls, node, host, auto_ok_request=True, user=None, pw=None):
304 ''' 305 Searches for the screen associated with the given node and kill this screens. 306 @param node: the name of the node those screen output to show 307 @type node: C{str} 308 @param host: the host name or ip where the screen is running 309 @type host: C{str} 310 ''' 311 if node is None or len(node) == 0: 312 return False 313 try: 314 # get the available screens 315 screens = cls._getActiveScreens(host, cls.createSessionName(node), auto_ok_request, user=user, pwd=pw) # user=user, pwd=pwd 316 if screens: 317 do_kill = True 318 if auto_ok_request: 319 try: 320 from python_qt_binding.QtGui import QMessageBox 321 except: 322 from python_qt_binding.QtWidgets import QMessageBox 323 result = QMessageBox.question(None, "Kill SCREENs?", '\n'.join(screens), QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok) 324 if result & QMessageBox.Ok: 325 do_kill = True 326 if do_kill: 327 for s in screens: 328 pid, _, _ = s.partition('.') 329 if pid: 330 try: 331 nm.starter()._kill_wo(host, int(pid), auto_ok_request, user, pw) 332 except: 333 import traceback 334 rospy.logwarn("Error while kill screen (PID: %s) on host '%s': %s", str(pid), str(host), traceback.format_exc(1)) 335 if nm.is_local(host): 336 SupervisedPopen([cls.SCREEN, '-wipe'], object_id='screen -wipe', description="screen: clean up the socket with -wipe") 337 else: 338 nm.ssh().ssh_exec(host, [cls.SCREEN, '-wipe'], close_stdin=True, close_stdout=True, close_stderr=True) 339 except nm.AuthenticationRequest as e: 340 raise nm.InteractionNeededError(e, cls.killScreens, (node, host, auto_ok_request))
341 342 @classmethod
343 - def getLocalOutput(cls, cmd):
344 ''' 345 This method is used to read the output of the command executed in a terminal. 346 @param cmd: the command to execute in a terminal 347 @type cmd: C{str} 348 @return: the output generated by the execution of the command. 349 @rtype: output 350 ''' 351 ps = SupervisedPopen(cmd, stdout=subprocess.PIPE) 352 result = ps.stdout.read() 353 return result
354 355 @classmethod
356 - def getSceenCmd(cls, node):
357 ''' 358 Generates a configuration file and return the command prefix to start the given node 359 in a screen terminal. 360 @param node: the name of the node 361 @type node: C{str} 362 @return: the command prefix 363 @rtype: C{str} 364 ''' 365 f = open(cls.getScreenCfgFile(node=node), 'w') 366 f.write(''.join(["logfile ", cls.getScreenLogFile(node=node), "\n"])) 367 f.write("logfile flush 0\n") 368 f.write("defscrollback 10000\n") 369 ld_library_path = os.getenv('LD_LIBRARY_PATH', '') 370 if ld_library_path: 371 f.write(' '.join(['setenv', 'LD_LIBRARY_PATH', ld_library_path, "\n"])) 372 ros_etc_dir = os.getenv('ROS_ETC_DIR', '') 373 if ros_etc_dir: 374 f.write(' '.join(['setenv', 'ROS_ETC_DIR', ros_etc_dir, "\n"])) 375 f.close() 376 return ' '.join([cls.SCREEN, '-c', cls.getScreenCfgFile(node=node), '-L', '-dmS', cls.createSessionName(node=node)])
377