2 This file defines the IotlabSlices class by which all the slice checkings
3 upon lease creation are done.
5 from sfa.util.xrn import get_authority, urn_to_hrn
6 from sfa.util.sfalogging import logger
13 This class is responsible for checking the slice when creating a
14 lease or a sliver. Those checks include verifying that the user is valid,
15 that the slice is known from the testbed or from our peers, that the list
16 of nodes involved has not changed (in this case the lease is modified
19 rspec_to_slice_tag = {'max_rate': 'net_max_rate'}
21 def __init__(self, driver):
23 Get the reference to the driver here.
27 def get_peer(self, xrn):
29 Finds the authority of a resource based on its xrn.
30 If the authority is Iotlab (local) return None,
31 Otherwise, look up in the DB if Iotlab is federated with this site
32 authority and returns its DB record if it is the case.
34 :param xrn: resource's xrn
40 hrn, hrn_type = urn_to_hrn(xrn)
41 #Does this slice belong to a local site or a peer iotlab site?
44 # get this slice's authority (site)
45 slice_authority = get_authority(hrn)
47 #This slice belongs to the current site
48 if slice_authority == self.driver.iotlab_api.root_auth:
49 site_authority = slice_authority
52 site_authority = get_authority(slice_authority).lower()
53 # get this site's authority (sfa root authority or sub authority)
55 logger.debug("IOTLABSLICES \t get_peer slice_authority %s \
56 site_authority %s hrn %s"
57 % (slice_authority, site_authority, hrn))
59 # check if we are already peered with this site_authority
60 #if so find the peer record
61 peers = self.driver.iotlab_api.GetPeers(peer_filter=site_authority)
62 for peer_record in peers:
63 if site_authority == peer_record.hrn:
65 logger.debug(" IOTLABSLICES \tget_peer peer %s " % (peer))
68 def get_sfa_peer(self, xrn):
69 """Returns the authority name for the xrn or None if the local site
72 :param xrn: the xrn of the resource we are looking the authority for.
74 :returns: the resources's authority name.
78 hrn, hrn_type = urn_to_hrn(xrn)
80 # return the authority for this hrn or None if we are the authority
82 slice_authority = get_authority(hrn)
83 site_authority = get_authority(slice_authority)
85 if site_authority != self.driver.hrn:
86 sfa_peer = site_authority
90 def verify_slice_leases(self, sfa_slice, requested_jobs_dict, peer):
92 Compare requested leases with the leases already scheduled/
93 running in OAR. If necessary, delete and recreate modified leases,
94 and delete no longer requested ones.
96 :param sfa_slice: sfa slice record
97 :param requested_jobs_dict: dictionary of requested leases
98 :param peer: sfa peer record
100 :type sfa_slice: dict
101 :type requested_jobs_dict: dict
103 :returns: leases list of dictionary
108 logger.debug("IOTLABSLICES verify_slice_leases sfa_slice %s "
110 #First get the list of current leases from OAR
111 leases = self.driver.iotlab_api.GetLeases({'name': sfa_slice['hrn']})
112 logger.debug("IOTLABSLICES verify_slice_leases requested_jobs_dict %s \
113 leases %s " % (requested_jobs_dict, leases))
115 current_nodes_reserved_by_start_time = {}
116 requested_nodes_by_start_time = {}
117 leases_by_start_time = {}
118 reschedule_jobs_dict = {}
120 #Create reduced dictionary with key start_time and value
122 #-for the leases already registered by OAR first
123 # then for the new leases requested by the user
125 #Leases already scheduled/running in OAR
127 current_nodes_reserved_by_start_time[lease['t_from']] = \
128 lease['reserved_nodes']
129 leases_by_start_time[lease['t_from']] = lease
131 #First remove job whose duration is too short
132 for job in requested_jobs_dict.values():
133 if job['duration'] < self.driver.iotlab_api.GetLeaseGranularity():
134 del requested_jobs_dict[job['start_time']]
137 for start_time in requested_jobs_dict:
138 requested_nodes_by_start_time[int(start_time)] = \
139 requested_jobs_dict[start_time]['hostname']
140 #Check if there is any difference between the leases already
141 #registered in OAR and the requested jobs.
142 #Difference could be:
143 #-Lease deleted in the requested jobs
144 #-Added/removed nodes
147 logger.debug("IOTLABSLICES verify_slice_leases \
148 requested_nodes_by_start_time %s \
149 "% (requested_nodes_by_start_time))
150 #Find all deleted leases
152 list(set(leases_by_start_time.keys()).\
153 difference(requested_nodes_by_start_time.keys()))
154 deleted_leases = [leases_by_start_time[start_time]['lease_id'] \
155 for start_time in start_time_list]
158 #Find added or removed nodes in exisiting leases
159 for start_time in requested_nodes_by_start_time:
160 logger.debug("IOTLABSLICES verify_slice_leases start_time %s \
162 if start_time in current_nodes_reserved_by_start_time:
164 if requested_nodes_by_start_time[start_time] == \
165 current_nodes_reserved_by_start_time[start_time]:
170 set(requested_nodes_by_start_time[start_time])
172 update_node_set.difference(\
173 current_nodes_reserved_by_start_time[start_time])
175 update_node_set.intersection(\
176 current_nodes_reserved_by_start_time[start_time])
179 current_nodes_reserved_by_start_time[start_time])
181 old_nodes_set.difference(\
182 requested_nodes_by_start_time[start_time])
183 logger.debug("IOTLABSLICES verify_slice_leases \
184 shared_nodes %s added_nodes %s removed_nodes %s"\
185 %(shared_nodes, added_nodes,removed_nodes ))
186 #If the lease is modified, delete it before
188 #Add the deleted lease job id in the list
189 #WARNING :rescheduling does not work if there is already
190 # 2 running/scheduled jobs because deleting a job
191 #takes time SA 18/10/2012
192 if added_nodes or removed_nodes:
193 deleted_leases.append(\
194 leases_by_start_time[start_time]['lease_id'])
196 if added_nodes or shared_nodes:
197 reschedule_jobs_dict[str(start_time)] = \
198 requested_jobs_dict[str(start_time)]
203 job = requested_jobs_dict[str(start_time)]
204 logger.debug("IOTLABSLICES \
205 NEWLEASE slice %s job %s"
207 self.driver.iotlab_api.AddLeases(
209 sfa_slice, int(job['start_time']),
210 int(job['duration']))
212 #Deleted leases are the ones with lease id not declared in the Rspec
214 self.driver.iotlab_api.DeleteLeases(deleted_leases,
215 sfa_slice['user']['uid'])
216 logger.debug("IOTLABSLICES \
217 verify_slice_leases slice %s deleted_leases %s"
218 % (sfa_slice, deleted_leases))
220 if reschedule_jobs_dict:
221 for start_time in reschedule_jobs_dict:
222 job = reschedule_jobs_dict[start_time]
223 self.driver.iotlab_api.AddLeases(
225 sfa_slice, int(job['start_time']),
226 int(job['duration']))
229 def verify_slice_nodes(self, sfa_slice, requested_slivers, peer):
230 """Check for wanted and unwanted nodes in the slice.
232 Removes nodes and associated leases that the user does not want anymore
233 by deleteing the associated job in OAR (DeleteSliceFromNodes).
234 Returns the nodes' hostnames that are going to be in the slice.
236 :param sfa_slice: slice record. Must contain node_ids and list_node_ids.
238 :param requested_slivers: list of requested nodes' hostnames.
239 :param peer: unused so far.
241 :type sfa_slice: dict
242 :type requested_slivers: list
245 :returns: list requested nodes hostnames
248 .. warning:: UNUSED SQA 24/07/13
249 .. seealso:: DeleteSliceFromNodes
250 .. todo:: check what to do with the peer? Can not remove peer nodes from
251 slice here. Anyway, in this case, the peer should have gotten the
258 if 'node_ids' in sfa_slice:
259 nodes = self.driver.iotlab_api.GetNodes(
260 sfa_slice['list_node_ids'],
262 current_slivers = [node['hostname'] for node in nodes]
264 # remove nodes not in rspec
265 deleted_nodes = list(set(current_slivers).
266 difference(requested_slivers))
268 logger.debug("IOTLABSLICES \tverify_slice_nodes slice %s\
269 \r\n \r\n deleted_nodes %s"
270 % (sfa_slice, deleted_nodes))
273 #Delete the entire experience
274 self.driver.iotlab_api.DeleteSliceFromNodes(sfa_slice)
277 def verify_slice(self, slice_hrn, slice_record, sfa_peer):
278 """Ensures slice record exists.
280 The slice record must exist either in Iotlab or in the other
281 federated testbed (sfa_peer). If the slice does not belong to Iotlab,
282 check if the user already exists in LDAP. In this case, adds the slice
283 to the sfa DB and associates its LDAP user.
285 :param slice_hrn: slice's name
286 :param slice_record: sfa record of the slice
287 :param sfa_peer: name of the peer authority if any.(not Iotlab).
289 :type slice_hrn: string
290 :type slice_record: dictionary
291 :type sfa_peer: string
293 .. seealso:: AddSlice
298 slicename = slice_hrn
299 # check if slice belongs to Iotlab
300 slices_list = self.driver.iotlab_api.GetSlices(
301 slice_filter=slicename, slice_filter_type='slice_hrn')
306 for sl in slices_list:
308 logger.debug("SLABSLICE \t verify_slice slicename %s \
309 slices_list %s sl %s \r slice_record %s"
310 % (slicename, slices_list, sl, slice_record))
312 sfa_slice.update(slice_record)
315 #Search for user in ldap based on email SA 14/11/12
316 ldap_user = self.driver.iotlab_api.ldap.LdapFindUser(\
317 slice_record['user'])
318 logger.debug(" IOTLABSLICES \tverify_slice Oups \
319 slice_record %s sfa_peer %s ldap_user %s"
320 % (slice_record, sfa_peer, ldap_user))
321 #User already registered in ldap, meaning user should be in SFA db
322 #and hrn = sfa_auth+ uid
323 sfa_slice = {'hrn': slicename,
325 'authority': slice_record['authority'],
326 'gid': slice_record['gid'],
327 'slice_id': slice_record['record_id'],
328 'reg-researchers': slice_record['reg-researchers'],
329 'peer_authority': str(sfa_peer)
333 hrn = self.driver.iotlab_api.root_auth + '.' + ldap_user['uid']
334 user = self.driver.get_user_record(hrn)
336 logger.debug(" IOTLABSLICES \tverify_slice hrn %s USER %s"
339 # add the external slice to the local SFA iotlab DB
341 self.driver.iotlab_api.AddSlice(sfa_slice, user)
343 logger.debug("IOTLABSLICES \tverify_slice ADDSLICE OK")
347 def verify_persons(self, slice_hrn, slice_record, users, options={}):
348 """Ensures the users in users list exist and are enabled in LDAP. Adds
351 Checking that a user exist is based on the user's email. If the user is
352 still not found in the LDAP, it means that the user comes from another
353 federated. In this case an account has to be created in LDAP
354 so as to enable the user to use the testbed, since we trust the testbed
355 he comes from. This is done by calling AddPerson.
357 :param slice_hrn: slice name
358 :param slice_record: record of the slice_hrn
359 :param users: users is a record list. Records can either be
360 local records or users records from known and trusted federated
361 sites.If the user is from another site that iotlab doesn't trust yet,
362 then Resolve will raise an error before getting to create_sliver.
364 :type slice_hrn: string
365 :type slice_record: string
368 .. seealso:: AddPerson
369 .. note:: Removed unused peer and sfa_peer parameters. SA 18/07/13.
373 #TODO SA 21/08/12 verify_persons Needs review
375 logger.debug("IOTLABSLICES \tverify_persons \tslice_hrn %s \
376 \t slice_record %s\r\n users %s \t "
377 % (slice_hrn, slice_record, users))
381 #users_dict : dict whose keys can either be the user's hrn or its id.
382 #Values contains only id and hrn
385 #First create dicts by hrn and id for each user in the user record list:
387 if 'slice_record' in info:
388 slice_rec = info['slice_record']
389 user = slice_rec['user']
392 users_by_email[user['email']] = user
393 users_dict[user['email']] = user
395 logger.debug("SLABSLICE.PY \t verify_person \
396 users_dict %s \r\n user_by_email %s \r\n \
398 % (users_dict, users_by_email, users_by_id))
400 existing_user_ids = []
401 existing_user_emails = []
403 # Check if user is in Iotlab LDAP using its hrn.
404 # Assuming Iotlab is centralised : one LDAP for all sites,
405 # user's record_id unknown from LDAP
406 # LDAP does not provide users id, therefore we rely on email to find the
410 #Construct the list of filters (list of dicts) for GetPersons
411 filter_user = [users_by_email[email] for email in users_by_email]
412 #Check user i in LDAP with GetPersons
413 #Needed because what if the user has been deleted in LDAP but
415 existing_users = self.driver.iotlab_api.GetPersons(filter_user)
416 logger.debug(" \r\n SLABSLICE.PY \tverify_person filter_user \
417 %s existing_users %s "
418 % (filter_user, existing_users))
419 #User is in iotlab LDAP
421 for user in existing_users:
422 users_dict[user['email']].update(user)
423 existing_user_emails.append(
424 users_dict[user['email']]['email'])
427 # User from another known trusted federated site. Check
428 # if a iotlab account matching the email has already been created.
431 if isinstance(users, list):
432 req += users[0]['email']
434 req += users['email']
435 ldap_reslt = self.driver.iotlab_api.ldap.LdapSearch(req)
438 logger.debug(" SLABSLICE.PY \tverify_person users \
439 USER already in Iotlab \t ldap_reslt %s \
441 existing_users.append(ldap_reslt[1])
444 #User not existing in LDAP
445 logger.debug("SLABSLICE.PY \tverify_person users \
446 not in ldap ...NEW ACCOUNT NEEDED %s \r\n \t \
447 ldap_reslt %s " % (users, ldap_reslt))
449 requested_user_emails = users_by_email.keys()
450 requested_user_hrns = \
451 [users_by_email[user]['hrn'] for user in users_by_email]
452 logger.debug("SLABSLICE.PY \tverify_person \
453 users_by_email %s " % (users_by_email))
455 #Check that the user of the slice in the slice record
456 #matches one of the existing users
458 if slice_record['PI'][0] in requested_user_hrns:
459 logger.debug(" SLABSLICE \tverify_person ['PI']\
460 slice_record %s" % (slice_record))
465 # users to be added, removed or updated
466 #One user in one iotlab slice : there should be no need
467 #to remove/ add any user from/to a slice.
468 #However a user from SFA which is not registered in Iotlab yet
469 #should be added to the LDAP.
470 added_user_emails = set(requested_user_emails).\
471 difference(set(existing_user_emails))
474 #self.verify_keys(existing_slice_users, updated_users_list, \
479 #requested_user_email is in existing_user_emails
480 if len(added_user_emails) == 0:
481 slice_record['login'] = users_dict[requested_user_emails[0]]['uid']
482 logger.debug(" SLABSLICE \tverify_person QUICK DIRTY %s"
485 for added_user_email in added_user_emails:
486 added_user = users_dict[added_user_email]
487 logger.debug(" IOTLABSLICES \r\n \r\n \t verify_person \
488 added_user %s" % (added_user))
490 person['peer_person_id'] = None
491 k_list = ['first_name', 'last_name', 'person_id']
494 person[k] = added_user[k]
496 person['pkey'] = added_user['keys'][0]
497 person['mail'] = added_user['email']
498 person['email'] = added_user['email']
499 person['key_ids'] = added_user.get('key_ids', [])
501 ret = self.driver.iotlab_api.AddPerson(person)
503 # meaning bool is True and the AddPerson was successful
504 person['uid'] = ret['uid']
505 slice_record['login'] = person['uid']
507 # error message in ret
508 logger.debug(" IOTLABSLICES ret message %s" %(ret))
510 logger.debug(" SLABSLICE \r\n \r\n \t THE SECOND verify_person\
511 person %s" % (person))
512 #Update slice_Record with the id now known to LDAP
515 added_persons.append(person)
519 def verify_keys(self, persons, users, peer, options={}):
525 for person in persons:
526 key_ids.extend(person['key_ids'])
527 keylist = self.driver.iotlab_api.GetKeys(key_ids, ['key_id', 'key'])
531 keydict[key['key']] = key['key_id']
532 existing_keys = keydict.keys()
535 for person in persons:
536 persondict[person['email']] = person
541 users_by_key_string = {}
543 user_keys = user.get('keys', [])
544 updated_persons.append(user)
545 for key_string in user_keys:
546 users_by_key_string[key_string] = user
547 requested_keys.append(key_string)
548 if key_string not in existing_keys:
549 key = {'key': key_string, 'key_type': 'ssh'}
552 #person = persondict[user['email']]
553 #self.driver.iotlab_api.UnBindObjectFromPeer(
554 # 'person',person['person_id'],
556 ret = self.driver.iotlab_api.AddPersonKey(
559 #key_index = user_keys.index(key['key'])
560 #remote_key_id = user['key_ids'][key_index]
561 #self.driver.iotlab_api.BindObjectToPeer('key', \
562 #key['key_id'], peer['shortname'], \
567 #self.driver.iotlab_api.BindObjectToPeer('person', \
568 #person['person_id'], peer['shortname'], \
571 # remove old keys (only if we are not appending)
572 append = options.get('append', True)
574 removed_keys = set(existing_keys).difference(requested_keys)
575 for key in removed_keys:
577 #self.driver.iotlab_api.UnBindObjectFromPeer('key', \
578 #key, peer['shortname'])
580 user = users_by_key_string[key]
581 self.driver.iotlab_api.DeleteKey(user, key)