Allocate, Describe, Provision now working for iotlab.
[sfa.git] / sfa / iotlab / iotlabdriver.py
index c8d5617..699e111 100644 (file)
@@ -3,20 +3,22 @@ Implements what a driver should provide for SFA to work.
 """
 from sfa.util.faults import SliverDoesNotExist, UnknownSfaType
 from sfa.util.sfalogging import logger
-# from sfa.storage.alchemy import dbsession
 from sfa.storage.model import RegRecord
+from sfa.util.sfatime import utcparse, datetime_to_string
 
 from sfa.managers.driver import Driver
 from sfa.rspecs.version_manager import VersionManager
 from sfa.rspecs.rspec import RSpec
 
-from sfa.util.xrn import Xrn, hrn_to_urn, get_authority
+from sfa.iotlab.iotlabxrn import xrn_object
+from sfa.util.xrn import Xrn, hrn_to_urn, get_authority, urn_to_hrn
 
-from sfa.iotlab.iotlabaggregate import IotlabAggregate, iotlab_xrn_to_hostname
+from sfa.iotlab.iotlabaggregate import IotlabAggregate
+from sfa.iotlab.iotlabxrn import xrn_to_hostname
 from sfa.iotlab.iotlabslices import IotlabSlices
 
-
-from sfa.iotlab.iotlabapi import IotlabTestbedAPI
+from sfa.storage.model import SliverAllocation
+from sfa.iotlab.iotlabshell import IotlabShell
 
 
 class IotlabDriver(Driver):
@@ -28,7 +30,7 @@ class IotlabDriver(Driver):
     .. seealso::: Driver class
 
     """
-    def __init__(self, config):
+    def __init__(self, api):
         """
 
         Sets the iotlab SFA config parameters,
@@ -38,9 +40,10 @@ class IotlabDriver(Driver):
         :type config: Config object
 
         """
-        Driver.__init__(self, config)
-        self.config = config
-        self.iotlab_api = IotlabTestbedAPI(config)
+        Driver.__init__(self, api)
+        self.api = api
+        config = api.config
+        self.testbed_shell = IotlabShell(api)
         self.cache = None
 
     def augment_records_with_testbed_info(self, record_list):
@@ -85,7 +88,7 @@ class IotlabDriver(Driver):
                     # look for node info using GetNodes
                     # the record is about one node only
                     filter_dict = {'hrn': [record['hrn']]}
-                    node_info = self.iotlab_api.GetNodes(filter_dict)
+                    node_info = self.testbed_shell.GetNodes(filter_dict)
                     # the node_info is about one node only, but it is formatted
                     # as a list
                     record.update(node_info[0])
@@ -117,7 +120,7 @@ class IotlabDriver(Driver):
                              'key_ids': ''})
 
                     #Get iotlab slice record and oar job id if any.
-                    recslice_list = self.iotlab_api.GetSlices(
+                    recslice_list = self.testbed_shell.GetSlices(
                         slice_filter=str(record['hrn']),
                         slice_filter_type='slice_hrn')
 
@@ -131,7 +134,7 @@ class IotlabDriver(Driver):
                             fill_record_info oar_job_id %s "
                                          % (rec['oar_job_id']))
 
-                            record['node_ids'] = [self.iotlab_api.root_auth +
+                            record['node_ids'] = [self.testbed_shell.root_auth +
                                                   '.' + hostname for hostname
                                                   in rec['node_ids']]
                     except KeyError:
@@ -145,7 +148,7 @@ class IotlabDriver(Driver):
                     #The record is a SFA user record.
                     #Get the information about his slice from Iotlab's DB
                     #and add it to the user record.
-                    recslice_list = self.iotlab_api.GetSlices(
+                    recslice_list = self.testbed_shell.GetSlices(
                         slice_filter=record['record_id'],
                         slice_filter_type='record_id_user')
 
@@ -178,7 +181,7 @@ class IotlabDriver(Driver):
                                      'hrn': recslice_list[0]['hrn']})
 
                     #GetPersons takes [] as filters
-                    user_iotlab = self.iotlab_api.GetPersons([record])
+                    user_iotlab = self.testbed_shell.GetPersons([record])
 
                     record.update(user_iotlab[0])
                     #For client_helper.py compatibility
