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 supervised_popen import SupervisedPopen 
 40  import node_manager_fkie as nm 
 41  from node_manager_fkie.common import utf8 
42 43 44 -class NoScreenOpenLogRequest(Exception):
45 ''' ''' 46
47 - def __init__(self, node, host):
48 Exception.__init__(self) 49 self.node = node 50 self.host = host
51
52 - def msg(self):
53 return 'No screen for %s on %s found! See log for details!' % (self.node, self.host)
54
55 - def __str__(self):
56 return "NoScreenOpenLogRequest for %s on %s" % (self.node, self.host)
57
58 59 -class ScreenHandlerException(Exception):
60 pass
61
62 63 -class ScreenSelectionRequest(Exception):
64 ''' ''' 65
66 - def __init__(self, choices, error):
67 Exception.__init__(self) 68 self.choices = choices 69 self.error = error
70
71 - def __str__(self):
72 return "ScreenSelectionRequest from " + self.choices + "::" + repr(self.error)
73
74 75 -class ScreenHandler(object):
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
86 - def createSessionName(cls, node=None):
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 # package_name = utf8(package) if not package is None else '' 96 # lanchfile_name = utf8(launchfile).replace('.launch', '') if not launchfile is None else '' 97 node_name = utf8(node).replace('/', cls.SLASH_SEP) if node is not None else '' 98 # result = ''.join([node_name, '.', package_name, '.', lanchfile_name]) 99 return node_name
100 101 @classmethod
102 - def splitSessionName(cls, session):
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] # .replace(cls.SLASH_SEP, '/') 118 # package = result[2] 119 # launch = ''.join([result[3], '.launch']) if len(result[2]) > 0 else result[2] 120 return pid, node # , package, launch
121 122 @classmethod
123 - def testScreen(cls):
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
132 - def getScreenLogFile(cls, session=None, node=None):
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
148 - def getROSLogFile(cls, node):
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
164 - def getScreenCfgFile(cls, session=None, node=None):
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
180 - def getScreenPidFile(cls, session=None, node=None):
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
244 - def openScreenTerminal(cls, host, screen_name, nodename, user=None):
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 # create a title of the terminal 257 # pid, session_name = cls.splitSessionName(screen_name) 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 # open the selected screen 285 cls.openScreenTerminal(host, item, node, user) 286 else: 287 # get the available screens 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 # create a list to let the user make a choice, which screen must be open 293 choices = {} 294 for s in screens: 295 pid, session_name = cls.splitSessionName(s) 296 choices[''.join([session_name, ' [', pid, ']'])] = s 297 # Open selection 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 # open the selected screen 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 # get the available screens 330 screens = cls._getActiveScreens(host, cls.createSessionName(node), auto_ok_request, user=user, pwd=pw) # user=user, pwd=pwd 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
355 - def getLocalOutput(cls, cmd):
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
368 - def getSceenCmd(cls, node):
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