Back to index

nordugrid-arc-nox  1.1.0~rc6
ahash.py
Go to the documentation of this file.
00001 """
00002 A-Hash
00003 ----
00004 
00005 Centralized prototype implementation of the A-Hash service.
00006 This service is a metadata-store capable of storing object with unique IDs,
00007 where each object has a key-value pairs organized in sections.
00008 
00009 Methods:
00010     - get
00011     - change
00012 
00013 Sample configuration:
00014 
00015         <Service name="pythonservice" id="ahash">
00016             <ClassName>storage.ahash.ahash.AHashService</ClassName>
00017             <AHashClass>storage.ahash.ahash.CentralAHash</AHashClass>
00018             <StoreClass>storage.common.PickleStore</StoreClass>
00019             <StoreCfg><DataDir>ahash_data</DataDir></StoreCfg>
00020         </Service>
00021 """
00022 import arc
00023 import traceback
00024 import time
00025 from arcom import import_class_from_string, get_child_nodes
00026 from arcom.service import ahash_uri, node_to_data, ahash_servicetype
00027 from storage.common import create_metadata
00028 
00029 from arcom.logger import Logger
00030 log = Logger(arc.Logger(arc.Logger_getRootLogger(), 'Storage.A-Hash'))
00031 
00032 class CentralAHash:
00033     """ A centralized implementation of the A-Hash service. """
00034 
00035     def __init__(self, cfg):
00036         """ The constructor of the CentralAHash class.
00037 
00038         CentralAHash(cfg)
00039 
00040         cfg must contain the following parameters:
00041         'storeclass', the name of the class which will store the data
00042         'storecfg', an XMLNode with the configuration of the storeclass
00043         """
00044         log.msg(arc.VERBOSE, "CentralAHash constructor called")
00045         
00046         self.public_request_names = []
00047         # import the storeclass and call its constructor with the datadir
00048 
00049         # the name of the class which is capable of storing the object
00050         storeclass = str(cfg.Get('StoreClass'))
00051         # this is a directory for storing object data
00052         storecfg = cfg.Get('StoreCfg')
00053 
00054         try:
00055             if not hasattr(self, "store"):
00056                 cl = import_class_from_string(storeclass)
00057         except:
00058             log.msg(arc.ERROR, 'Error importing', storeclass)
00059             raise Exception, 'A-Hash cannot run without a store.'
00060         if not hasattr(self, "store"):
00061             self.store = cl(storecfg)
00062 
00063     def get(self, ids, neededMetadata = []):
00064         """ Gets all data of the given IDs.
00065 
00066         get(ids)
00067 
00068         'ids' is a list of strings: the IDs of the requested object
00069         Returns a dictionary of { ID : metadata }
00070             where 'metadata' is a dictionary where the keys are ('section', 'property') tuples
00071         """
00072         if neededMetadata: # if the needed metadata is given
00073             # if a property is empty in a (section, property) pair
00074             # it means we need all properties from that section
00075             allpropsections = [section for (section, property) in neededMetadata if property == '']
00076             # gets the metadata for each ID, filters it and creates an {ID : metadata} dictionary
00077             return dict([(
00078                 ID,
00079                 dict([((section, property), value) # for all metadata entry of this object
00080                     for (section, property), value in self.store.get(ID).items()
00081                         # if this (section, property) is needed or if this section needs all the properties
00082                         if (section, property) in neededMetadata or section in allpropsections])
00083             ) for ID in ids])
00084         else: # gets the metadata for each ID, and creates an {ID : metadata} dictionary
00085             return dict([(ID, self.store.get(ID)) for ID in ids])
00086 
00087     def change(self, changes):
00088         """ Change the '(section, property) : value' entries of an object, if the given conditions are met.
00089 
00090         change(changes)
00091 
00092         'changes' is a dictionary of {'changeID' : 'change'}, where
00093             'changeID' is a reference ID used in the response list
00094             'change' is a (ID, changeType, section, property, value, conditions) tuple, where:
00095                 'ID' is the ID of the object which we want to change
00096                 'changeType' could be 'set', 'unset' or 'delete'
00097                     'set' sets '(section, property)' to 'value'
00098                     'unset' removes '(section, property)', the value does not matter
00099                     'delete' removes the whole objects
00100                 'conditions' is a dictionary of {'conditionID' : 'condition'}, where
00101                     'conditionID' is an ID of this condition
00102                     'condition' is a (type, section, property, value) tuple:
00103                         'type' could be 'is', 'isnot', 'isset', 'unset'
00104                             'is': '(section, property)' is set to 'value'
00105                             'isnot': '(section, property)' is not set to 'value'
00106                             'isset': '(section, property)' is set to any value
00107                             'unset': '(section, property)' is not set at all
00108         
00109         This method returns a dictionary of {'changeID' : (success, conditionID)},
00110             where success could be:
00111                 - 'set'
00112                 - 'unset'
00113                 - 'deleted'
00114                 - 'failed'
00115                 - 'condition not met' (in this case, 'conditionID' gives the ID of the failed condition)
00116                 - 'invalid change type'
00117                 - 'unknown'
00118         """
00119         # prepare the dictionary which will hold the response
00120         response = {}
00121         for (changeID, (ID, changeType, section, property, value, conditionList)) in changes.items():
00122             # for each change in the changes list
00123             # lock the store to avoid inconsistency
00124             while not self.store.lock(blocking = False):
00125                 #print 'A-Hash cannot acquire lock, waiting...'
00126                 time.sleep(0.2)
00127             # prepare the 'success' of this change
00128             success = 'unknown'
00129             # prepare the 'conditionID' for an unmet condition
00130             unmetConditionID = ''
00131             try:
00132                 # get the current content of the object
00133                 obj = self.store.get(ID)
00134                 # now check all the conditions if there is any
00135                 ok = True
00136                 for (conditionID, (conditionType, conditionSection,
00137                         conditionProperty, conditionValue)) in conditionList.items():
00138                     # get the current value of the conditional (section, property), or None if it is not set
00139                     currentValue = obj.get((conditionSection, conditionProperty), None)
00140                     if conditionType == 'is':
00141                         # if the (section, property) is not set to value
00142                         if currentValue != conditionValue:
00143                             ok = False
00144                             unmetConditionID = conditionID
00145                             # jump out of the for statement
00146                             break
00147                     elif conditionType == 'isnot':
00148                         # if the (section, property) is set to value
00149                         if currentValue == conditionValue:
00150                             ok = False
00151                             unmetConditionID = conditionID
00152                             break
00153                     elif conditionType == 'isset':
00154                         # if the (section, property) is not set:
00155                         if currentValue is None:
00156                             ok = False
00157                             unmetConditionID = conditionID
00158                             break
00159                     elif conditionType == 'unset':
00160                         # if the (section, property) is set:
00161                         if currentValue is not None:
00162                             ok = False
00163                             unmetConditionID = conditionID
00164                             break
00165                 # if 'ok' is true then all conditions are met
00166                 if ok:
00167                     if changeType == 'set':
00168                         # sets the new value (overwrites old value of this (section, property))
00169                         obj[(section, property)] = value
00170                         # store the changed object
00171                         self.store.set(ID, obj)
00172                         # set the result of this change
00173                         success = 'set'
00174                     elif changeType == 'unset':
00175                         # removes the entry of (section, property)
00176                         del obj[(section, property)]
00177                         # store the changed object
00178                         self.store.set(ID, obj)
00179                         # set the result of this change
00180                         success = 'unset'
00181                     elif changeType == 'delete':
00182                         # remove the whole object
00183                         self.store.set(ID, None)
00184                         success = 'deleted'
00185                     else:
00186                         # there is no other changeType
00187                         success = 'invalid change type'
00188                 else:
00189                     success = 'condition not met'
00190             except:
00191                 # if there was an exception, set this to failed
00192                 success = 'failed'
00193                 log.msg()
00194             # we are done, release the lock
00195             self.store.unlock()
00196             # append the result of the change to the response list
00197             response[changeID] = (success, unmetConditionID)
00198         return response
00199 
00200 from arcom.xmltree import XMLTree
00201 from arcom.service import Service
00202 
00203 class AHashService(Service):
00204     """ AHashService class implementing the XML interface of the A-Hash service. """
00205 
00206     def __init__(self, cfg):
00207         """ Constructor of the AHashService
00208 
00209         AHashService(cfg)
00210 
00211         'cfg' is an XMLNode which containes the config of this service.
00212         """
00213         self.service_name = 'A-Hash'
00214         # names of provided methods
00215         request_names = ['get','change']
00216         # the name of the class which implements the business logic of the A-Hash service
00217         ahashclass = str(cfg.Get('AHashClass'))
00218         # import and instatiate the business logic class
00219         try:
00220             cl = import_class_from_string(ahashclass)
00221         except:
00222             log.msg(arc.ERROR, 'Error importing class', ahashclass)
00223             raise Exception, 'A-Hash cannot run.'
00224         self.ahash = cl(cfg)
00225         ahash_request_names = self.ahash.public_request_names
00226         # bring the additional request methods into the namespace of this object
00227         for name in ahash_request_names:
00228             if not hasattr(self, name):
00229                 setattr(self, name, getattr(self.ahash, name))
00230                 request_names.append(name)
00231         # call the Service's constructor
00232         Service.__init__(self, [{'request_names' : request_names, 'namespace_prefix': 'ahash', 'namespace_uri': ahash_uri}], cfg)
00233         self.ahash.ns = self.ns
00234 
00235     def get(self, inpayload):
00236         """ Returns the data of the requested objects.
00237 
00238         get(inpayload)
00239 
00240         'inpayload' is an XMLNode containing the IDs of the requested objects
00241         """
00242         # if inpayload.auth:
00243         #     print 'A-Hash auth "get": ', inpayload.auth
00244         # extract the IDs from the XMLNode using the '//ID' XPath expression
00245         ids = [str(id) for id in inpayload.XPathLookup('//ahash:ID', self.ns)]
00246         # get the neededMetadata from the XMLNode
00247         try:
00248             neededMetadata = [
00249                 node_to_data(node, ['section', 'property'], single = True)
00250                     for node in get_child_nodes(inpayload.Child().Get('neededMetadataList'))
00251             ]
00252         except:
00253             log.msg()
00254             neededMetadata = []
00255         # create the response payload
00256         out = self._new_soap_payload()
00257         # create the 'getResponse' node
00258         response_node = out.NewChild('ahash:getResponse')
00259         # gets the result from the business logic class
00260         try:
00261             objects = self.ahash.get(ids, neededMetadata)
00262         except:
00263             error_node = response_node.NewChild('ahash:error')
00264             error_node.Set('ahash is not ready')
00265             return out
00266         # create an XMLTree from the results
00267         tree = XMLTree(from_tree = 
00268             ('ahash:objects', [
00269                 ('ahash:object', [ # for each object
00270                     ('ahash:ID', ID),
00271                     # create the metadata section of the response:
00272                     ('ahash:metadataList', create_metadata(metadata, 'ahash'))
00273                 ]) for (ID, metadata) in objects.items()
00274             ])
00275         )
00276         # convert to tree to XML via adding it to the 'getResponse' node
00277         tree.add_to_node(response_node)
00278         return out
00279 
00280     def change(self, inpayload):
00281         """ Make changes in objects, if given conditions are met, return which change was successful.
00282 
00283         change(inpayload)
00284 
00285         'inpayload' is an XMLNode containing the change requests.
00286         """
00287         # get the grandchild of the root node, which is the 'changeRequestList'
00288         requests_node = inpayload.Child().Child()
00289         # get all the requests:
00290         request_nodes = [requests_node.Child(i) for i in range(requests_node.Size())]
00291         # prepare the dictionary which will hold the requests
00292         changes = {}
00293         # for each request:
00294         for request_node in request_nodes:
00295             # get all the data: changeID, ID, changeType, section, property, value, conditions
00296             changeID, change = node_to_data(request_node,
00297                 ['changeID', 'ID', 'changeType', 'section', 'property', 'value'])
00298             # get the conditionList node
00299             conditionList_node = request_node.Get('conditionList')
00300             # get the nodes of all the conditions
00301             condition_nodes = [conditionList_node.Child(i) for i in range(conditionList_node.Size())]
00302             # for each condition get all the data: conditionID, conditionType, section, property, value
00303             conditions = dict([
00304                 node_to_data(condition_node, ['conditionID', 'conditionType', 'section', 'property', 'value'])
00305                     for condition_node in condition_nodes
00306             ])
00307             # append the conditions dict to the chnage request
00308             change.append(conditions)
00309             # put this change request into the changes dictionary
00310             changes[changeID] = change
00311         # prepare the response payload
00312         out = self._new_soap_payload()
00313         # create the 'changeResponse' node
00314         response_node = out.NewChild('ahash:changeResponse')
00315         # call the business logic class
00316         try:
00317             resp = self.ahash.change(changes)
00318         except:
00319             error_node = response_node.NewChild('ahash:error')
00320             error_node.Set('ahash is not ready')
00321             return out
00322         # create an XMLTree for the response
00323         tree = XMLTree(from_tree = 
00324             ('ahash:changeResponseList', [
00325                 ('ahash:changeResponseElement', [ # for each change
00326                     ('ahash:changeID', changeID),
00327                     ('ahash:success', success),
00328                     ('ahash:conditionID', conditionID)
00329                 ]) for (changeID, (success, conditionID)) in resp.items()
00330             ])
00331         )
00332         # add the XMLTree to the XMLNode
00333         tree.add_to_node(response_node)
00334         return out
00335 
00336     def RegistrationCollector(self, doc):
00337         regentry = arc.XMLNode('<RegEntry />')
00338         regentry.NewChild('SrcAdv').NewChild('Type').Set(ahash_servicetype)
00339         #Place the document into the doc attribute
00340         doc.Replace(regentry)
00341         return True
00342 
00343     def GetAdditionalLocalInformation(self, service_node):
00344         service_node.NewChild('Type').Set(ahash_servicetype)
00345 
00346