@@ -214,7 +217,7 @@ class IotlabDriver(Driver):
         """
 
         #First get the slice with the slice hrn
-        slice_list = self.iotlab_api.GetSlices(slice_filter=slice_hrn,
+        slice_list = self.testbed_shell.GetSlices(slice_filter=slice_hrn,
                                                slice_filter_type='slice_hrn')
 
         if len(slice_list) == 0:
@@ -227,7 +230,7 @@ class IotlabDriver(Driver):
         slice_nodes_list = []
         slice_nodes_list = one_slice['node_ids']
         #Get all the corresponding nodes details
-        nodes_all = self.iotlab_api.GetNodes(
+        nodes_all = self.testbed_shell.GetNodes(
             {'hostname': slice_nodes_list},
             ['node_id', 'hostname', 'site', 'boot_state'])
         nodeall_byhostname = dict([(one_node['hostname'], one_node)
@@ -297,8 +300,7 @@ class IotlabDriver(Driver):
                          % (resources, res))
             return result
 
-    @staticmethod
-    def get_user_record(hrn):
+    def get_user_record(self, hrn):
         """
 
         Returns the user record based on the hrn from the SFA DB .
@@ -309,7 +311,7 @@ class IotlabDriver(Driver):
         :rtype: RegUser
 
         """
-        return dbsession.query(RegRecord).filter_by(hrn=hrn).first()
+        return self.api.dbsession().query(RegRecord).filter_by(hrn=hrn).first()
 
     def testbed_name(self):
         """
@@ -365,9 +367,9 @@ class IotlabDriver(Driver):
 
             if not lease.get('lease_id'):
                 if get_authority(lease['component_id']) == \
-                        self.iotlab_api.root_auth:
+                        self.testbed_shell.root_auth:
                     single_requested_lease['hostname'] = \
