parse timestamps correctly
[sfa.git] / sfa / storage / record.py
1 ##
2 # Implements support for SFA records
3 #
4 # TODO: Use existing PLC database methods? or keep this separate?
5 ##
6
7 from types import StringTypes
8 from sfa.trust.gid import GID
9 from sfa.storage.parameter import Parameter
10 from sfa.util.xrn import get_authority
11 from sfa.storage.row import Row
12 from sfa.util.xml import XML 
13 from sfa.util.sfalogging import logger
14 from sfa.util.sfatime import utcparse, datetime_to_string
15
16 class SfaRecord(Row):
17     """ 
18     The SfaRecord class implements an SFA Record. A SfaRecord is a tuple
19     (Hrn, GID, Type, Info).
20  
21     Hrn specifies the Human Readable Name of the object
22     GID is the GID of the object
23     Type is user | authority | slice | component
24  
25     Info is comprised of the following sub-fields
26            pointer = a pointer to the record in the PL database
27  
28     The pointer is interpreted depending on the type of the record. For example,
29     if the type=="user", then pointer is assumed to be a person_id that indexes
30     into the persons table.
31  
32     A given HRN may have more than one record, provided that the records are
33     of different types.
34     """
35
36 #    table_name = 'sfa'
37 #    primary_key = 'record_id'
38
39     ### the wsdl generator assumes this is named 'fields'
40     internal_fields = {
41         'record_id': Parameter(int, "An id that uniquely identifies this record", ro=True),
42         'pointer': Parameter(int, "An id that uniquely identifies this record in an external database")
43     }
44
45     fields = {
46         'authority': Parameter(str, "The authority for this record"),
47         'peer_authority': Parameter(str, "The peer authority for this record"),
48         'hrn': Parameter(str, "Human readable name of object"),
49         'gid': Parameter(str, "GID of the object"),
50         'type': Parameter(str, "Record type"),
51         'last_updated': Parameter(int, "Date and time of last update", ro=True),
52         'date_created': Parameter(int, "Date and time this record was created", ro=True),
53     }
54     all_fields = dict(fields.items() + internal_fields.items())
55     ##
56     # Create an SFA Record
57     #
58     # @param name if !=None, assign the name of the record
59     # @param gid if !=None, assign the gid of the record
60     # @param type one of user | authority | slice | component
61     # @param pointer is a pointer to a PLC record
62     # @param dict if !=None, then fill in this record from the dictionary
63
64     def __init__(self, hrn=None, gid=None, type=None, pointer=None, authority=None, 
65                  peer_authority=None, dict=None, string=None):
66         self.dirty = True
67         self.hrn = None
68         self.gid = None
69         self.type = None
70         self.pointer = None
71         self.set_peer_auth(peer_authority)
72         self.set_authority(authority)
73         if hrn:
74             self.set_name(hrn)
75         if gid:
76             self.set_gid(gid)
77         if type:
78             self.set_type(type)
79         if pointer:
80             self.set_pointer(pointer)
81         if dict:
82             self.load_from_dict(dict)
83         if string:
84             self.load_from_string(string)
85
86
87     def validate_last_updated(self, last_updated):
88         return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
89         
90     def update(self, new_dict):
91         if isinstance(new_dict, list):
92             new_dict = new_dict[0]
93
94         # Convert any boolean strings to real bools
95         for key in new_dict:
96             if isinstance(new_dict[key], StringTypes):
97                 if new_dict[key].lower() in ["true"]:
98                     new_dict[key] = True
99                 elif new_dict[key].lower() in ["false"]:
100                     new_dict[key] = False
101         dict.update(self, new_dict)
102
103     ##
104     # Set the name of the record
105     #
106     # @param hrn is a string containing the HRN
107
108     def set_name(self, hrn):
109         """
110         Set the name of the record
111         """
112         self.hrn = hrn
113         self['hrn'] = hrn
114         self.dirty = True
115
116     def set_authority(self, authority):
117         """
118         Set the authority
119         """
120         if not authority:
121             authority = ""
122         self.authority = authority
123         self['authority'] = authority
124         self.dirty = True    
125         
126
127     ##
128     # Set the GID of the record
129     #
130     # @param gid is a GID object or the string representation of a GID object
131
132     def set_gid(self, gid):
133         """
134         Set the GID of the record
135         """
136
137         if isinstance(gid, StringTypes):
138             self.gid = gid
139             self['gid'] = gid
140         else:
141             self.gid = gid.save_to_string(save_parents=True)
142             self['gid'] = gid.save_to_string(save_parents=True)
143         self.dirty = True
144
145     ##
146     # Set the type of the record
147     #
148     # @param type is a string: user | authority | slice | component
149
150     def set_type(self, type):
151         """
152         Set the type of the record
153         """
154         self.type = type
155         self['type'] = type
156         self.dirty = True
157
158     ##
159     # Set the pointer of the record
160     #
161     # @param pointer is an integer containing the ID of a PLC record
162
163     def set_pointer(self, pointer):
164         """
165         Set the pointer of the record
166         """
167         self.pointer = pointer
168         self['pointer'] = pointer
169         self.dirty = True
170
171
172     def set_peer_auth(self, peer_authority):
173         self.peer_authority = peer_authority
174         self['peer_authority'] = peer_authority
175         self.dirty = True
176
177     ##
178     # Return the name (HRN) of the record
179
180     def get_name(self):
181         """
182         Return the name (HRN) of the record
183         """
184         return self.hrn
185
186     ##
187     # Return the type of the record
188
189     def get_type(self):
190         """
191         Return the type of the record
192         """
193         return self.type
194
195     ##
196     # Return the pointer of the record. The pointer is an integer that may be
197     # used to look up the record in the PLC database. The evaluation of pointer
198     # depends on the type of the record
199
200     def get_pointer(self):
201         """
202         Return the pointer of the record. The pointer is an integer that may be
203         used to look up the record in the PLC database. The evaluation of pointer
204         depends on the type of the record
205         """
206         return self.pointer
207
208     ##
209     # Return the GID of the record, in the form of a GID object
210     # TODO: not the best name for the function, because we have things called
211     # gidObjects in the Cred
212
213     def get_gid_object(self):
214         """
215         Return the GID of the record, in the form of a GID object
216         """
217         return GID(string=self.gid)
218
219     ##
220     # Returns the value of a field
221
222     def get_field(self, fieldname, default=None):
223         # sometimes records act like classes, and sometimes they act like dicts
224         try:
225             return getattr(self, fieldname)
226         except AttributeError:
227             try:
228                  return self[fieldname]
229             except KeyError:
230                  if default != None:
231                      return default
232                  else:
233                      raise
234
235     ##
236     # Returns a list of field names in this record. 
237
238     def get_field_names(self):
239         """
240         Returns a list of field names in this record.
241         """
242         return self.fields.keys()
243
244     ##
245     # Given a field name ("hrn", "gid", ...) return the value of that field.
246     #
247     # @param fieldname is the name of field to be returned
248
249     def get_field_value_string(self, fieldname):
250         """
251         Given a field name ("hrn", "gid", ...) return the value of that field.
252         """
253         if fieldname == "authority":
254             val = get_authority(self['hrn'])
255         else:
256             try:
257                 val = getattr(self, fieldname)
258             except:
259                 val = self[fieldname] 
260         if isinstance(val, str):
261             return "'" + str(val) + "'"
262         else:
263             return str(val)
264
265     ##
266     # Given a list of field names, return a list of values for those public.
267     #
268     # @param fieldnames is a list of field names
269
270     def get_field_value_strings(self, fieldnames):
271         """
272         Given a list of field names, return a list of values for those public.
273         """
274         return [ self.get_field_value_string (fieldname) for fieldname in fieldnames ]
275
276     ##
277     # Return the record in the form of a dictionary
278
279     def as_dict(self):
280         """
281         Return the record in the form of a dictionary
282         """
283         return dict(self)
284
285     ##
286     # Load the record from a dictionary
287     #
288     # @param dict dictionary to load record public from
289
290     def load_from_dict(self, dict):
291         """
292         Load the record from a dictionary 
293         """
294
295         self.set_name(dict['hrn'])
296         gidstr = dict.get("gid", None)
297         if gidstr:
298             self.set_gid(dict['gid'])
299
300         if "pointer" in dict:
301            self.set_pointer(dict['pointer'])
302
303         self.set_type(dict['type'])
304         self.update(dict)        
305     
306     ##
307     # Save the record to a string. The string contains an XML representation of
308     # the record.
309
310     def save_to_string(self):
311         """
312         Save the record to a string. The string contains an XML representation of
313         the record.
314         """
315         recorddict = self.as_dict()
316         filteredDict = dict([(key, val) for (key, val) in recorddict.iteritems() if key in self.fields.keys()])
317         xml_record = XML('<record/>')
318         xml_record.parse_dict(filteredDict)
319         str = xml_record.toxml()
320         return str
321
322     ##
323     # Load the record from a string. The string is assumed to contain an XML
324     # representation of the record.
325
326     def load_from_string(self, str):
327         """
328         Load the record from a string. The string is assumed to contain an XML
329         representation of the record.
330         """
331         #dict = xmlrpclib.loads(str)[0][0]
332
333         xml_record = XML(str)
334         self.load_from_dict(xml_record.todict())
335
336     ##
337     # Dump the record to stdout
338     #
339     # @param dump_parents if true, then the parents of the GID will be dumped
340
341     def dump_text(self, dump_parents=False):
342         """
343         Walk tree and dump records.
344         """
345         # print core fields in this order 
346         print "".join(['=' for i in range(40)])
347         print "RECORD"
348         print "    hrn:", self.get('hrn')
349         print "    type:", self.get('type')
350         print "    authority:", self.get('authority')
351         date_created = datetime_to_string(utcparse(self.get('date_created')))    
352         print "    date created:", date_created
353         last_updated = datetime_to_string(utcparse(self.get('last_updated')))    
354         print "    last updated:", last_updated
355         print "    gid:"
356         print "\t\t", self.get_gid_object().dump_string(8, dump_parents)
357        
358         # print remaining fields
359         all_fields = set(UserRecord.fields.keys() + 
360                          AuthorityRecord.fields.keys() + 
361                          SliceRecord.fields.keys() + 
362                          NodeRecord.fields.keys())                   
363         for field in self:
364             # dont print core fields
365             if field in all_fields and field not in SfaRecord.fields:    
366                 print "     %s: %s" % (field, self[field])
367
368     def dump(self, format=None, dump_parents=False):
369         if not format:
370             format = 'text'
371         else:
372             format = format.lower()
373
374         if format == 'text': 
375             self.dump_text(dump_parents)            
376         elif format == 'xml':
377             print self.save_to_string()    
378         elif format == 'summary':
379             print self.summary_string()
380         else:
381             raise Exception, "Invalid format %s" % format 
382               
383     def summary_string(self):
384         return "Record(record_id=%s, hrn=%s, type=%s, authority=%s, pointer=%s)" % \
385                 (self.get('record_id'), self.get('hrn'), self.get('type'), self.get('authority'), \
386                  self.get('pointer'))
387
388     def getdict(self):
389         return dict(self)
390    
391     def sync(self):
392         """ 
393         Sync this record with the database.
394         """ 
395         from sfa.storage.table import SfaTable
396         table = SfaTable()
397         filter = {}
398         if self.get('record_id'):
399             filter['record_id'] = self.get('record_id')
400         if self.get('hrn') and self.get('type'):
401             filter['hrn'] = self.get('hrn') 
402             filter['type'] = self.get('type')
403             if self.get('pointer'):
404                 filter['pointer'] = self.get('pointer')
405         existing_records = table.find(filter)
406         if not existing_records:
407             table.insert(self)
408         else:
409             existing_record = existing_records[0]
410             self['record_id'] = existing_record['record_id']
411             table.update(self) 
412
413     def delete(self):
414         """
415         Remove record from the database.
416         """
417         from sfa.storage.table import SfaTable
418         table = SfaTable()
419         filter = {}
420         if self.get('record_id'):
421             filter['record_id'] = self.get('record_id')
422         if self.get('hrn') and self.get('type'):
423             filter['hrn'] = self.get('hrn')
424             filter['type'] = self.get('type')
425             if self.get('pointer'):
426                 filter['pointer'] = self.get('pointer')
427         if filter:
428             existing_records = table.find(filter)
429             for record in existing_records:
430                 table.remove(record)
431
432 class UserRecord(SfaRecord):
433
434     fields = {
435         'email': Parameter(str, 'email'),
436         'first_name': Parameter(str, 'First name'),
437         'last_name': Parameter(str, 'Last name'),
438         'phone': Parameter(str, 'Phone Number'),
439         'keys': Parameter(str, 'Public key'),
440         'slices': Parameter([str], 'List of slices this user belongs to'),
441         }
442     fields.update(SfaRecord.fields)
443     
444 class SliceRecord(SfaRecord):
445     fields = {
446         'name': Parameter(str, 'Slice name'),
447         'url': Parameter(str, 'Slice url'),
448         'expires': Parameter(int, 'Date and time this slice exipres'),
449         'researcher': Parameter([str], 'List of users for this slice'),
450         'PI': Parameter([str], 'List of PIs responsible for this slice'),
451         'description': Parameter([str], 'Description of this slice'), 
452         }
453     fields.update(SfaRecord.fields)
454
455  
456 class NodeRecord(SfaRecord):
457     fields = {
458         'hostname': Parameter(str, 'This nodes dns name'),
459         'node_type': Parameter(str, 'Type of node this is'),
460         'latitude': Parameter(str, 'latitude'),
461         'longitude': Parameter(str, 'longitude'),
462         }
463     fields.update(SfaRecord.fields)
464
465
466 class AuthorityRecord(SfaRecord):
467     fields =  {
468         'name': Parameter(str, 'Name'),
469         'login_base': Parameter(str, 'login base'),
470         'enabled': Parameter(bool, 'Is this site enabled'),
471         'url': Parameter(str, 'URL'),
472         'nodes': Parameter([str], 'List of nodes at this site'),  
473         'operator': Parameter([str], 'List of operators'),
474         'researcher': Parameter([str], 'List of researchers'),
475         'PI': Parameter([str], 'List of Principal Investigators'),
476         }
477     fields.update(SfaRecord.fields)
478     
479