cleanup
[sfa.git] / sfa / openstack / nova_driver.py
1 import time
2 import datetime
3
4 from sfa.util.faults import MissingSfaInfo, UnknownSfaType, \
5     RecordNotFound, SfaNotImplemented, SfaInvalidArgument
6
7 from sfa.util.sfalogging import logger
8 from sfa.util.defaultdict import defaultdict
9 from sfa.util.sfatime import utcparse, datetime_to_string, datetime_to_epoch
10 from sfa.util.xrn import Xrn, hrn_to_urn, get_leaf 
11 from sfa.openstack.osxrn import OSXrn, hrn_to_os_slicename, hrn_to_os_tenant_name
12 from sfa.util.cache import Cache
13 from sfa.trust.credential import Credential
14 # used to be used in get_ticket
15 #from sfa.trust.sfaticket import SfaTicket
16
17 from sfa.rspecs.version_manager import VersionManager
18 from sfa.rspecs.rspec import RSpec
19
20 # the driver interface, mostly provides default behaviours
21 from sfa.managers.driver import Driver
22 from sfa.openstack.shell import Shell
23 from sfa.openstack.osaggregate import OSAggregate
24 from sfa.planetlab.plslices import PlSlices
25
26 def list_to_dict(recs, key):
27     """
28     convert a list of dictionaries into a dictionary keyed on the 
29     specified dictionary key 
30     """
31     return dict ( [ (rec[key],rec) for rec in recs ] )
32
33 #
34 # PlShell is just an xmlrpc serverproxy where methods
35 # can be sent as-is; it takes care of authentication
36 # from the global config
37
38 class NovaDriver(Driver):
39
40     # the cache instance is a class member so it survives across incoming requests
41     cache = None
42
43     def __init__ (self, config):
44         Driver.__init__(self, config)
45         self.shell = Shell(config=config)
46         self.cache=None
47         if config.SFA_AGGREGATE_CACHING:
48             if NovaDriver.cache is None:
49                 NovaDriver.cache = Cache()
50             self.cache = NovaDriver.cache
51  
52     ########################################
53     ########## registry oriented
54     ########################################
55
56     ########## disabled users 
57     def is_enabled (self, record):
58         # all records are enabled
59         return True
60
61     def augment_records_with_testbed_info (self, sfa_records):
62         return self.fill_record_info (sfa_records)
63
64     ########## 
65     def register (self, sfa_record, hrn, pub_key):
66         
67         if sfa_record['type'] == 'slice':
68             record = self.register_slice(sfa_record, hrn)         
69         elif sfa_record['type'] == 'user':
70             record = self.register_user(sfa_record, hrn, pub_key)
71         elif sfa_record['type'].startswith('authority'): 
72             record = self.register_authority(sfa_record, hrn)
73         # We should be returning the records id as a pointer but
74         # this is a string and the records table expects this to be an 
75         # int.
76         #return record.id
77         return -1
78
79     def register_slice(self, sfa_record, hrn):
80         # add slice description, name, researchers, PI
81         name = hrn_to_os_tenant_name(hrn)
82         description = sfa_record.get('description', None)
83         self.shell.auth_manager.tenants.create(name, description)
84         tenant = self.shell.auth_manager.tenants.find(name=name)
85         auth_hrn = OSXrn(xrn=hrn, type='slice').get_authority_hrn()
86         parent_tenant_name = OSXrn(xrn=auth_hrn, type='slice').get_tenant_name()
87         parent_tenant = self.shell.auth_manager.tenants.find(name=parent_tenant_name)
88         researchers = sfa_record.get('researchers', [])
89         for researcher in researchers:
90             name = Xrn(researcher).get_leaf()
91             user = self.shell.auth_manager.users.find(name=name)
92             self.shell.auth_manager.roles.add_user_role(user, 'Member', tenant)
93             self.shell.auth_manager.roles.add_user_role(user, 'user', tenant)
94             
95
96         pis = sfa_record.get('pis', [])
97         for pi in pis:
98             name = Xrn(pi).get_leaf()
99             user = self.shell.auth_manager.users.find(name=name)
100             self.shell.auth_manager.roles.add_user_role(user, 'pi', tenant)
101             self.shell.auth_manager.roles.add_user_role(user, 'pi', parent_tenant)
102
103         return tenant
104        
105     def register_user(self, sfa_record, hrn, pub_key):
106         # add person roles, projects and keys
107         email = sfa_record.get('email', None)
108         xrn = Xrn(hrn)
109         name = xrn.get_leaf()
110         auth_hrn = xrn.get_authority_hrn()
111         tenant_name = OSXrn(xrn=auth_hrn, type='authority').get_tenant_name()  
112         tenant = self.shell.auth_manager.tenants.find(name=tenant_name)  
113         self.shell.auth_manager.users.create(name, email=email, tenant_id=tenant.id)
114         user = self.shell.auth_manager.users.find(name=name)
115         slices = sfa_records.get('slices', [])
116         for slice in projects:
117             slice_tenant_name = OSXrn(xrn=slice, type='slice').get_tenant_name()
118             slice_tenant = self.shell.auth_manager.tenants.find(name=slice_tenant_name)
119             self.shell.auth_manager.roles.add_user_role(user, slice_tenant, 'user')
120         keys = sfa_records.get('keys', [])
121         for key in keys:
122             keyname = OSXrn(xrn=hrn, type='user').get_slicename()
123             self.shell.nova_client.keypairs.create(keyname, key)
124         return user
125
126     def register_authority(self, sfa_record, hrn):
127         name = OSXrn(xrn=hrn, type='authority').get_tenant_name()
128         self.shell.auth_manager.tenants.create(name, sfa_record.get('description', ''))
129         tenant = self.shell.auth_manager.tenants.find(name=name)
130         return tenant
131         
132         
133     ##########
134     # xxx actually old_sfa_record comes filled with plc stuff as well in the original code
135     def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
136         type = new_sfa_record['type'] 
137         
138         # new_key implemented for users only
139         if new_key and type not in [ 'user' ]:
140             raise UnknownSfaType(type)
141
142         elif type == "slice":
143             # can update project manager and description
144             name = hrn_to_os_slicename(hrn)
145             researchers = sfa_record.get('researchers', [])
146             pis = sfa_record.get('pis', [])
147             project_manager = None
148             description = sfa_record.get('description', None)
149             if pis:
150                 project_manager = Xrn(pis[0], 'user').get_leaf()
151             elif researchers:
152                 project_manager = Xrn(researchers[0], 'user').get_leaf()
153             self.shell.auth_manager.modify_project(name, project_manager, description)
154
155         elif type == "user":
156             # can techinally update access_key and secret_key,
157             # but that is not in our scope, so we do nothing.  
158             pass
159         return True
160         
161
162     ##########
163     def remove (self, sfa_record):
164         type=sfa_record['type']
165         if type == 'user':
166             name = Xrn(sfa_record['hrn']).get_leaf()     
167             if self.shell.auth_manager.get_user(name):
168                 self.shell.auth_manager.delete_user(name)
169         elif type == 'slice':
170             name = hrn_to_os_slicename(sfa_record['hrn'])     
171             if self.shell.auth_manager.get_project(name):
172                 self.shell.auth_manager.delete_project(name)
173         return True
174
175
176     ####################
177     def fill_record_info(self, records):
178         """
179         Given a (list of) SFA record, fill in the PLC specific 
180         and SFA specific fields in the record. 
181         """
182         if not isinstance(records, list):
183             records = [records]
184
185         for record in records:
186             if record['type'] == 'user':
187                 record = self.fill_user_record_info(record)
188             elif record['type'] == 'slice':
189                 record = self.fill_slice_record_info(record)
190             elif record['type'].startswith('authority'):
191                 record = self.fill_auth_record_info(record)
192             else:
193                 continue
194             record['geni_urn'] = hrn_to_urn(record['hrn'], record['type'])
195             record['geni_certificate'] = record['gid'] 
196             #if os_record.created_at is not None:    
197             #    record['date_created'] = datetime_to_string(utcparse(os_record.created_at))
198             #if os_record.updated_at is not None:
199             #    record['last_updated'] = datetime_to_string(utcparse(os_record.updated_at))
200  
201         return records
202
203     def fill_user_record_info(self, record):
204         xrn = Xrn(record['hrn'])
205         name = xrn.get_leaf()
206         record['name'] = name
207         user = self.shell.auth_manager.users.find(name=name)
208         record['email'] = user.email
209         tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
210         slices = []
211         all_tenants = self.shell.auth_manager.tenants.list()
212         for tmp_tenant in all_tenants:
213             if tmp_tenant.name.startswith(tenant.name +"."):
214                 for tmp_user in tmp_tenant.list_users():
215                     if tmp_user.name == user.name:
216                         slice_hrn = ".".join([self.hrn, tmp_tenant.name]) 
217                         slices.append(slice_hrn)   
218         record['slices'] = slices
219         roles = self.shell.auth_manager.roles.roles_for_user(user, tenant)
220         record['roles'] = [role.name for role in roles] 
221         keys = self.shell.nova_manager.keypairs.findall(name=record['hrn'])
222         record['keys'] = [key.public_key for key in keys]
223         return record
224
225     def fill_slice_record_info(self, record):
226         tenant_name = hrn_to_os_tenant_name(record['hrn'])
227         tenant = self.shell.auth_manager.tenants.find(name=tenant_name)
228         parent_tenant_name = OSXrn(xrn=tenant_name).get_authority_hrn()
229         parent_tenant = self.shell.auth_manager.tenants.find(name=parent_tenant_name)
230         researchers = []
231         pis = []
232
233         # look for users and pis in slice tenant
234         for user in tenant.list_users():
235             for role in self.shell.auth_manager.roles.roles_for_user(user, tenant):
236                 if role.name.lower() == 'pi':
237                     user_tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
238                     hrn = ".".join([self.hrn, user_tenant.name, user.name])
239                     pis.append(hrn)
240                 elif role.name.lower() in ['user', 'member']:
241                     user_tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
242                     hrn = ".".join([self.hrn, user_tenant.name, user.name])
243                     researchers.append(hrn)
244
245         # look for pis in the slice's parent (site/organization) tenant
246         for user in parent_tenant.list_users():
247             for role in self.shell.auth_manager.roles.roles_for_user(user, parent_tenant):
248                 if role.name.lower() == 'pi':
249                     user_tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
250                     hrn = ".".join([self.hrn, user_tenant.name, user.name])
251                     pis.append(hrn)
252         record['name'] = tenant_name
253         record['description'] = tenant.description
254         record['PI'] = pis
255         if pis:
256             record['geni_creator'] = pis[0]
257         else:
258             record['geni_creator'] = None
259         record['researcher'] = researchers
260         return record
261
262     def fill_auth_record_info(self, record):
263         tenant_name = hrn_to_os_tenant_name(record['hrn'])
264         tenant = self.shell.auth_manager.tenants.find(name=tenant_name)
265         researchers = []
266         pis = []
267
268         # look for users and pis in slice tenant
269         for user in tenant.list_users():
270             for role in self.shell.auth_manager.roles.roles_for_user(user, tenant):
271                 hrn = ".".join([self.hrn, tenant.name, user.name])
272                 if role.name.lower() == 'pi':
273                     pis.append(hrn)
274                 elif role.name.lower() in ['user', 'member']:
275                     researchers.append(hrn)
276
277         # look for slices
278         slices = []
279         all_tenants = self.shell.auth_manager.tenants.list() 
280         for tmp_tenant in all_tenants:
281             if tmp_tenant.name.startswith(tenant.name+"."):
282                 slices.append(".".join([self.hrn, tmp_tenant.name])) 
283
284         record['name'] = tenant_name
285         record['description'] = tenant.description
286         record['PI'] = pis
287         record['enabled'] = tenant.enabled
288         record['researchers'] = researchers
289         record['slices'] = slices
290         return record
291
292     ####################
293     # plcapi works by changes, compute what needs to be added/deleted
294     def update_relation (self, subject_type, target_type, subject_id, target_ids):
295         # hard-wire the code for slice/user for now, could be smarter if needed
296         if subject_type =='slice' and target_type == 'user':
297             subject=self.shell.project_get(subject_id)[0]
298             current_target_ids = [user.name for user in subject.members]
299             add_target_ids = list ( set (target_ids).difference(current_target_ids))
300             del_target_ids = list ( set (current_target_ids).difference(target_ids))
301             logger.debug ("subject_id = %s (type=%s)"%(subject_id,type(subject_id)))
302             for target_id in add_target_ids:
303                 self.shell.project_add_member(target_id,subject_id)
304                 logger.debug ("add_target_id = %s (type=%s)"%(target_id,type(target_id)))
305             for target_id in del_target_ids:
306                 logger.debug ("del_target_id = %s (type=%s)"%(target_id,type(target_id)))
307                 self.shell.project_remove_member(target_id, subject_id)
308         else:
309             logger.info('unexpected relation to maintain, %s -> %s'%(subject_type,target_type))
310
311         
312     ########################################
313     ########## aggregate oriented
314     ########################################
315
316     def testbed_name (self): return "openstack"
317
318     def aggregate_version (self):
319         return {}
320
321     def list_slices (self, creds, options):
322         # get data from db
323         instance_urns = []
324         instances = self.shell.nova_manager.servers.findall()
325         for instance in instances:
326             if instance.name not in instance_urns:
327                 instance_urns.append(OSXrn(instance.name, type='slice').urn)
328         return instance_urns
329         
330     # first 2 args are None in case of resource discovery
331     def list_resources (self, creds, version, options):
332         aggregate = OSAggregate(self)
333         rspec =  aggregate.list_resources(version=version, options=options)
334         return rspec
335
336     def describe(self, creds, urns, version, options):
337         aggregate = OSAggregate(self)
338         return aggregate.describe(urns, version=version, options=options)
339     
340     def status (self, urns):
341         aggregate = OSAggregate(self)
342         desc =  aggregate.describe(urns, version=version, options=options)
343         return desc['geni_slivers']
344
345     def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, users, options):
346
347         aggregate = OSAggregate(self)
348
349         # assume first user is the caller and use their context
350         # for the ec2/euca api connection. Also, use the first users
351         # key as the project key.
352         key_name = None
353         if len(users) > 1:
354             key_name = aggregate.create_instance_key(slice_hrn, users[0])
355
356         # collect public keys
357         pubkeys = []
358         for user in users:
359             pubkeys.extend(user['keys'])
360            
361         rspec = RSpec(rspec_string)
362         instance_name = hrn_to_os_slicename(slice_hrn)
363         tenant_name = OSXrn(xrn=slice_hrn, type='slice').get_tenant_name()
364         aggregate.run_instances(instance_name, tenant_name, rspec_string, key_name, pubkeys)    
365    
366         return aggregate.describe(slice_xrn=slice_urn, version=rspec.version)
367
368     def delete_sliver (self, slice_urn, slice_hrn, creds, options):
369         aggregate = OSAggregate(self)
370         tenant_name = OSXrn(xrn=slice_hrn, type='slice').get_tenant_name()
371         project_name = hrn_to_os_slicename(slice_hrn)
372         return aggregate.delete_instances(project_name, tenant_name)   
373
374     def update_sliver(self, slice_urn, slice_hrn, rspec, creds, options):
375         name = hrn_to_os_slicename(slice_hrn)
376         tenant_name = OSXrn(xrn=slice_hrn, type='slice').get_tenant_name()
377         aggregate = OSAggregate(self)
378         return aggregate.update_instances(name)
379     
380     def renew_sliver (self, slice_urn, slice_hrn, creds, expiration_time, options):
381         return True
382
383     def start_slice (self, slice_urn, slice_hrn, creds):
384         return 1
385
386     def stop_slice (self, slice_urn, slice_hrn, creds):
387         tenant_name = OSXrn(xrn=slice_hrn, type='slice').get_tenant_name()
388         name = OSXrn(xrn=slice_urn).name
389         aggregate = OSAggregate(self)
390         return aggregate.stop_instances(name, tenant_name) 
391
392     def reset_slice (self, slice_urn, slice_hrn, creds):
393         raise SfaNotImplemented ("reset_slice not available at this interface")
394     
395     # xxx this code is quite old and has not run for ages
396     # it is obviously totally broken and needs a rewrite
397     def get_ticket (self, slice_urn, slice_hrn, creds, rspec_string, options):
398         raise SfaNotImplemented,"OpenStackDriver.get_ticket needs a rewrite"
399 # please keep this code for future reference
400 #        slices = PlSlices(self)
401 #        peer = slices.get_peer(slice_hrn)
402 #        sfa_peer = slices.get_sfa_peer(slice_hrn)
403 #    
404 #        # get the slice record
405 #        credential = api.getCredential()
406 #        interface = api.registries[api.hrn]
407 #        registry = api.server_proxy(interface, credential)
408 #        records = registry.Resolve(xrn, credential)
409 #    
410 #        # make sure we get a local slice record
411 #        record = None
412 #        for tmp_record in records:
413 #            if tmp_record['type'] == 'slice' and \
414 #               not tmp_record['peer_authority']:
415 #    #Error (E0602, GetTicket): Undefined variable 'SliceRecord'
416 #                slice_record = SliceRecord(dict=tmp_record)
417 #        if not record:
418 #            raise RecordNotFound(slice_hrn)
419 #        
420 #        # similar to CreateSliver, we must verify that the required records exist
421 #        # at this aggregate before we can issue a ticket
422 #        # parse rspec
423 #        rspec = RSpec(rspec_string)
424 #        requested_attributes = rspec.version.get_slice_attributes()
425 #    
426 #        # ensure site record exists
427 #        site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer)
428 #        # ensure slice record exists
429 #        slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer)
430 #        # ensure person records exists
431 #    # xxx users is undefined in this context
432 #        persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer)
433 #        # ensure slice attributes exists
434 #        slices.verify_slice_attributes(slice, requested_attributes)
435 #        
436 #        # get sliver info
437 #        slivers = slices.get_slivers(slice_hrn)
438 #    
439 #        if not slivers:
440 #            raise SliverDoesNotExist(slice_hrn)
441 #    
442 #        # get initscripts
443 #        initscripts = []
444 #        data = {
445 #            'timestamp': int(time.time()),
446 #            'initscripts': initscripts,
447 #            'slivers': slivers
448 #        }
449 #    
450 #        # create the ticket
451 #        object_gid = record.get_gid_object()
452 #        new_ticket = SfaTicket(subject = object_gid.get_subject())
453 #        new_ticket.set_gid_caller(api.auth.client_gid)
454 #        new_ticket.set_gid_object(object_gid)
455 #        new_ticket.set_issuer(key=api.key, subject=self.hrn)
456 #        new_ticket.set_pubkey(object_gid.get_pubkey())
457 #        new_ticket.set_attributes(data)
458 #        new_ticket.set_rspec(rspec)
459 #        #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
460 #        new_ticket.encode()
461 #        new_ticket.sign()
462 #    
463 #        return new_ticket.save_to_string(save_parents=True)