4 from sfa.util.faults import MissingSfaInfo, UnknownSfaType, \
5 RecordNotFound, SfaNotImplemented, SliverDoesNotExist, \
8 from sfa.util.sfalogging import logger
9 from sfa.util.defaultdict import defaultdict
10 from sfa.util.sfatime import utcparse, datetime_to_string, datetime_to_epoch
11 from sfa.util.xrn import Xrn, hrn_to_urn, get_leaf, urn_to_sliver_id
12 from sfa.openstack.osxrn import OSXrn, hrn_to_os_slicename, hrn_to_os_tenant_name
13 from sfa.util.cache import Cache
14 from sfa.trust.credential import Credential
15 # used to be used in get_ticket
16 #from sfa.trust.sfaticket import SfaTicket
18 from sfa.rspecs.version_manager import VersionManager
19 from sfa.rspecs.rspec import RSpec
21 # the driver interface, mostly provides default behaviours
22 from sfa.managers.driver import Driver
23 from sfa.openstack.shell import Shell
24 from sfa.openstack.osaggregate import OSAggregate
25 from sfa.planetlab.plslices import PlSlices
27 def list_to_dict(recs, key):
29 convert a list of dictionaries into a dictionary keyed on the
30 specified dictionary key
32 return dict ( [ (rec[key],rec) for rec in recs ] )
35 # PlShell is just an xmlrpc serverproxy where methods
36 # can be sent as-is; it takes care of authentication
37 # from the global config
39 class NovaDriver(Driver):
41 # the cache instance is a class member so it survives across incoming requests
44 def __init__ (self, config):
45 Driver.__init__(self, config)
46 self.shell = Shell(config)
48 if config.SFA_AGGREGATE_CACHING:
49 if NovaDriver.cache is None:
50 NovaDriver.cache = Cache()
51 self.cache = NovaDriver.cache
53 ########################################
54 ########## registry oriented
55 ########################################
57 ########## disabled users
58 def is_enabled (self, record):
59 # all records are enabled
62 def augment_records_with_testbed_info (self, sfa_records):
63 return self.fill_record_info (sfa_records)
66 def register (self, sfa_record, hrn, pub_key):
68 if sfa_record['type'] == 'slice':
69 record = self.register_slice(sfa_record, hrn)
70 elif sfa_record['type'] == 'user':
71 record = self.register_user(sfa_record, hrn, pub_key)
72 elif sfa_record['type'].startswith('authority'):
73 record = self.register_authority(sfa_record, hrn)
74 # We should be returning the records id as a pointer but
75 # this is a string and the records table expects this to be an
80 def register_slice(self, sfa_record, hrn):
81 # add slice description, name, researchers, PI
82 name = hrn_to_os_tenant_name(hrn)
83 description = sfa_record.get('description', None)
84 self.shell.auth_manager.tenants.create(name, description)
85 tenant = self.shell.auth_manager.tenants.find(name=name)
86 auth_hrn = OSXrn(xrn=hrn, type='slice').get_authority_hrn()
87 parent_tenant_name = OSXrn(xrn=auth_hrn, type='slice').get_tenant_name()
88 parent_tenant = self.shell.auth_manager.tenants.find(name=parent_tenant_name)
89 researchers = sfa_record.get('researchers', [])
90 for researcher in researchers:
91 name = Xrn(researcher).get_leaf()
92 user = self.shell.auth_manager.users.find(name=name)
93 self.shell.auth_manager.roles.add_user_role(user, 'user', tenant)
95 pis = sfa_record.get('pis', [])
97 name = Xrn(pi).get_leaf()
98 user = self.shell.auth_manager.users.find(name=name)
99 self.shell.auth_manager.roles.add_user_role(user, 'pi', tenant)
100 self.shell.auth_manager.roles.add_user_role(user, 'pi', parent_tenant)
104 def register_user(self, sfa_record, hrn, pub_key):
105 # add person roles, projects and keys
106 email = sfa_record.get('email', None)
108 name = xrn.get_leaf()
109 auth_hrn = xrn.get_authority_hrn()
110 tenant_name = OSXrn(xrn=auth_hrn, type='authority').get_tenant_name()
111 tenant = self.shell.auth_manager.tenants.find(name=tenant_name)
112 self.shell.auth_manager.users.create(name, email=email, tenant_id=tenant.id)
113 user = self.shell.auth_manager.users.find(name=name)
114 slices = sfa_records.get('slices', [])
115 for slice in projects:
116 slice_tenant_name = OSXrn(xrn=slice, type='slice').get_tenant_name()
117 slice_tenant = self.shell.auth_manager.tenants.find(name=slice_tenant_name)
118 self.shell.auth_manager.roles.add_user_role(user, slice_tenant, 'user')
119 keys = sfa_records.get('keys', [])
121 keyname = OSXrn(xrn=hrn, type='user').get_slicename()
122 self.shell.nova_client.keypairs.create(keyname, key)
125 def register_authority(self, sfa_record, hrn):
126 name = OSXrn(xrn=hrn, type='authority').get_tenant_name()
127 self.shell.auth_manager.tenants.create(name, sfa_record.get('description', ''))
128 tenant = self.shell.auth_manager.tenants.find(name=name)
133 # xxx actually old_sfa_record comes filled with plc stuff as well in the original code
134 def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
135 type = new_sfa_record['type']
137 # new_key implemented for users only
138 if new_key and type not in [ 'user' ]:
139 raise UnknownSfaType(type)
141 elif type == "slice":
142 # can update project manager and description
143 name = hrn_to_os_slicename(hrn)
144 researchers = sfa_record.get('researchers', [])
145 pis = sfa_record.get('pis', [])
146 project_manager = None
147 description = sfa_record.get('description', None)
149 project_manager = Xrn(pis[0], 'user').get_leaf()
151 project_manager = Xrn(researchers[0], 'user').get_leaf()
152 self.shell.auth_manager.modify_project(name, project_manager, description)
155 # can techinally update access_key and secret_key,
156 # but that is not in our scope, so we do nothing.
162 def remove (self, sfa_record):
163 type=sfa_record['type']
165 name = Xrn(sfa_record['hrn']).get_leaf()
166 if self.shell.auth_manager.get_user(name):
167 self.shell.auth_manager.delete_user(name)
168 elif type == 'slice':
169 name = hrn_to_os_slicename(sfa_record['hrn'])
170 if self.shell.auth_manager.get_project(name):
171 self.shell.auth_manager.delete_project(name)
176 def fill_record_info(self, records):
178 Given a (list of) SFA record, fill in the PLC specific
179 and SFA specific fields in the record.
181 if not isinstance(records, list):
184 for record in records:
185 if record['type'] == 'user':
186 record = self.fill_user_record_info(record)
187 elif record['type'] == 'slice':
188 record = self.fill_slice_record_info(record)
189 elif record['type'].startswith('authority'):
190 record = self.fill_auth_record_info(record)
193 record['geni_urn'] = hrn_to_urn(record['hrn'], record['type'])
194 record['geni_certificate'] = record['gid']
195 #if os_record.created_at is not None:
196 # record['date_created'] = datetime_to_string(utcparse(os_record.created_at))
197 #if os_record.updated_at is not None:
198 # record['last_updated'] = datetime_to_string(utcparse(os_record.updated_at))
202 def fill_user_record_info(self, record):
203 xrn = Xrn(record['hrn'])
204 name = xrn.get_leaf()
205 record['name'] = name
206 user = self.shell.auth_manager.users.find(name=name)
207 record['email'] = user.email
208 tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
210 all_tenants = self.shell.auth_manager.tenants.list()
211 for tmp_tenant in all_tenants:
212 if tmp_tenant.name.startswith(tenant.name +"."):
213 for tmp_user in tmp_tenant.list_users():
214 if tmp_user.name == user.name:
215 slice_hrn = ".".join([self.hrn, tmp_tenant.name])
216 slices.append(slice_hrn)
217 record['slices'] = slices
218 roles = self.shell.auth_manager.roles.roles_for_user(user, tenant)
219 record['roles'] = [role.name for role in roles]
220 keys = self.shell.nova_manager.keypairs.findall(name=record['hrn'])
221 record['keys'] = [key.public_key for key in keys]
224 def fill_slice_record_info(self, record):
225 tenant_name = hrn_to_os_tenant_name(record['hrn'])
226 tenant = self.shell.auth_manager.tenants.find(name=tenant_name)
227 parent_tenant_name = OSXrn(xrn=tenant_name).get_authority_hrn()
228 parent_tenant = self.shell.auth_manager.tenants.find(name=parent_tenant_name)
232 # look for users and pis in slice tenant
233 for user in tenant.list_users():
234 for role in self.shell.auth_manager.roles.roles_for_user(user, tenant):
235 if role.name.lower() == 'pi':
236 user_tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
237 hrn = ".".join([self.hrn, user_tenant.name, user.name])
239 elif role.name.lower() in ['user', 'member']:
240 user_tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
241 hrn = ".".join([self.hrn, user_tenant.name, user.name])
242 researchers.append(hrn)
244 # look for pis in the slice's parent (site/organization) tenant
245 for user in parent_tenant.list_users():
246 for role in self.shell.auth_manager.roles.roles_for_user(user, parent_tenant):
247 if role.name.lower() == 'pi':
248 user_tenant = self.shell.auth_manager.tenants.find(id=user.tenantId)
249 hrn = ".".join([self.hrn, user_tenant.name, user.name])
251 record['name'] = tenant_name
252 record['description'] = tenant.description
255 record['geni_creator'] = pis[0]
257 record['geni_creator'] = None
258 record['researcher'] = researchers
261 def fill_auth_record_info(self, record):
262 tenant_name = hrn_to_os_tenant_name(record['hrn'])
263 tenant = self.shell.auth_manager.tenants.find(name=tenant_name)
267 # look for users and pis in slice tenant
268 for user in tenant.list_users():
269 for role in self.shell.auth_manager.roles.roles_for_user(user, tenant):
270 hrn = ".".join([self.hrn, tenant.name, user.name])
271 if role.name.lower() == 'pi':
273 elif role.name.lower() in ['user', 'member']:
274 researchers.append(hrn)
278 all_tenants = self.shell.auth_manager.tenants.list()
279 for tmp_tenant in all_tenants:
280 if tmp_tenant.name.startswith(tenant.name+"."):
281 slices.append(".".join([self.hrn, tmp_tenant.name]))
283 record['name'] = tenant_name
284 record['description'] = tenant.description
286 record['enabled'] = tenant.enabled
287 record['researchers'] = researchers
288 record['slices'] = slices
292 # plcapi works by changes, compute what needs to be added/deleted
293 def update_relation (self, subject_type, target_type, subject_id, target_ids):
294 # hard-wire the code for slice/user for now, could be smarter if needed
295 if subject_type =='slice' and target_type == 'user':
296 subject=self.shell.project_get(subject_id)[0]
297 current_target_ids = [user.name for user in subject.members]
298 add_target_ids = list ( set (target_ids).difference(current_target_ids))
299 del_target_ids = list ( set (current_target_ids).difference(target_ids))
300 logger.debug ("subject_id = %s (type=%s)"%(subject_id,type(subject_id)))
301 for target_id in add_target_ids:
302 self.shell.project_add_member(target_id,subject_id)
303 logger.debug ("add_target_id = %s (type=%s)"%(target_id,type(target_id)))
304 for target_id in del_target_ids:
305 logger.debug ("del_target_id = %s (type=%s)"%(target_id,type(target_id)))
306 self.shell.project_remove_member(target_id, subject_id)
308 logger.info('unexpected relation to maintain, %s -> %s'%(subject_type,target_type))
311 ########################################
312 ########## aggregate oriented
313 ########################################
315 def testbed_name (self): return "openstack"
317 # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
318 def aggregate_version (self):
319 version_manager = VersionManager()
320 ad_rspec_versions = []
321 request_rspec_versions = []
322 for rspec_version in version_manager.versions:
323 if rspec_version.content_type in ['*', 'ad']:
324 ad_rspec_versions.append(rspec_version.to_dict())
325 if rspec_version.content_type in ['*', 'request']:
326 request_rspec_versions.append(rspec_version.to_dict())
328 'testbed':self.testbed_name(),
329 'geni_request_rspec_versions': request_rspec_versions,
330 'geni_ad_rspec_versions': ad_rspec_versions,
333 def list_slices (self, creds, options):
334 # look in cache first
336 slices = self.cache.get('slices')
338 logger.debug("OpenStackDriver.list_slices returns from cache")
342 projs = self.shell.auth_manager.get_projects()
343 slice_urns = [OSXrn(proj.name, 'slice').urn for proj in projs]
347 logger.debug ("OpenStackDriver.list_slices stores value in cache")
348 self.cache.add('slices', slice_urns)
352 # first 2 args are None in case of resource discovery
353 def list_resources (self, slice_urn, slice_hrn, creds, options):
354 cached_requested = options.get('cached', True)
356 version_manager = VersionManager()
357 # get the rspec's return format from options
358 rspec_version = version_manager.get_version(options.get('geni_rspec_version'))
359 version_string = "rspec_%s" % (rspec_version)
361 #panos adding the info option to the caching key (can be improved)
362 if options.get('info'):
363 version_string = version_string + "_"+options.get('info', 'default')
365 # look in cache first
366 if cached_requested and self.cache and not slice_hrn:
367 rspec = self.cache.get(version_string)
369 logger.debug("OpenStackDriver.ListResources: returning cached advertisement")
372 #panos: passing user-defined options
373 #print "manager options = ",options
374 aggregate = OSAggregate(self)
375 rspec = aggregate.get_rspec(slice_xrn=slice_urn, version=rspec_version,
379 if self.cache and not slice_hrn:
380 logger.debug("OpenStackDriver.ListResources: stores advertisement in cache")
381 self.cache.add(version_string, rspec)
385 def sliver_status (self, slice_urn, slice_hrn):
386 # find out where this slice is currently running
387 project_name = hrn_to_os_slicename(slice_hrn)
388 project = self.shell.auth_manager.get_project(project_name)
389 instances = self.shell.db.instance_get_all_by_project(project_name)
390 if len(instances) == 0:
391 raise SliverDoesNotExist("You have not allocated any slivers here")
394 top_level_status = 'unknown'
396 top_level_status = 'ready'
397 result['geni_urn'] = slice_urn
398 result['plos_login'] = 'root'
399 # do we need real dates here?
400 result['plos_expires'] = None
401 result['geni_expires'] = None
404 for instance in instances:
406 # instances are accessed by ip, not hostname. We need to report the ip
407 # somewhere so users know where to ssh to.
408 res['geni_expires'] = None
409 res['plos_hostname'] = instance.hostname
410 res['plos_created_at'] = datetime_to_string(utcparse(instance.created_at))
411 res['plos_boot_state'] = instance.vm_state
412 res['plos_sliver_type'] = instance.instance_type.name
413 sliver_id = Xrn(slice_urn).get_sliver_id(instance.project_id, \
414 instance.hostname, instance.id)
415 res['geni_urn'] = sliver_id
417 if instance.vm_state == 'running':
418 res['boot_state'] = 'ready'
419 res['geni_status'] = 'ready'
421 res['boot_state'] = 'unknown'
422 res['geni_status'] = 'unknown'
423 resources.append(res)
425 result['geni_status'] = top_level_status
426 result['geni_resources'] = resources
429 def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, users, options):
431 aggregate = OSAggregate(self)
432 rspec = RSpec(rspec_string)
433 instance_name = hrn_to_os_slicename(slice_hrn)
435 # assume first user is the caller and use their context
436 # for the ec2/euca api connection. Also, use the first users
437 # key as the project key.
440 key_name = aggregate.create_instance_key(slice_hrn, users[0])
442 # collect public keys
445 pubkeys.extend(user['keys'])
447 aggregate.run_instances(instance_name, rspec_string, key_name, pubkeys)
449 return aggregate.get_rspec(slice_xrn=slice_urn, version=rspec.version)
451 def delete_sliver (self, slice_urn, slice_hrn, creds, options):
452 aggregate = OSAggregate(self)
453 project_name = hrn_to_os_slicename(slice_hrn)
454 return aggregate.delete_instances(project_name)
456 def update_sliver(self, slice_urn, slice_hrn, rspec, creds, options):
457 name = hrn_to_os_slicename(slice_hrn)
458 aggregate = OSAggregate(self)
459 return aggregate.update_instances(name)
461 def renew_sliver (self, slice_urn, slice_hrn, creds, expiration_time, options):
464 def start_slice (self, slice_urn, slice_hrn, creds):
467 def stop_slice (self, slice_urn, slice_hrn, creds):
468 name = OSXrn(xrn=slice_urn).name
469 aggregate = OSAggregate(self)
470 return aggregate.stop_instances(name)
472 def reset_slice (self, slice_urn, slice_hrn, creds):
473 raise SfaNotImplemented ("reset_slice not available at this interface")
475 # xxx this code is quite old and has not run for ages
476 # it is obviously totally broken and needs a rewrite
477 def get_ticket (self, slice_urn, slice_hrn, creds, rspec_string, options):
478 raise SfaNotImplemented,"OpenStackDriver.get_ticket needs a rewrite"
479 # please keep this code for future reference
480 # slices = PlSlices(self)
481 # peer = slices.get_peer(slice_hrn)
482 # sfa_peer = slices.get_sfa_peer(slice_hrn)
484 # # get the slice record
485 # credential = api.getCredential()
486 # interface = api.registries[api.hrn]
487 # registry = api.server_proxy(interface, credential)
488 # records = registry.Resolve(xrn, credential)
490 # # make sure we get a local slice record
492 # for tmp_record in records:
493 # if tmp_record['type'] == 'slice' and \
494 # not tmp_record['peer_authority']:
495 # #Error (E0602, GetTicket): Undefined variable 'SliceRecord'
496 # slice_record = SliceRecord(dict=tmp_record)
498 # raise RecordNotFound(slice_hrn)
500 # # similar to CreateSliver, we must verify that the required records exist
501 # # at this aggregate before we can issue a ticket
503 # rspec = RSpec(rspec_string)
504 # requested_attributes = rspec.version.get_slice_attributes()
506 # # ensure site record exists
507 # site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer)
508 # # ensure slice record exists
509 # slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer)
510 # # ensure person records exists
511 # # xxx users is undefined in this context
512 # persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer)
513 # # ensure slice attributes exists
514 # slices.verify_slice_attributes(slice, requested_attributes)
517 # slivers = slices.get_slivers(slice_hrn)
520 # raise SliverDoesNotExist(slice_hrn)
525 # 'timestamp': int(time.time()),
526 # 'initscripts': initscripts,
530 # # create the ticket
531 # object_gid = record.get_gid_object()
532 # new_ticket = SfaTicket(subject = object_gid.get_subject())
533 # new_ticket.set_gid_caller(api.auth.client_gid)
534 # new_ticket.set_gid_object(object_gid)
535 # new_ticket.set_issuer(key=api.key, subject=self.hrn)
536 # new_ticket.set_pubkey(object_gid.get_pubkey())
537 # new_ticket.set_attributes(data)
538 # new_ticket.set_rspec(rspec)
539 # #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
540 # new_ticket.encode()
543 # return new_ticket.save_to_string(save_parents=True)