buildframework/helium/sf/python/pythoncore/lib/ccm/extra.py
changeset 587 85df38eb4012
child 588 c7c26511138f
equal deleted inserted replaced
217:0f5e3a7fb6af 587:85df38eb4012
       
     1 #============================================================================ 
       
     2 #Name        : extra.py 
       
     3 #Part of     : Helium 
       
     4 
       
     5 #Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     6 #All rights reserved.
       
     7 #This component and the accompanying materials are made available
       
     8 #under the terms of the License "Eclipse Public License v1.0"
       
     9 #which accompanies this distribution, and is available
       
    10 #at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
    11 #
       
    12 #Initial Contributors:
       
    13 #Nokia Corporation - initial contribution.
       
    14 #
       
    15 #Contributors:
       
    16 #
       
    17 #Description:
       
    18 #===============================================================================
       
    19 
       
    20 """ Library that contains custom Synergy functionnlities: e.g
       
    21         * Snapshotter that can snapshot unfrozen baselines
       
    22         * Threaded snapshotter.
       
    23 """
       
    24 import ccm
       
    25 import os
       
    26 import threading
       
    27 import threadpool
       
    28 import traceback
       
    29 import logging
       
    30 from xml.dom.minidom import getDOMImplementation, parse
       
    31 import StringIO     #pylint throws this up as unused but it is required by delete call in the code 
       
    32                     #so do not remove unless removeing the delete (which is required at some point).
       
    33 
       
    34 # Uncomment this line to enable logging in this module, or configure logging elsewhere
       
    35 #logging.basicConfig(level=logging.DEBUG)
       
    36 _logger = logging.getLogger('ccm.extra')
       
    37 
       
    38 class CCMExtraException(ccm.CCMException):
       
    39     """ Exception raised by the methods of this module. """
       
    40     def __init__(self, description, subexceptions):
       
    41         ccm.CCMException.__init__(self, description)
       
    42         self.subexceptions = subexceptions
       
    43     
       
    44     
       
    45 
       
    46 def Snapshot(project, targetdir, dir=None):
       
    47     """ This function can snapshot anything from Synergy, even prep/working projects """
       
    48     assert project != None, "a project object must be supplied"
       
    49     assert project.type == "project", "project must be of project type"
       
    50     if not dir:
       
    51         dir = project.root_dir()
       
    52     targetdir = os.path.join(targetdir, dir.name)
       
    53     os.makedirs(targetdir)
       
    54     for object in dir.children(project):
       
    55         if object.type == 'dir':
       
    56             Snapshot(project, targetdir, object)
       
    57         elif object.type == 'project':
       
    58             Snapshot(object, targetdir)
       
    59         else:
       
    60             object.to_file(os.path.join(targetdir, object.name))
       
    61 
       
    62 
       
    63 class _FastSnapshot:
       
    64     """ Snapshot Job executed by the thread pool. """
       
    65     def __init__(self, pool, project, targetdir, callback, exc_hld):
       
    66         """ Construtor, will store the parameter for the checkout. """
       
    67         self.pool = pool
       
    68         self.project = project
       
    69         self.targetdir = targetdir
       
    70         self.callback = callback
       
    71         self.exc_hld = exc_hld
       
    72 
       
    73     def __call__(self):
       
    74         """ Do the checkout, and then walkthrough the project hierarchy to find subproject to snapshot. """
       
    75         _logger.info("Snapshotting %s under %s" % (self.project, self.targetdir))
       
    76         self.project.snapshot(self.targetdir, False)
       
    77         def walk(dir, targetdir):
       
    78             """walkthrough the project hierarchy to find subproject to snapshot"""
       
    79             for object in dir.children(self.project):
       
    80                 if isinstance(object, ccm.Dir):
       
    81                     walk(object, os.path.join(targetdir, object.name))
       
    82                 elif isinstance(object, ccm.Project):
       
    83                     _logger.info("Adding project %s" % object.objectname)
       
    84                     self.pool.addWork(_FastSnapshot(self.pool, object, targetdir, self.callback, self.exc_hld))
       
    85                     
       
    86         if len(self.project.subprojects) > 0:
       
    87             rootdir = self.project.root_dir()
       
    88             walk(rootdir, os.path.join(self.targetdir, rootdir.name))
       
    89         return ""
       
    90 
       
    91 def FastSnapshot(project, targetdir, threads=4):
       
    92     """ Create snapshot running by running snapshots concurrently.
       
    93         Snapshot will be made recursively top-down, and each sub project will
       
    94         be snapshotted in parallel. 
       
    95     """
       
    96     assert threads > 0, "Number of threads must be > 0."
       
    97     assert project != None, "a project object must be supplied."
       
    98     assert project.type == "project", "project must be of project type."
       
    99     
       
   100     # error handling
       
   101     exceptions = []
       
   102     results = []
       
   103     def handle_exception(request, exc_info):
       
   104         """ append the exceptions"""
       
   105         _logger.error( "Exception occurred in request #%s: %s" % (request.requestID, exc_info[1]))
       
   106         exceptions.append(exc_info[1])
       
   107 
       
   108     def handle_result(result):
       
   109         """ append the result"""
       
   110         results.append(result)
       
   111    
       
   112     pool = threadpool.ThreadPool(threads)
       
   113     pool.addWork(_FastSnapshot(pool, project, targetdir, handle_result, handle_exception))
       
   114     pool.wait()
       
   115 
       
   116     if len(exceptions):
       
   117         raise CCMExtraException("Errors occurred during snapshot.", exceptions)
       
   118 
       
   119     return "\n".join(results)
       
   120 
       
   121 
       
   122 
       
   123 def FastMaintainWorkArea(project, path, pst=None, threads=4, wat=False):
       
   124     """ Maintain the workarea of a project in parallel. """
       
   125     assert threads > 0, "Number of threads must be > 0."
       
   126     assert isinstance(project, ccm.Project), "a valid project object must be supplied."
       
   127             
       
   128     # error handling
       
   129     exceptions = []
       
   130     results = []
       
   131     def handle_exception(request, exc_info):
       
   132         """append the exception"""
       
   133         _logger.error( "Exception occured in request #%s: %s\n%s" % (request.requestID, exc_info[1], traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])))
       
   134         exceptions.append(exc_info[1])
       
   135     
       
   136     def handle_result(result):
       
   137         """append  the result"""
       
   138         results.append(result)
       
   139 
       
   140     class __MaintainProject:
       
   141         """_Maintain Project"""
       
   142         def __init__(self, subproject, toplevel, wat=False):
       
   143             self.subproject = subproject
       
   144             self.toplevel = toplevel
       
   145             self.wat = wat
       
   146         
       
   147         def __call__(self):
       
   148             output = ""
       
   149             _logger.info("Maintaining project %s" % self.subproject)
       
   150             for tuple in self.subproject.finduse():
       
   151                 if tuple['project'] == self.toplevel:
       
   152                     self.subproject['wa_path'] = os.path.join(self.toplevel['wa_path'], tuple['path'])
       
   153                     self.subproject["project_subdir_template"] = ""
       
   154                     _logger.info("Maintaining project %s under %s" % (self.subproject, self.subproject['wa_path']))
       
   155                     output = self.subproject.work_area(True, True, True, wat=self.wat)
       
   156             _logger.info("Project %s maintained" % self.subproject)
       
   157             return output
       
   158             
       
   159     pool = threadpool.ThreadPool(threads)
       
   160     project.work_area(True, False, True, path, pst, wat=wat)
       
   161     for subproject in project.get_members(type="project"):
       
   162         _logger.info("Adding project %s" % subproject)
       
   163         pool.addWork(__MaintainProject(subproject, project, wat), callback=handle_result, exc_callback=handle_exception)
       
   164     pool.wait()
       
   165     
       
   166     if len(exceptions) > 0:
       
   167         raise CCMExtraException("Errors occured during work area maintenance.", exceptions)
       
   168     
       
   169     return "\n".join(results)
       
   170 
       
   171 
       
   172 def get_toplevel_project(session, path):
       
   173     """get the top level project from CCM or return None"""
       
   174     try:
       
   175         wainfo = session.get_workarea_info(path)
       
   176         project = get_toplevel_project(session, os.path.dirname(wainfo['path']))
       
   177         if project == None:
       
   178             project = wainfo['project']
       
   179         return project
       
   180     except ccm.CCMException:
       
   181         return None
       
   182 
       
   183 
       
   184 class SessionProvider:
       
   185     """ A class which provides an open user session """
       
   186     def __init__(self, opener=None):
       
   187         """initialisation"""
       
   188         self._opener = opener
       
   189         if self._opener is None:
       
   190             self._opener = ccm.open_session
       
   191         
       
   192     def get(self, username=None, password=None, engine=None, dbpath=None, database=None, reuse=True):
       
   193         """return the paramaters required to open a synergy session"""
       
   194         _logger.debug("SessionProvider: Creating a new session.")
       
   195         return self._opener(username, password, engine, dbpath, database, reuse)
       
   196 
       
   197     def __del__(self):
       
   198         """delete the CCM session"""
       
   199         _logger.info("Deleting the session provider.")
       
   200         self.close()
       
   201 
       
   202     def close(self):
       
   203         """close the session which actually does nothing"""
       
   204         pass
       
   205         
       
   206         
       
   207 class CachedSessionProvider(SessionProvider):
       
   208     """
       
   209 <sessions>
       
   210     <session database="foobar" ccmaddr="xxxx"/>
       
   211     <session database="foobarx" ccmaddr="xxxx"/>
       
   212 </sessions>
       
   213     """
       
   214 
       
   215     def __init__(self, opener=None, cache=None):
       
   216         """ Creates CachedSessionProvider, with a specific 
       
   217             opener and cache file.
       
   218         """
       
   219         SessionProvider.__init__(self, opener=opener)
       
   220         _logger.info("Using CachedSessionProvider.")
       
   221         self.__closed = False
       
   222         self._lock = threading.Lock()
       
   223         self.cacheXml = cache
       
   224         self.cacheFree = {}
       
   225         self.cacheUsed = []
       
   226         self.load()
       
   227 
       
   228     
       
   229     def close(self):
       
   230         """ Closing the SessionProvider. """
       
   231         _logger.info("Closing the CachedSessionProvider.")
       
   232         self.save()
       
   233         if self.cacheXml == None:
       
   234             _logger.info("Cleaning up opened sessions.")
       
   235             self._lock.acquire()
       
   236             for dbname in self.cacheFree.keys():
       
   237                 while len(self.cacheFree[dbname]) > 0:
       
   238                     session = self.cacheFree[dbname].pop()
       
   239                     session.close_on_exit = True
       
   240                     session.close()
       
   241             while len(self.cacheUsed) > 0:
       
   242                 session = self.cacheUsed.pop()
       
   243                 session.close_on_exit = True
       
   244             self._lock.release()
       
   245         self.__closed = True
       
   246     
       
   247     def save(self):
       
   248         """ save the sessionProvider"""
       
   249         if self.cacheXml is not None and not self.__closed:
       
   250             _logger.info("Writing %s" % self.cacheXml)
       
   251             impl = getDOMImplementation()
       
   252             sessions = impl.createDocument(None, "sessions", None)
       
   253             top_element = sessions.documentElement
       
   254             self._lock.acquire()
       
   255             def add_session(dbname, session):
       
   256                 """add session"""
       
   257                 sessionNode = sessions.createElement("session")
       
   258                 sessionNode.setAttribute("database", dbname)
       
   259                 sessionNode.setAttribute("ccmaddr", session.addr())
       
   260                 top_element.appendChild(sessionNode)
       
   261             for dbname in self.cacheFree.keys():
       
   262                 for session in self.cacheFree[dbname]:
       
   263                     add_session(dbname, session)
       
   264             for session in self.cacheUsed:
       
   265                 add_session(session.database(), session)
       
   266             self._lock.release()
       
   267             open_f = open(self.cacheXml, "w+")
       
   268             open_f.write(sessions.toprettyxml())
       
   269             open_f.close()
       
   270             _logger.debug(sessions.toprettyxml())
       
   271             
       
   272     
       
   273     def load(self):
       
   274         """load the command"""
       
   275         if self.cacheXml is not None and os.path.exists(self.cacheXml):
       
   276             _logger.info("Loading %s" % self.cacheXml)
       
   277             doc = parse(open(self.cacheXml, 'r')) 
       
   278             sessions = doc.documentElement
       
   279             self._lock.acquire()
       
   280             try:
       
   281                 for child in sessions.childNodes:
       
   282                     if child.nodeType == child.ELEMENT_NODE and child.tagName == "session" and child.hasAttribute('database') and child.hasAttribute('ccmaddr'):
       
   283                         if child.getAttribute('database') not in self.cacheFree:
       
   284                             self.cacheFree[child.getAttribute('database')] = []
       
   285                         if ccm.session_exists(child.getAttribute('ccmaddr'), child.getAttribute('database')):
       
   286                             _logger.info(" + Session: database=%s, ccmaddr=%s" % (child.getAttribute('database'), child.getAttribute('ccmaddr')))
       
   287                             self.cacheFree[child.getAttribute('database')].append(ccm.Session(None, None, None, ccm_addr=child.getAttribute('ccmaddr'), close_on_exit=False))
       
   288                         else:
       
   289                             _logger.info(" - Session database=%s, ccmaddr=%s doesn't seem to be valid anymore." % (child.getAttribute('database'), child.getAttribute('ccmaddr')))
       
   290             finally:
       
   291                 self._lock.release()
       
   292 
       
   293 
       
   294     def get(self, username=None, password=None, engine=None, dbpath=None, database=None, reuse=True):
       
   295         """create a CCM session"""
       
   296         if self.__closed:
       
   297             raise Exception("Could not create further session the provider is closed.")
       
   298         _logger.debug("CachedSessionProvider: Getting a session.")
       
   299         if database is not None and database in self.cacheFree and len(self.cacheFree[database]) > 0:
       
   300             _logger.info("CachedSessionProvider: Reusing session.")
       
   301             self._lock.acquire()
       
   302             session_free = self.cacheFree[database].pop()
       
   303             self.cacheUsed.append(session_free)
       
   304             self._lock.release()
       
   305             return CachedProxySession(self, session_free) 
       
   306         else:
       
   307             _logger.debug("CachedSessionProvider: Creating new session.")
       
   308             session = SessionProvider.get(self, username, password, engine, dbpath, database, False)
       
   309             session.close_on_exit = False
       
   310             proxy_session = CachedProxySession(self, session)
       
   311             data_base = proxy_session.database()
       
   312             self._lock.acquire()
       
   313             if data_base not in self.cacheFree:
       
   314                 self.cacheFree[data_base] = []
       
   315             self.cacheUsed.append(session)
       
   316             self._lock.release()
       
   317             return proxy_session
       
   318 
       
   319     def free(self, session):
       
   320         """freeup a CCM session"""
       
   321         _logger.debug("CachedSessionProvider: Freeing session: %s" % session)
       
   322         data_base = session.database()
       
   323         if session in self.cacheUsed:
       
   324             _logger.debug("CachedSessionProvider: Removing session from used list.")
       
   325             self._lock.acquire()
       
   326             self.cacheUsed.remove(session)
       
   327             self.cacheFree[data_base].append(session)
       
   328             self._lock.release()
       
   329 
       
   330 class CachedProxySession:
       
   331     """ Proxy session which will cleanup the session and free it from the provider """
       
   332     
       
   333     def __init__(self, provider, session):
       
   334         """ Constructor. """
       
   335         self.__session = session 
       
   336         self.__provider = provider
       
   337     
       
   338     def __getattr__(self, attrib):
       
   339         """ Delegate attributes to the session object. """
       
   340         _logger.debug("CachedProxySession.__getattr__(%s)" % attrib)
       
   341         if attrib == "close":
       
   342             return self.__close
       
   343         return getattr(self.__session, attrib)
       
   344 
       
   345     def __close(self):
       
   346         """ Overriding the session closing. """
       
   347         _logger.debug("CachedProxySession.__close")
       
   348         self.__provider.free(self.__session)
       
   349         self.__session.close()
       
   350         
       
   351     def __del__(self):
       
   352         """ Free the session on destruction. """
       
   353         _logger.debug("CachedProxySession.__del__")
       
   354         self.__close()