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 recors['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={}):
416 aggregate = DummyAggregate(self)
417 rspec = aggregate.list_resources(version=version, options=options)
420 def describe(self, urns, version, options={}):
421 aggregate = DummyAggregate(self)
422 return aggregate.describe(urns, version=version, options=options)
424 def status (self, urns, options={}):
425 aggregate = DummyAggregate(self)
426 desc = aggregate.describe(urns, version='GENI 3')
427 status = {'geni_urn': desc['geni_urn'],
428 'geni_slivers': desc['geni_slivers']}
432 def allocate (self, urn, rspec_string, expiration, options={}):
434 aggregate = DummyAggregate(self)
435 slices = DummySlices(self)
437 users = options.get('geni_users', [])
439 slice_record = users[0].get('slice_record', {})
442 rspec = RSpec(rspec_string)
443 requested_attributes = rspec.version.get_slice_attributes()
445 # ensure slice record exists
446 slice = slices.verify_slice(xrn.hrn, slice_record, expiration=expiration, options=options)
447 # ensure person records exists
448 #persons = slices.verify_persons(xrn.hrn, slice, users, peer, sfa_peer, options=options)
450 # add/remove slice from nodes
451 request_nodes = rspec.version.get_nodes_with_slivers()
452 nodes = slices.verify_slice_nodes(urn, slice, request_nodes)
454 return aggregate.describe([xrn.get_urn()], version=rspec.version)
456 def provision(self, urns, options={}):
458 slices = DummySlices(self)
459 aggregate = DummyAggregate(self)
460 slivers = aggregate.get_slivers(urns)
462 geni_users = options.get('geni_users', [])
463 #users = slices.verify_users(None, slice, geni_users, options=options)
464 # update sliver allocation states and set them to geni_provisioned
465 sliver_ids = [sliver['sliver_id'] for sliver in slivers]
466 dbsession=self.api.dbsession()
467 SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned',dbsession)
468 version_manager = VersionManager()
469 rspec_version = version_manager.get_version(options['geni_rspec_version'])
470 return self.describe(urns, rspec_version, options=options)
472 def delete(self, urns, options={}):
473 # collect sliver ids so we can update sliver allocation states after
474 # we remove the slivers.
475 aggregate = DummyAggregate(self)
476 slivers = aggregate.get_slivers(urns)
478 slice_id = slivers[0]['slice_id']
481 for sliver in slivers:
482 node_ids.append(sliver['node_id'])
483 sliver_ids.append(sliver['sliver_id'])
485 # determine if this is a peer slice
486 # xxx I wonder if this would not need to use PlSlices.get_peer instead
487 # in which case plc.peers could be deprecated as this here
488 # is the only/last call to this last method in plc.peers
489 slice_hrn = DummyXrn(auth=self.hrn, slicename=slivers[0]['slice_name']).get_hrn()
491 self.shell.DeleteSliceFromNodes({'slice_id': slice_id, 'node_ids': node_ids})
492 # delete sliver allocation states
493 dbsession=self.api.dbsession()
494 SliverAllocation.delete_allocations(sliver_ids,dbsession)
498 # prepare return struct
500 for sliver in slivers:
502 {'geni_sliver_urn': sliver['sliver_id'],
503 'geni_allocation_status': 'geni_unallocated',
504 'geni_expires': datetime_to_string(utcparse(sliver['expires']))})
507 def renew (self, urns, expiration_time, options={}):
508 aggregate = DummyAggregate(self)
509 slivers = aggregate.get_slivers(urns)
511 raise SearchFailed(urns)
513 requested_time = utcparse(expiration_time)
514 record = {'expires': int(datetime_to_epoch(requested_time))}
515 self.shell.UpdateSlice({'slice_id': slice['slice_id'], 'fileds': record})
516 description = self.describe(urns, 'GENI 3', options)
517 return description['geni_slivers']
519 def perform_operational_action (self, urns, action, options={}):
520 # Dummy doesn't support operational actions. Lets pretend like it
521 # supports start, but reject everything else.
522 action = action.lower()
523 if action not in ['geni_start']:
524 raise UnsupportedOperation(action)
526 # fault if sliver is not full allocated (operational status is geni_pending_allocation)
527 description = self.describe(urns, 'GENI 3', options)
528 for sliver in description['geni_slivers']:
529 if sliver['geni_operational_status'] == 'geni_pending_allocation':
530 raise UnsupportedOperation(action, "Sliver must be fully allocated (operational status is not geni_pending_allocation)")
532 # Perform Operational Action Here
535 geni_slivers = self.describe(urns, 'GENI 3', options)['geni_slivers']
538 def shutdown (self, xrn, options={}):
539 xrn = DummyXrn(xrn=xrn, type='slice')
540 slicename = xrn.pl_slicename()
541 slices = self.shell.GetSlices({'name': slicename}, ['slice_id'])
543 raise RecordNotFound(slice_hrn)
544 slice_id = slices[0]['slice_id']
545 slice_tags = self.shell.GetSliceTags({'slice_id': slice_id, 'tagname': 'enabled'})
547 self.shell.AddSliceTag(slice_id, 'enabled', '0')
548 elif slice_tags[0]['value'] != "0":
549 tag_id = slice_tags[0]['slice_tag_id']
550 self.shell.UpdateSliceTag(tag_id, '0')