with this we can use the sfa update method to renew slices
[sfa.git] / sfa / plc / api.py
1 #
2 # Geniwrapper XML-RPC and SOAP interfaces
3 #
4 ### $Id$
5 ### $URL$
6 #
7
8 import sys
9 import os
10 import traceback
11 import string
12 import xmlrpclib
13
14 from sfa.trust.auth import Auth
15 from sfa.util.config import *
16 from sfa.util.faults import *
17 from sfa.util.debug import *
18 from sfa.trust.rights import *
19 from sfa.trust.credential import *
20 from sfa.trust.certificate import *
21 from sfa.util.misc import *
22 from sfa.util.sfalogging import *
23 from sfa.util.genitable import *
24 from sfa.util.api import *
25
26 class GeniAPI(BaseAPI):
27
28     # flat list of method names
29     import sfa.methods
30     methods = sfa.methods.all
31     
32     def __init__(self, config = "/etc/sfa/sfa_config", encoding = "utf-8", methods='sfa.methods', 
33                  peer_cert = None, interface = None, key_file = None, cert_file = None):
34         BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert,
35                          interface=interface, key_file=key_file, cert_file=cert_file)
36  
37         self.encoding = encoding
38
39         # Better just be documenting the API
40         if config is None:
41             return
42
43         # Load configuration
44         self.config = Config(config)
45         self.auth = Auth(peer_cert)
46         self.interface = interface
47         self.key_file = key_file
48         self.key = Keypair(filename=self.key_file)
49         self.cert_file = cert_file
50         self.cert = Certificate(filename=self.cert_file)
51         self.credential = None
52         
53         # Initialize the PLC shell only if SFA wraps a myPLC
54         rspec_type = self.config.get_aggregate_rspec_type()
55         if (rspec_type == 'pl' or rspec_type == 'vini'):
56             self.plshell = self.getPLCShell()
57             self.plshell_version = self.getPLCShellVersion()
58
59         self.hrn = self.config.SFA_INTERFACE_HRN
60         self.time_format = "%Y-%m-%d %H:%M:%S"
61         self.logger=get_sfa_logger()
62
63     def getPLCShell(self):
64         self.plauth = {'Username': self.config.SFA_PLC_USER,
65                        'AuthMethod': 'password',
66                        'AuthString': self.config.SFA_PLC_PASSWORD}
67         try:
68             self.plshell_type = 'direct'
69             import PLC.Shell
70             shell = PLC.Shell.Shell(globals = globals())
71             shell.AuthCheck(self.plauth)
72             return shell
73         except ImportError:
74             self.plshell_type = 'xmlrpc' 
75             # connect via xmlrpc
76             url = self.config.SFA_PLC_URL
77             shell = xmlrpclib.Server(url, verbose = 0, allow_none = True)
78             shell.AuthCheck(self.plauth)
79             return shell
80
81     def getPLCShellVersion(self):
82         # We need to figure out what version of PLCAPI we are talking to.
83         # Some calls we need to make later will be different depending on
84         # the api version. 
85         try:
86             # This is probably a bad way to determine api versions
87             # but its easy and will work for now. Lets try to make 
88             # a call that only exists is PLCAPI.4.3. If it fails, we
89             # can assume the api version is 4.2
90             self.plshell.GetTagTypes(self.plauth)
91             return '4.3'
92         except:
93             return '4.2'
94             
95
96     def getCredential(self):
97         if self.interface in ['registry']:
98             return self.getCredentialFromLocalRegistry()
99         else:
100             return self.getCredentialFromRegistry()
101     
102     def getCredentialFromRegistry(self):
103         """ 
104         Get our credential from a remote registry using a geniclient connection
105         """
106         type = 'authority'
107         path = self.config.SFA_DATA_DIR
108         filename = ".".join([self.interface, self.hrn, type, "cred"])
109         cred_filename = path + os.sep + filename
110         try:
111             credential = Credential(filename = cred_filename)
112             return credential.save_to_string(save_parents=True)
113         except IOError:
114             from sfa.server.registry import Registries
115             registries = Registries(self)
116             registry = registries[self.hrn]
117             cert_string=self.cert.save_to_string(save_parents=True)
118             # get self credential
119             arg_list = [cert_string,type,self.hrn]
120             request_hash=self.key.compute_hash(arg_list)
121             self_cred = registry.get_self_credential(cert_string, type, self.hrn, request_hash)
122             # get credential
123             arg_list = [self_cred,type,self.hrn]
124             request_hash=self.key.compute_hash(arg_list)
125             cred = registry.get_credential(self_cred, type, self.hrn, request_hash)
126             
127             # save cred to file
128             Credential(string=cred).save_to_file(cred_filename, save_parents=True)
129             return cred
130
131     def getCredentialFromLocalRegistry(self):
132         """
133         Get our current credential directly from the local registry.
134         """
135
136         hrn = self.hrn
137         auth_hrn = self.auth.get_authority(hrn)
138     
139         # is this a root or sub authority
140         if not auth_hrn or hrn == self.config.SFA_INTERFACE_HRN:
141             auth_hrn = hrn
142         auth_info = self.auth.get_auth_info(auth_hrn)
143         table = GeniTable()
144         records = table.findObjects(hrn)
145         if not records:
146             raise RecordNotFound
147         record = records[0]
148         type = record['type']
149         object_gid = record.get_gid_object()
150         new_cred = Credential(subject = object_gid.get_subject())
151         new_cred.set_gid_caller(object_gid)
152         new_cred.set_gid_object(object_gid)
153         new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
154         new_cred.set_pubkey(object_gid.get_pubkey())
155         r1 = determine_rights(type, hrn)
156         new_cred.set_privileges(r1)
157
158         auth_kind = "authority,ma,sa"
159
160         new_cred.set_parent(self.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
161
162         new_cred.encode()
163         new_cred.sign()
164
165         return new_cred.save_to_string(save_parents=True)
166    
167
168     def loadCredential (self):
169         """
170         Attempt to load credential from file if it exists. If it doesnt get
171         credential from registry.
172         """
173
174         # see if this file exists
175         # XX This is really the aggregate's credential. Using this is easier than getting
176         # the registry's credential from iteslf (ssl errors).   
177         ma_cred_filename = self.config.SFA_DATA_DIR + os.sep + self.interface + self.hrn + ".ma.cred"
178         try:
179             self.credential = Credential(filename = ma_cred_filename)
180         except IOError:
181             self.credential = self.getCredentialFromRegistry()
182
183     ##
184     # Convert geni fields to PLC fields for use when registering up updating
185     # registry record in the PLC database
186     #
187     # @param type type of record (user, slice, ...)
188     # @param hrn human readable name
189     # @param geni_fields dictionary of geni fields
190     # @param pl_fields dictionary of PLC fields (output)
191
192     def geni_fields_to_pl_fields(self, type, hrn, record):
193
194         def convert_ints(tmpdict, int_fields):
195             for field in int_fields:
196                 if field in tmpdict:
197                     tmpdict[field] = int(tmpdict[field])
198
199         pl_record = {}
200         #for field in record:
201         #    pl_record[field] = record[field]
202  
203         if type == "slice":
204             if not "instantiation" in pl_record:
205                 pl_record["instantiation"] = "plc-instantiated"
206             pl_record["name"] = hrn_to_pl_slicename(hrn)
207             if "url" in record:
208                pl_record["url"] = record["url"]
209             if "description" in record:
210                 pl_record["description"] = record["description"]
211             if "expires" in record:
212                 pl_record["expires"] = int(record["expires"])
213
214         elif type == "node":
215             if not "hostname" in pl_record:
216                 if not "hostname" in record:
217                     raise MissingGeniInfo("hostname")
218                 pl_record["hostname"] = record["hostname"]
219             if not "model" in pl_record:
220                 pl_record["model"] = "geni"
221
222         elif type == "authority":
223             pl_record["login_base"] = hrn_to_pl_login_base(hrn)
224
225             if not "name" in pl_record:
226                 pl_record["name"] = hrn
227
228             if not "abbreviated_name" in pl_record:
229                 pl_record["abbreviated_name"] = hrn
230
231             if not "enabled" in pl_record:
232                 pl_record["enabled"] = True
233
234             if not "is_public" in pl_record:
235                 pl_record["is_public"] = True
236
237         return pl_record
238
239     def fill_record_pl_info(self, record):
240         """
241         Fill in the planetlab specific fields of a Geni record. This
242         involves calling the appropriate PLC method to retrieve the 
243         database record for the object.
244         
245         PLC data is filled into the pl_info field of the record.
246     
247         @param record: record to fill in field (in/out param)     
248         """
249         type = record['type']
250         pointer = record['pointer']
251         auth_hrn = self.hrn
252         login_base = ''
253         # records with pointer==-1 do not have plc info associated with them.
254         # for example, the top level authority records which are
255         # authorities, but not PL "sites"
256         if pointer == -1:
257             record.update({})
258             return
259
260         if (type in ["authority"]):
261             pl_res = self.plshell.GetSites(self.plauth, [pointer])
262         elif (type == "slice"):
263             pl_res = self.plshell.GetSlices(self.plauth, [pointer])
264         elif (type == "user"):
265             pl_res = self.plshell.GetPersons(self.plauth, [pointer])
266         elif (type == "node"):
267             pl_res = self.plshell.GetNodes(self.plauth, [pointer])
268         else:
269             raise UnknownGeniType(type)
270         
271         if not pl_res:
272             raise PlanetLabRecordDoesNotExist(record['hrn'])
273
274         # convert ids to hrns
275         pl_record = pl_res[0]
276         if 'site_id' in pl_record:
277             sites = self.plshell.GetSites(self.plauth, pl_record['site_id'], ['login_base'])
278             site = sites[0]
279             login_base = site['login_base']
280             pl_record['site'] = ".".join([auth_hrn, login_base])
281         if 'person_ids' in pl_record:
282             persons =  self.plshell.GetPersons(self.plauth, pl_record['person_ids'], ['email'])
283             emails = [person['email'] for person in persons]
284             usernames = [email.split('@')[0] for email in emails]
285             person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames]
286             pl_record['persons'] = person_hrns 
287         if 'slice_ids' in pl_record:
288             slices = self.plshell.GetSlices(self.plauth, pl_record['slice_ids'], ['name'])
289             slicenames = [slice['name'] for slice in slices]
290             slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
291             pl_record['slices'] = slice_hrns
292         if 'node_ids' in pl_record:
293             nodes = self.plshell.GetNodes(self.plauth, pl_record['node_ids'], ['hostname'])
294             hostnames = [node['hostname'] for node in nodes]
295             node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
296             pl_record['nodes'] = node_hrns
297         if 'site_ids' in pl_record:
298             sites = self.plshell.GetSites(self.plauth, pl_record['site_ids'], ['login_base'])
299             login_bases = [site['login_base'] for site in sites]
300             site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases]
301             pl_record['sites'] = site_hrns
302         if 'key_ids' in pl_record:
303             keys = self.plshell.GetKeys(self.plauth, pl_record['key_ids'])
304             pubkeys = []
305             if keys:
306                 pubkeys = [key['key'] for key in keys]
307             pl_record['keys'] = pubkeys     
308
309         record.update(pl_record)
310
311
312
313     def fill_record_geni_info(self, record):
314         geni_info = {}
315         type = record['type']
316         table = GeniTable()
317         if (type == "slice"):
318             person_ids = record.get("person_ids", [])
319             persons = table.find({'type': 'user', 'pointer': person_ids})
320             researchers = [person['hrn'] for person in persons]
321             geni_info['researcher'] = researchers
322
323         elif (type == "authority"):
324             person_ids = record.get("person_ids", [])
325             persons = table.find({'type': 'user', 'pointer': person_ids})
326             persons_dict = {}
327             for person in persons:
328                 persons_dict[person['pointer']] = person 
329             pl_persons = self.plshell.GetPersons(self.plauth, person_ids, ['person_id', 'roles'])
330             pis, techs, admins = [], [], []
331             for person in pl_persons:
332                 pointer = person['person_id']
333                 
334                 if pointer not in persons_dict:
335                     # this means there is not sfa record for this user
336                     continue    
337                 hrn = persons_dict[pointer]['hrn']    
338                 if 'pi' in person['roles']:
339                     pis.append(hrn)
340                 if 'tech' in person['roles']:
341                     techs.append(hrn)
342                 if 'admin' in person['roles']:
343                     admins.append(hrn)
344             
345             geni_info['PI'] = pis
346             geni_info['operator'] = techs
347             geni_info['owner'] = admins
348             # xxx TODO: OrganizationName
349
350         elif (type == "node"):
351             geni_info['dns'] = record.get("hostname", "")
352             # xxx TODO: URI, LatLong, IP, DNS
353     
354         elif (type == "user"):
355             geni_info['email'] = record.get("email", "")
356             # xxx TODO: PostalAddress, Phone
357
358         record.update(geni_info)
359
360     def fill_record_info(self, record):
361         """
362         Given a geni record, fill in the PLC specific and Geni specific
363         fields in the record. 
364         """
365         self.fill_record_pl_info(record)
366         self.fill_record_geni_info(record)
367
368     def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc):
369         # get a list of the HRNs tht are members of the old and new records
370         if oldRecord:
371             oldList = oldRecord.get(listName, [])
372         else:
373             oldList = []     
374         newList = record.get(listName, [])
375
376         # if the lists are the same, then we don't have to update anything
377         if (oldList == newList):
378             return
379
380         # build a list of the new person ids, by looking up each person to get
381         # their pointer
382         newIdList = []
383         table = GeniTable()
384         records = table.find({'type': 'user', 'hrn': newList})
385         for rec in records:
386             newIdList.append(rec['pointer'])
387
388         # build a list of the old person ids from the person_ids field 
389         if oldRecord:
390             oldIdList = oldRecord.get("person_ids", [])
391             containerId = oldRecord.get_pointer()
392         else:
393             # if oldRecord==None, then we are doing a Register, instead of an
394             # update.
395             oldIdList = []
396             containerId = record.get_pointer()
397
398     # add people who are in the new list, but not the oldList
399         for personId in newIdList:
400             if not (personId in oldIdList):
401                 addFunc(self.plauth, personId, containerId)
402
403         # remove people who are in the old list, but not the new list
404         for personId in oldIdList:
405             if not (personId in newIdList):
406                 delFunc(self.plauth, personId, containerId)
407
408     def update_membership(self, oldRecord, record):
409         if record.type == "slice":
410             self.update_membership_list(oldRecord, record, 'researcher',
411                                         self.plshell.AddPersonToSlice,
412                                         self.plshell.DeletePersonFromSlice)
413         elif record.type == "authority":
414             # xxx TODO
415             pass
416
417
418
419 class ComponentAPI(BaseAPI):
420
421     def __init__(self, config = "/etc/sfa/sfa_config", encoding = "utf-8", methods='sfa.methods',
422                  peer_cert = None, interface = None, key_file = None, cert_file = None):
423
424         BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert,
425                          interface=interface, key_file=key_file, cert_file=cert_file)
426         self.encoding = encoding
427
428         # Better just be documenting the API
429         if config is None:
430             return
431
432         self.nodemanager = self.getNodeManagerShell()
433
434     def getNodeManagerShell(self):
435         # just import the nm api source
436         sys.path.append('/usr/share/NodeManager')
437         import api_calls
438         return api_calls
439
440
441     def sliver_exists(self):
442         sliver_dict = self.nodemanager.GetXIDs()
443         if slicename in sliver_dict.keys():
444             return True
445         else:
446             return False