4 from sfa.util.faults import MissingSfaInfo, UnknownSfaType, \
5 RecordNotFound, SfaNotImplemented, SliverDoesNotExist, SearchFailed, \
6 UnsupportedOperation, Forbidden
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.util.cache import Cache
13 # one would think the driver should not need to mess with the SFA db, but..
14 from sfa.storage.model import RegRecord, SliverAllocation
15 from sfa.trust.credential import Credential
17 # used to be used in get_ticket
18 #from sfa.trust.sfaticket import SfaTicket
19 from sfa.rspecs.version_manager import VersionManager
20 from sfa.rspecs.rspec import RSpec
22 # the driver interface, mostly provides default behaviours
23 from sfa.managers.driver import Driver
25 from sfa.dummy.dummyshell import DummyShell
26 from sfa.dummy.dummyaggregate import DummyAggregate
27 from sfa.dummy.dummyslices import DummySlices
28 from sfa.dummy.dummyxrn import DummyXrn, slicename_to_hrn, hostname_to_hrn, hrn_to_dummy_slicename, xrn_to_hostname
31 def list_to_dict(recs, key):
33 convert a list of dictionaries into a dictionary keyed on the
34 specified dictionary key
36 return dict ( [ (rec[key],rec) for rec in recs ] )
39 # DummyShell is just an xmlrpc serverproxy where methods can be sent as-is;
41 class DummyDriver (Driver):
43 # the cache instance is a class member so it survives across incoming requests
46 def __init__ (self, api):
47 Driver.__init__ (self, api)
49 self.hrn = config.SFA_INTERFACE_HRN
50 self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
51 self.shell = DummyShell (config)
52 self.testbedInfo = self.shell.GetTestbedInfo()
54 def check_sliver_credentials(self, creds, urns):
55 # build list of cred object hrns
58 slice_cred_hrn = Credential(cred=cred).get_gid_object().get_hrn()
59 slice_cred_names.append(DummyXrn(xrn=slice_cred_hrn).dummy_slicename())
61 # look up slice name of slivers listed in urns arg
64 sliver_id_parts = Xrn(xrn=urn).get_sliver_id_parts()
66 slice_ids.append(int(sliver_id_parts[0]))
71 raise Forbidden("sliver urn not provided")
73 slices = self.shell.GetSlices({'slice_ids': slice_ids})
74 sliver_names = [slice['slice_name'] for slice in slices]
76 # make sure we have a credential for every specified sliver ierd
77 for sliver_name in sliver_names:
78 if sliver_name not in slice_cred_names:
79 msg = "Valid credential not found for target: %s" % sliver_name
82 ########################################
83 ########## registry oriented
84 ########################################
86 def augment_records_with_testbed_info (self, sfa_records):
87 return self.fill_record_info (sfa_records)
90 def register (self, sfa_record, hrn, pub_key):
91 type = sfa_record['type']
92 dummy_record = self.sfa_fields_to_dummy_fields(type, hrn, sfa_record)
94 if type == 'authority':
98 slices = self.shell.GetSlices({'slice_name': dummy_record['slice_name']})
100 pointer = self.shell.AddSlice(dummy_record)
102 pointer = slices[0]['slice_id']
105 users = self.shell.GetUsers({'email':sfa_record['email']})
107 pointer = self.shell.AddUser(dummy_record)
109 pointer = users[0]['user_id']
113 self.shell.AddUserKey({'user_id' : pointer, 'key' : pub_key})
116 nodes = self.shell.GetNodes(dummy_record['hostname'])
118 pointer = self.shell.AddNode(dummy_record)
120 pointer = users[0]['node_id']
125 def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
126 pointer = old_sfa_record['pointer']
127 type = old_sfa_record['type']
128 dummy_record=self.sfa_fields_to_dummy_fields(type, hrn, new_sfa_record)
130 # new_key implemented for users only
131 if new_key and type not in [ 'user' ]:
132 raise UnknownSfaType(type)
136 self.shell.UpdateSlice({'slice_id': pointer, 'fields': dummy_record})
139 self.shell.UpdateUser({'user_id': pointer, 'fields': dummy_record})
142 self.shell.AddUserKey({'user_id' : pointer, 'key' : new_key})
145 self.shell.UpdateNode({'node_id': pointer, 'fields': dummy_record})
152 def remove (self, sfa_record):
153 type=sfa_record['type']
154 pointer=sfa_record['pointer']
156 self.shell.DeleteUser({'user_id': pointer})
157 elif type == 'slice':
158 self.shell.DeleteSlice({'slice_id': pointer})
160 self.shell.DeleteNode({'node_id': pointer})
169 # Convert SFA fields to Dummy testbed fields for use when registering or updating
170 # registry record in the dummy testbed
173 def sfa_fields_to_dummy_fields(self, type, hrn, sfa_record):
178 dummy_record["slice_name"] = hrn_to_dummy_slicename(hrn)
181 if "hostname" not in sfa_record:
182 raise MissingSfaInfo("hostname")
183 dummy_record["hostname"] = sfa_record["hostname"]
184 if "type" in sfa_record:
185 dummy_record["type"] = sfa_record["type"]
187 dummy_record["type"] = "dummy_type"
189 elif type == "authority":
190 dummy_record["name"] = hrn
193 dummy_record["user_name"] = sfa_record["email"].split('@')[0]
194 dummy_record["email"] = sfa_record["email"]
199 def fill_record_info(self, records):
201 Given a (list of) SFA record, fill in the DUMMY TESTBED specific
202 and SFA specific fields in the record.
204 if not isinstance(records, list):
207 self.fill_record_dummy_info(records)
208 self.fill_record_hrns(records)
209 self.fill_record_sfa_info(records)
212 def fill_record_dummy_info(self, records):
214 Fill in the DUMMY specific fields of a SFA record. This
215 involves calling the appropriate DUMMY method to retrieve the
216 database record for the object.
218 @param record: record to fill in field (in/out param)
221 node_ids, slice_ids, user_ids = [], [], []
222 type_map = {'node': node_ids, 'slice': slice_ids, 'user': user_ids}
224 for record in records:
225 for type in type_map:
226 if type == record['type']:
227 type_map[type].append(record['pointer'])
230 nodes, slices, users = {}, {}, {}
232 node_list = self.shell.GetNodes({'node_ids':node_ids})
233 nodes = list_to_dict(node_list, 'node_id')
235 slice_list = self.shell.GetSlices({'slice_ids':slice_ids})
236 slices = list_to_dict(slice_list, 'slice_id')
238 user_list = self.shell.GetUsers({'user_ids': user_ids})
239 users = list_to_dict(user_list, 'user_id')
241 dummy_records = {'node': nodes, 'slice': slices, 'user': users}
245 for record in records:
246 # records with pointer==-1 do not have dummy info.
247 if record['pointer'] == -1:
250 for type in dummy_records:
251 if record['type'] == type:
252 if record['pointer'] in dummy_records[type]:
253 record.update(dummy_records[type][record['pointer']])
256 if record['type'] == 'user':
257 record['key_ids'] = []
259 for key in dummy_records['user'][record['pointer']]['keys']:
260 record['key_ids'].append(-1)
261 record['keys'].append(key)
265 def fill_record_hrns(self, records):
267 convert dummy ids to hrns
271 slice_ids, user_ids, node_ids = [], [], []
272 for record in records:
273 if 'user_ids' in record:
274 user_ids.extend(record['user_ids'])
275 if 'slice_ids' in record:
276 slice_ids.extend(record['slice_ids'])
277 if 'node_ids' in record:
278 node_ids.extend(record['node_ids'])
281 slices, users, nodes = {}, {}, {}
283 user_list = self.shell.GetUsers({'user_ids': user_ids})
284 users = list_to_dict(user_list, 'user_id')
286 slice_list = self.shell.GetSlices({'slice_ids': slice_ids})
287 slices = list_to_dict(slice_list, 'slice_id')
289 node_list = self.shell.GetNodes({'node_ids': node_ids})
290 nodes = list_to_dict(node_list, 'node_id')
292 # convert ids to hrns
293 for record in records:
294 # get all relevant data
295 type = record['type']
296 pointer = record['pointer']
297 testbed_name = self.testbed_name()
302 if 'user_ids' in record:
303 emails = [users[user_id]['email'] for user_id in record['user_ids'] \
305 usernames = [email.split('@')[0] for email in emails]
306 user_hrns = [".".join([auth_hrn, testbed_name, username]) for username in usernames]
307 record['users'] = user_hrns
308 if 'slice_ids' in record:
309 slicenames = [slices[slice_id]['slice_name'] for slice_id in record['slice_ids'] \
310 if slice_id in slices]
311 slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames]
312 record['slices'] = slice_hrns
313 if 'node_ids' in record:
314 hostnames = [nodes[node_id]['hostname'] for node_id in record['node_ids'] \
316 node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames]
317 record['nodes'] = node_hrns
322 def fill_record_sfa_info(self, records):
324 def startswith(prefix, values):
325 return [value for value in values if value.startswith(prefix)]
329 for record in records:
330 user_ids.extend(record.get("user_ids", []))
332 # get sfa records for all records associated with these records.
333 # we'll replace pl ids (person_ids) with hrns from the sfa records
336 # get the registry records
337 user_list, users = [], {}
338 user_list = self.api.dbsession().query (RegRecord).filter(RegRecord.pointer.in_(user_ids))
339 # create a hrns keyed on the sfa record's pointer.
340 # Its possible for multiple records to have the same pointer so
341 # the dict's value will be a list of hrns.
342 users = defaultdict(list)
343 for user in user_list:
344 users[user.pointer].append(user)
346 # get the dummy records
347 dummy_user_list, dummy_users = [], {}
348 dummy_user_list = self.shell.GetUsers({'user_ids': user_ids})
349 dummy_users = list_to_dict(dummy_user_list, 'user_id')
352 for record in records:
353 # skip records with no pl info (top level authorities)
354 #if record['pointer'] == -1:
357 type = record['type']
358 logger.info("fill_record_sfa_info - incoming record typed %s"%type)
359 if (type == "slice"):
360 # all slice users are researchers
361 record['geni_urn'] = hrn_to_urn(record['hrn'], 'slice')
363 record['researcher'] = []
364 for user_id in record.get('user_ids', []):
365 hrns = [user.hrn for user in users[user_id]]
366 record['researcher'].extend(hrns)
368 elif (type.startswith("authority")):
370 logger.info("fill_record_sfa_info - authority xherex")
372 elif (type == "node"):
373 sfa_info['dns'] = record.get("hostname", "")
374 # xxx TODO: URI, LatLong, IP, DNS
376 elif (type == "user"):
377 logger.info('setting user.email')
378 sfa_info['email'] = record.get("email", "")
379 sfa_info['geni_urn'] = hrn_to_urn(record['hrn'], 'user')
380 sfa_info['geni_certificate'] = record['gid']
381 # xxx TODO: PostalAddress, Phone
382 record.update(sfa_info)
386 def update_relation (self, subject_type, target_type, relation_name, subject_id, target_ids):
387 # hard-wire the code for slice/user for now, could be smarter if needed
388 if subject_type =='slice' and target_type == 'user' and relation_name == 'researcher':
389 subject=self.shell.GetSlices ({'slice_id': subject_id})[0]
390 if 'user_ids' not in subject.keys():
391 subject['user_ids'] = []
392 current_target_ids = subject['user_ids']
393 add_target_ids = list ( set (target_ids).difference(current_target_ids))
394 del_target_ids = list ( set (current_target_ids).difference(target_ids))
395 logger.debug ("subject_id = %s (type=%s)"%(subject_id,type(subject_id)))
396 for target_id in add_target_ids:
397 self.shell.AddUserToSlice ({'user_id': target_id, 'slice_id': subject_id})
398 logger.debug ("add_target_id = %s (type=%s)"%(target_id,type(target_id)))
399 for target_id in del_target_ids:
400 logger.debug ("del_target_id = %s (type=%s)"%(target_id,type(target_id)))
401 self.shell.DeleteUserFromSlice ({'user_id': target_id, 'slice_id': subject_id})
403 logger.info('unexpected relation %s to maintain, %s -> %s'%(relation_name,subject_type,target_type))
406 ########################################
407 ########## aggregate oriented
408 ########################################
410 def testbed_name (self): return "dummy"
412 def aggregate_version (self):
415 def list_resources (self, version=None, options=None):
416 if options is None: options={}
417 aggregate = DummyAggregate(self)
418 rspec = aggregate.list_resources(version=version, options=options)
421 def describe(self, urns, version, options=None):
422 if options is None: options={}
423 aggregate = DummyAggregate(self)
424 return aggregate.describe(urns, version=version, options=options)
426 def status (self, urns, options=None):
427 if options is None: options={}
428 aggregate = DummyAggregate(self)
429 desc = aggregate.describe(urns, version='GENI 3')
430 status = {'geni_urn': desc['geni_urn'],
431 'geni_slivers': desc['geni_slivers']}
435 def allocate (self, urn, rspec_string, expiration, options=None):
436 if options is None: options={}
438 aggregate = DummyAggregate(self)
439 slices = DummySlices(self)
441 users = options.get('geni_users', [])
443 slice_record = users[0].get('slice_record', {})
446 rspec = RSpec(rspec_string)
447 requested_attributes = rspec.version.get_slice_attributes()
449 # ensure slice record exists
450 slice = slices.verify_slice(xrn.hrn, slice_record, expiration=expiration, options=options)
451 # ensure person records exists
452 #persons = slices.verify_persons(xrn.hrn, slice, users, peer, sfa_peer, options=options)
454 # add/remove slice from nodes
455 request_nodes = rspec.version.get_nodes_with_slivers()
456 nodes = slices.verify_slice_nodes(urn, slice, request_nodes)
458 return aggregate.describe([xrn.get_urn()], version=rspec.version)
460 def provision(self, urns, options=None):
461 if options is None: options={}
463 slices = DummySlices(self)
464 aggregate = DummyAggregate(self)
465 slivers = aggregate.get_slivers(urns)
467 geni_users = options.get('geni_users', [])
468 #users = slices.verify_users(None, slice, geni_users, options=options)
469 # update sliver allocation states and set them to geni_provisioned
470 sliver_ids = [sliver['sliver_id'] for sliver in slivers]
471 dbsession=self.api.dbsession()
472 SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned',dbsession)
473 version_manager = VersionManager()
474 rspec_version = version_manager.get_version(options['geni_rspec_version'])
475 return self.describe(urns, rspec_version, options=options)
477 def delete(self, urns, options=None):
478 if options is None: options={}
479 # collect sliver ids so we can update sliver allocation states after
480 # we remove the slivers.
481 aggregate = DummyAggregate(self)
482 slivers = aggregate.get_slivers(urns)
484 slice_id = slivers[0]['slice_id']
487 for sliver in slivers:
488 node_ids.append(sliver['node_id'])
489 sliver_ids.append(sliver['sliver_id'])
491 # determine if this is a peer slice
492 # xxx I wonder if this would not need to use PlSlices.get_peer instead
493 # in which case plc.peers could be deprecated as this here
494 # is the only/last call to this last method in plc.peers
495 slice_hrn = DummyXrn(auth=self.hrn, slicename=slivers[0]['slice_name']).get_hrn()
497 self.shell.DeleteSliceFromNodes({'slice_id': slice_id, 'node_ids': node_ids})
498 # delete sliver allocation states
499 dbsession=self.api.dbsession()
500 SliverAllocation.delete_allocations(sliver_ids,dbsession)
504 # prepare return struct
506 for sliver in slivers:
508 {'geni_sliver_urn': sliver['sliver_id'],
509 'geni_allocation_status': 'geni_unallocated',
510 'geni_expires': datetime_to_string(utcparse(sliver['expires']))})
513 def renew (self, urns, expiration_time, options=None):
514 if options is None: options={}
515 aggregate = DummyAggregate(self)
516 slivers = aggregate.get_slivers(urns)
518 raise SearchFailed(urns)
520 requested_time = utcparse(expiration_time)
521 record = {'expires': int(datetime_to_epoch(requested_time))}
522 self.shell.UpdateSlice({'slice_id': slice['slice_id'], 'fileds': record})
523 description = self.describe(urns, 'GENI 3', options)
524 return description['geni_slivers']
526 def perform_operational_action (self, urns, action, options=None):
527 if options is None: options={}
528 # Dummy doesn't support operational actions. Lets pretend like it
529 # supports start, but reject everything else.
530 action = action.lower()
531 if action not in ['geni_start']:
532 raise UnsupportedOperation(action)
534 # fault if sliver is not full allocated (operational status is geni_pending_allocation)
535 description = self.describe(urns, 'GENI 3', options)
536 for sliver in description['geni_slivers']:
537 if sliver['geni_operational_status'] == 'geni_pending_allocation':
538 raise UnsupportedOperation(action, "Sliver must be fully allocated (operational status is not geni_pending_allocation)")
540 # Perform Operational Action Here
543 geni_slivers = self.describe(urns, 'GENI 3', options)['geni_slivers']
546 def shutdown (self, xrn, options=None):
547 if options is None: options={}
548 xrn = DummyXrn(xrn=xrn, type='slice')
549 slicename = xrn.pl_slicename()
550 slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
552 raise RecordNotFound(slice_hrn)
553 slice_id = slices[0]['slice_id']
554 slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'})
556 self.shell.AddSliceTag(slice_id, 'enabled', '0')
557 elif slice_tags[0]['value'] != "0":
558 tag_id = slice_tags[0]['slice_tag_id']
559 self.shell.UpdateSliceTag(tag_id, '0')