4 from sfa.util.faults import MissingSfaInfo, UnknownSfaType, \
5 RecordNotFound, SfaNotImplemented, SfaInvalidArgument
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
17 from sfa.rspecs.version_manager import VersionManager
18 from sfa.rspecs.rspec import RSpec
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
26 def list_to_dict(recs, key):
28 convert a list of dictionaries into a dictionary keyed on the
29 specified dictionary key
31 return dict ( [ (rec[key],rec) for rec in recs ] )
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
38 class NovaDriver(Driver):
40 # the cache instance is a class member so it survives across incoming requests
43 def __init__ (self, config):
44 Driver.__init__(self, config)
45 self.shell = Shell(config=config)
47 if config.SFA_AGGREGATE_CACHING:
48 if NovaDriver.cache is None:
49 NovaDriver.cache = Cache()
50 self.cache = NovaDriver.cache
52 ########################################
53 ########## registry oriented
54 ########################################
56 ########## disabled users
57 def is_enabled (self, record):
58 # all records are enabled
61 def augment_records_with_testbed_info (self, sfa_records):
62 return self.fill_record_info (sfa_records)
65 def register (self, sfa_record, hrn, pub_key):
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
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)
96 pis = sfa_record.get('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)
105 def register_user(self, sfa_record, hrn, pub_key):
106 # add person roles, projects and keys
107 email = sfa_record.get('email', None)
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', [])
122 keyname = OSXrn(xrn=hrn, type='user').get_slicename()
123 self.shell.nova_client.keypairs.create(keyname, key)
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)
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']
138 # new_key implemented for users only
139 if new_key and type not in [ 'user' ]:
140 raise UnknownSfaType(type)
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)
150 project_manager = Xrn(pis[0], 'user').get_leaf()
152 project_manager = Xrn(researchers[0], 'user').get_leaf()
153 self.shell.auth_manager.modify_project(name, project_manager, description)
156 # can techinally update access_key and secret_key,
157 # but that is not in our scope, so we do nothing.
163 def remove (self, sfa_record):
164 type=sfa_record['type']
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)
177 def fill_record_info(self, records):
179 Given a (list of) SFA record, fill in the PLC specific
180 and SFA specific fields in the record.
182 if not isinstance(records, list):
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)
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))
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)
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]
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)
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])
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)
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])
252 record['name'] = tenant_name
253 record['description'] = tenant.description
256 record['geni_creator'] = pis[0]
258 record['geni_creator'] = None
259 record['researcher'] = researchers
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)
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':
274 elif role.name.lower() in ['user', 'member']:
275 researchers.append(hrn)
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]))
284 record['name'] = tenant_name
285 record['description'] = tenant.description
287 record['enabled'] = tenant.enabled
288 record['researchers'] = researchers
289 record['slices'] = slices
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)
309 logger.info('unexpected relation to maintain, %s -> %s'%(subject_type,target_type))
312 ########################################
313 ########## aggregate oriented
314 ########################################
316 def testbed_name (self): return "openstack"
318 def aggregate_version (self):
321 # first 2 args are None in case of resource discovery
322 def list_resources (self, version, options):
323 aggregate = OSAggregate(self)
324 rspec = aggregate.list_resources(version=version, options=options)
327 def describe(self, urns, version, options):
328 aggregate = OSAggregate(self)
329 return aggregate.describe(urns, version=version, options=options)
331 def status (self, urns, options={}):
332 aggregate = OSAggregate(self)
333 desc = aggregate.describe(urns)
334 return desc['geni_slivers']
336 def allocate (self, urn, rspec_string, options):
338 aggregate = OSAggregate(self)
340 # assume first user is the caller and use their context
341 # for the ec2/euca api connection. Also, use the first users
342 # key as the project key.
345 key_name = aggregate.create_instance_key(xrn.get_hrn(), users[0])
347 # collect public keys
348 users = options.get('geni_users', [])
351 pubkeys.extend(user['keys'])
353 rspec = RSpec(rspec_string)
354 instance_name = hrn_to_os_slicename(slice_hrn)
355 tenant_name = OSXrn(xrn=slice_hrn, type='slice').get_tenant_name()
356 aggregate.run_instances(instance_name, tenant_name, rspec_string, key_name, pubkeys)
358 return aggregate.describe(slice_xrn=slice_urn, version=rspec.version)
360 def provision(self, urns, version, options):
361 aggregate = OSAggregate(self)
362 return aggregate.describe(urns, version=version, options=options)
364 def delete (self, urns, options):
365 aggregate = OSAggregate(self)
367 xrn = OSXrn(xrn=urn, type='slice')
368 tenant_name = xrn.get_tenant_name()
369 project_name = xrn.get_slicename()
371 aggregate.delete_instance(tenant_name, project_name, id)
374 def renew (self, urns, expiration_time, options):
377 def perform_operational_action (self, urns, action, options):
378 tenant_name = OSXrn(xrn=slice_hrn, type='slice').get_tenant_name()
379 name = OSXrn(xrn=slice_urn).name
380 aggregate = OSAggregate(self)
381 return aggregate.stop_instances(name, tenant_name)
383 # xxx this code is quite old and has not run for ages
384 # it is obviously totally broken and needs a rewrite
385 def get_ticket (self, slice_urn, slice_hrn, creds, rspec_string, options):
386 raise SfaNotImplemented,"OpenStackDriver.get_ticket needs a rewrite"
387 # please keep this code for future reference
388 # slices = PlSlices(self)
389 # peer = slices.get_peer(slice_hrn)
390 # sfa_peer = slices.get_sfa_peer(slice_hrn)
392 # # get the slice record
393 # credential = api.getCredential()
394 # interface = api.registries[api.hrn]
395 # registry = api.server_proxy(interface, credential)
396 # records = registry.Resolve(xrn, credential)
398 # # make sure we get a local slice record
400 # for tmp_record in records:
401 # if tmp_record['type'] == 'slice' and \
402 # not tmp_record['peer_authority']:
403 # #Error (E0602, GetTicket): Undefined variable 'SliceRecord'
404 # slice_record = SliceRecord(dict=tmp_record)
406 # raise RecordNotFound(slice_hrn)
408 # # similar to CreateSliver, we must verify that the required records exist
409 # # at this aggregate before we can issue a ticket
411 # rspec = RSpec(rspec_string)
412 # requested_attributes = rspec.version.get_slice_attributes()
414 # # ensure site record exists
415 # site = slices.verify_site(slice_hrn, slice_record, peer, sfa_peer)
416 # # ensure slice record exists
417 # slice = slices.verify_slice(slice_hrn, slice_record, peer, sfa_peer)
418 # # ensure person records exists
419 # # xxx users is undefined in this context
420 # persons = slices.verify_persons(slice_hrn, slice, users, peer, sfa_peer)
421 # # ensure slice attributes exists
422 # slices.verify_slice_attributes(slice, requested_attributes)
425 # slivers = slices.get_slivers(slice_hrn)
428 # raise SliverDoesNotExist(slice_hrn)
433 # 'timestamp': int(time.time()),
434 # 'initscripts': initscripts,
438 # # create the ticket
439 # object_gid = record.get_gid_object()
440 # new_ticket = SfaTicket(subject = object_gid.get_subject())
441 # new_ticket.set_gid_caller(api.auth.client_gid)
442 # new_ticket.set_gid_object(object_gid)
443 # new_ticket.set_issuer(key=api.key, subject=self.hrn)
444 # new_ticket.set_pubkey(object_gid.get_pubkey())
445 # new_ticket.set_attributes(data)
446 # new_ticket.set_rspec(rspec)
447 # #new_ticket.set_parent(api.auth.hierarchy.get_auth_ticket(auth_hrn))
448 # new_ticket.encode()
451 # return new_ticket.save_to_string(save_parents=True)