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