-                        iotlab_xrn_to_hostname(\
+                        xrn_to_hostname(\
                             lease.get('component_id').strip())
                     single_requested_lease['start_time'] = \
                         lease.get('start_time')
@@ -376,7 +378,7 @@ class IotlabDriver(Driver):
                     #the lease to the requested leases list
                     duration_in_seconds = \
                         int(single_requested_lease['duration'])
-                    if duration_in_seconds >= self.iotlab_api.GetMinExperimentDurationInSec():
+                    if duration_in_seconds >= self.testbed_shell.GetMinExperimentDurationInGranularity():
                         requested_lease_list.append(single_requested_lease)
 
         return requested_lease_list
@@ -396,27 +398,27 @@ class IotlabDriver(Driver):
 
         """
 
-        requested_job_dict = {}
+        requested_xp_dict = {}
         for lease in requested_lease_list:
 
             #In case it is an asap experiment start_time is empty
             if lease['start_time'] == '':
                 lease['start_time'] = '0'
 
-            if lease['start_time'] not in requested_job_dict:
+            if lease['start_time'] not in requested_xp_dict:
                 if isinstance(lease['hostname'], str):
                     lease['hostname'] = [lease['hostname']]
 
-                requested_job_dict[lease['start_time']] = lease
+                requested_xp_dict[lease['start_time']] = lease
 
             else:
-                job_lease = requested_job_dict[lease['start_time']]
+                job_lease = requested_xp_dict[lease['start_time']]
                 if lease['duration'] == job_lease['duration']:
                     job_lease['hostname'].append(lease['hostname'])
 
-        return requested_job_dict
+        return requested_xp_dict
 
-    def _process_requested_jobs(self, rspec):
+    def _process_requested_xp_dict(self, rspec):
         """
         Turns the requested leases and information into a dictionary
             of requested jobs, grouped by starting time.
@@ -427,13 +429,14 @@ class IotlabDriver(Driver):
 
         """
         requested_lease_list = self._get_requested_leases_list(rspec)
-        logger.debug("IOTLABDRIVER _process_requested_jobs \
+        logger.debug("IOTLABDRIVER _process_requested_xp_dict \
             requested_lease_list  %s" % (requested_lease_list))
-        job_dict = self._group_leases_by_start_time(requested_lease_list)
-        logger.debug("IOTLABDRIVER _process_requested_jobs  job_dict\
-        %s" % (job_dict))
+        xp_dict = self._group_leases_by_start_time(requested_lease_list)
+        logger.debug("IOTLABDRIVER _process_requested_xp_dict  xp_dict\
+        %s" % (xp_dict))
+
+        return xp_dict
 
-        return job_dict
 
     def create_sliver(self, slice_urn, slice_hrn, creds, rspec_string,
                       users, options):
@@ -503,7 +506,7 @@ class IotlabDriver(Driver):
 
         #requested_slivers = [node.get('component_id') \
                     #for node in rspec.version.get_nodes_with_slivers()\
-                    #if node.get('authority_id') is self.iotlab_api.root_auth]
+                    #if node.get('authority_id') is self.testbed_shell.root_auth]
         #l = [ node for node in rspec.version.get_nodes_with_slivers() ]
         #logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
                                     #requested_slivers %s  listnodes %s" \
@@ -511,30 +514,27 @@ class IotlabDriver(Driver):
         #verify_slice_nodes returns nodes, but unused here. Removed SA 13/08/12.
         #slices.verify_slice_nodes(sfa_slice, requested_slivers, peer)
 
-        requested_job_dict = self._process_requested_jobs(rspec)
+        requested_xp_dict = self._process_requested_xp_dict(rspec)
 
-        logger.debug("IOTLABDRIVER.PY \tcreate_sliver  requested_job_dict %s "
-                     % (requested_job_dict))
+        logger.debug("IOTLABDRIVER.PY \tcreate_sliver  requested_xp_dict %s "
+                     % (requested_xp_dict))
         #verify_slice_leases returns the leases , but the return value is unused
         #here. Removed SA 13/08/12
         slices.verify_slice_leases(sfa_slice,
-                                   requested_job_dict, peer)
+                                   requested_xp_dict, peer)
 
         return aggregate.get_rspec(slice_xrn=slice_urn,
                                    login=sfa_slice['login'],
                                    version=rspec.version)
 
-    def delete_sliver(self, slice_urn, slice_hrn, creds, options):
+    def delete(self, slice_urns, options):
         """
         Deletes the lease associated with the slice hrn and the credentials
             if the slice belongs to iotlab. Answer to DeleteSliver.
 
         :param slice_urn: urn of the slice
-        :param slice_hrn: name of the slice
-        :param creds: slice credenials
         :type slice_urn: string
-        :type slice_hrn: string
-        :type creds: ? unused
+
 
         :returns: 1 if the slice to delete was not found on iotlab,
             True if the deletion was successful, False otherwise otherwise.
@@ -545,8 +545,22 @@ class IotlabDriver(Driver):
         .. note:: creds are unused, and are not used either in the dummy driver
              delete_sliver .
         """
-
-        sfa_slice_list = self.iotlab_api.GetSlices(
+        # collect sliver ids so we can update sliver allocation states after
+        # we remove the slivers.
+        aggregate = IotlabAggregate(self)
+        slivers = aggregate.get_slivers(slice_urns)
+        if slivers:
+            slice_id = slivers[0]['slice_id']
+            node_ids = []
+            sliver_ids = []
+            for sliver in slivers:
+                node_ids.append(sliver['node_id'])
+                sliver_ids.append(sliver['sliver_id'])
+        logger.debug("IOTLABDRIVER.PY delete_sliver slivers %s slice_urns %s"
+            % (slivers, slice_urns))
+        slice_hrn = urn_to_hrn(slice_urns[0])[0]
+
+        sfa_slice_list = self.testbed_shell.GetSlices(
             slice_filter=slice_hrn,
             slice_filter_type='slice_hrn')
 
@@ -564,10 +578,20 @@ class IotlabDriver(Driver):
             logger.debug("IOTLABDRIVER.PY delete_sliver peer %s \
                 \r\n \t sfa_slice %s " % (peer, sfa_slice))
             try:
-                self.iotlab_api.DeleteSliceFromNodes(sfa_slice)
-                return True
+                self.testbed_shell.DeleteSliceFromNodes(sfa_slice)
+                dbsession = self.api.dbsession()
+                SliverAllocation.delete_allocations(sliver_ids,dbsession)
             except:
-                return False
+                logger.log_exc("IOTLABDRIVER.PY delete error ")
+
+        # prepare return struct
+        geni_slivers = []
+        for sliver in slivers:
+            geni_slivers.append(
+                {'geni_sliver_urn': sliver['sliver_id'],
+                 'geni_allocation_status': 'geni_unallocated',
+                 'geni_expires': datetime_to_string(utcparse(sliver['expires']))})
+        return geni_slivers
 
     def list_resources (self, slice_urn, slice_hrn, creds, options):
         """
@@ -659,7 +683,7 @@ class IotlabDriver(Driver):
 
         # get data from db
 
-        slices = self.iotlab_api.GetSlices()
+        slices = self.testbed_shell.GetSlices()
         logger.debug("IOTLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n"
                      % (slices))
         slice_hrns = [iotlab_slice['hrn'] for iotlab_slice in slices]
@@ -736,11 +760,11 @@ class IotlabDriver(Driver):
 
             if new_key:
                 # must check this key against the previous one if it exists
-                persons = self.iotlab_api.GetPersons([old_sfa_record])
+                persons = self.testbed_shell.GetPersons([old_sfa_record])
                 person = persons[0]
                 keys = [person['pkey']]
                 #Get all the person's keys
-                keys_dict = self.iotlab_api.GetKeys(keys)
+                keys_dict = self.testbed_shell.GetKeys(keys)
 
                 # Delete all stale keys, meaning the user has only one key
                 #at a time
@@ -753,8 +777,8 @@ class IotlabDriver(Driver):
                 else:
                     #remove all the other keys
                     for key in keys_dict:
-                        self.iotlab_api.DeleteKey(person, key)
-                    self.iotlab_api.AddPersonKey(
+                        self.testbed_shell.DeleteKey(person, key)
+                    self.testbed_shell.AddPersonKey(
                         person, {'sshPublicKey': person['pkey']},
                         {'sshPublicKey': new_key})
         return True
@@ -783,16 +807,182 @@ class IotlabDriver(Driver):
         if sfa_record_type == 'user':
 
             #get user from iotlab ldap
-            person = self.iotlab_api.GetPersons(sfa_record)
+            person = self.testbed_shell.GetPersons(sfa_record)
             #No registering at a given site in Iotlab.
             #Once registered to the LDAP, all iotlab sites are
             #accesible.
             if person:
                 #Mark account as disabled in ldap
-                return self.iotlab_api.DeletePerson(sfa_record)
+                return self.testbed_shell.DeletePerson(sfa_record)
 
         elif sfa_record_type == 'slice':
-            if self.iotlab_api.GetSlices(slice_filter=hrn,
+            if self.testbed_shell.GetSlices(slice_filter=hrn,
                                          slice_filter_type='slice_hrn'):
-                ret = self.iotlab_api.DeleteSlice(sfa_record)
+                ret = self.testbed_shell.DeleteSlice(sfa_record)
             return True
+
+    def check_sliver_credentials(self, creds, urns):
+        # build list of cred object hrns
+        slice_cred_names = []
+        for cred in creds:
+            slice_cred_hrn = Credential(cred=cred).get_gid_object().get_hrn()
+            slicename = Xrn(xrn=slice_cred_hrn).iotlab_slicename()
+            logger.debug("IOTLABDRIVER.PY \t check_sliver_credentials slicename %s \r\n \r\n"
+                     % (slicename))
+            slice_cred_names.append(slicename)
+
+        # look up slice name of slivers listed in urns arg
+
+        slice_ids = []
+        for urn in urns:
+            sliver_id_parts = Xrn(xrn=urn).get_sliver_id_parts()
+            try:
+                slice_ids.append(int(sliver_id_parts[0]))
+            except ValueError:
+                pass
+
+        if not slice_ids:
+             raise Forbidden("sliver urn not provided")
+
+        slices = self.testbed_shell.GetSlices(slice_ids)
+        sliver_names = [single_slice['name'] for single_slice in slices]
+
+        # make sure we have a credential for every specified sliver ierd
+        for sliver_name in sliver_names:
+            if sliver_name not in slice_cred_names:
+                msg = "Valid credential not found for target: %s" % sliver_name
+                raise Forbidden(msg)
+
+    ########################################
+    ########## aggregate oriented
+    ########################################
+
+
+    def testbed_name (self): return "iotlab"
+
+    def aggregate_version (self):
+        return {}
+
+    # first 2 args are None in case of resource discovery
+    def list_resources (self, version=None, options={}):
+        aggregate = IotlabAggregate(self)
+        rspec =  aggregate.list_resources(version=version, options=options)
+        return rspec
+
+    def describe(self, urns, version, options={}):
+        aggregate = IotlabAggregate(self)
+        return aggregate.describe(urns, version=version, options=options)
+
+    def status (self, urns, options={}):
+        aggregate = IotlabAggregate(self)
+        desc =  aggregate.describe(urns, version='GENI 3')
+        status = {'geni_urn': desc['geni_urn'],
+                  'geni_slivers': desc['geni_slivers']}
+        return status
+
+
+    def allocate (self, urn, rspec_string, expiration, options={}):
+        xrn = Xrn(urn)
+        aggregate = IotlabAggregate(self)
+
+        slices = IotlabSlices(self)
+        peer = slices.get_peer(xrn.get_hrn())
+        sfa_peer = slices.get_sfa_peer(xrn.get_hrn())
+
+
+        slice_record = None
+        users = options.get('geni_users', [])
+
+        sfa_users = options.get('sfa_users', [])
+        if sfa_users:
+            slice_record = sfa_users[0].get('slice_record', [])
+        logger.debug("IOTLABDRIVER.PY \t ===============allocate \t\
+                            \r\n \r\n options %s slice_record %s" % (options,slice_record))
+        # parse rspec
+        rspec = RSpec(rspec_string)
+        # requested_attributes = rspec.version.get_slice_attributes()
+
+        # ensure site record exists
+        # site = slices.verify_site(xrn.hrn, slice_record, peer, sfa_peer, options=options)
+        # ensure slice record exists
+
+        current_slice = slices.verify_slice(xrn.hrn, slice_record, sfa_peer)
+        logger.debug("IOTLABDRIVER.PY \t ===============allocate \t\
+                            \r\n \r\n  current_slice %s" % (current_slice))
+        # ensure person records exists
+
+        # oui c'est degueulasse, le slice_record se retrouve modifie
+        # dans la methode avec les infos du user, els infos sont propagees
+        # dans verify_slice_leases
+        persons = slices.verify_persons(xrn.hrn, slice_record, users, options=options)
+        # ensure slice attributes exists
+        # slices.verify_slice_attributes(slice, requested_attributes, options=options)
+
+        # add/remove slice from nodes
+        requested_xp_dict = self._process_requested_xp_dict(rspec)
+
+        logger.debug("IOTLABDRIVER.PY \tallocate  requested_xp_dict %s "
+                     % (requested_xp_dict))
+        request_nodes = rspec.version.get_nodes_with_slivers()
+        nodes_list = []
+        for start_time in requested_xp_dict:
+            lease = requested_xp_dict[start_time]
+            for hostname in lease['hostname']:
+                nodes_list.append(hostname)
+
+        # nodes = slices.verify_slice_nodes(slice_record,request_nodes, peer)
+        logger.debug("IOTLABDRIVER.PY \tallocate  nodes_list %s slice_record %s"
+                     % (nodes_list, slice_record))
+
+        # add/remove leases
+        rspec_requested_leases = rspec.version.get_leases()
+        leases = slices.verify_slice_leases(slice_record, requested_xp_dict, peer)
+        logger.debug("IOTLABDRIVER.PY \tallocate leases  %s rspec_requested_leases %s"
+                     % (leases,rspec_requested_leases))
+         # update sliver allocations
+        for hostname in nodes_list:
+            client_id = hostname
+            node_urn = xrn_object(self.testbed_shell.root_auth, hostname).urn
+            component_id = node_urn
+            slice_urn = current_slice['reg-urn']
+            for lease in leases:
+                if hostname in lease['reserved_nodes']:
+                    index = lease['reserved_nodes'].index(hostname)
+                    sliver_hrn = '%s.%s-%s' % (self.hrn, lease['lease_id'],
+                                   lease['resource_ids'][index] )
+            sliver_id = Xrn(sliver_hrn, type='sliver').urn
+            record = SliverAllocation(sliver_id=sliver_id, client_id=client_id,
+                                      component_id=component_id,
+                                      slice_urn = slice_urn,
+                                      allocation_state='geni_allocated')
+
+            logger.debug("\r\n \
+                ===============================IOTLABDRIVER.PY \tallocate  sliver_id %s slice_urn %s \r\n"
+                     % (sliver_id,slice_urn))
+            record.sync(self.api.dbsession())
+        # add/remove links links
+        # slices.verify_slice_links(slice, rspec.version.get_link_requests(), nodes)
+
+
+
+        return aggregate.describe([xrn.get_urn()], version=rspec.version)
+
+    def provision(self, urns, options={}):
+        # update users
+        slices = IotlabSlices(self)
+        aggregate = IotlabAggregate(self)
+        slivers = aggregate.get_slivers(urns)
+        current_slice = slivers[0]
+        peer = slices.get_peer(current_slice['hrn'])
+        sfa_peer = slices.get_sfa_peer(current_slice['hrn'])
+        users = options.get('geni_users', [])
+        # persons = slices.verify_persons(current_slice['hrn'],
+            # current_slice, users, peer, sfa_peer, options=options)
+        # slices.handle_peer(None, None, persons, peer)
+        # update sliver allocation states and set them to geni_provisioned
+        sliver_ids = [sliver['sliver_id'] for sliver in slivers]
+        dbsession =self.api.dbsession()
+        SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned',dbsession)
+        version_manager = VersionManager()
+        rspec_version = version_manager.get_version(options['geni_rspec_version'])
+        return self.describe(urns, rspec_version, options=options)