Fixing bug in DeleteLeases.
[sfa.git] / sfa / iotlab / OARrestapi.py
1 from httplib import HTTPConnection, HTTPException, NotConnected
2 import json
3 from sfa.util.config import Config
4 from sfa.util.sfalogging import logger
5 import os.path
6 import sys
7
8
9 class JsonPage:
10
11     """Class used to manipulate json pages given by OAR.
12
13     In case the json answer from a GET request is too big to fit in one json
14     page, this class provides helper methods to retrieve all the pages and
15     store them in a list before putting them into one single json dictionary,
16     facilitating the parsing.
17
18     """
19
20     def __init__(self):
21         """Defines attributes to manipulate and parse the json pages.
22
23         """
24         #All are boolean variables
25         self.concatenate = False
26         #Indicates end of data, no more pages to be loaded.
27         self.end = False
28         self.next_page = False
29         #Next query address
30         self.next_offset = None
31         #Json page
32         self.raw_json = None
33
34     def FindNextPage(self):
35         """
36         Gets next data page from OAR when the query's results are too big to
37         be transmitted in a single page. Uses the "links' item in the json
38         returned to check if an additionnal page has to be loaded. Updates
39         object attributes next_page, next_offset, and end.
40
41         """
42         if "links" in self.raw_json:
43             for page in self.raw_json['links']:
44                 if page['rel'] == 'next':
45                     self.concatenate = True
46                     self.next_page = True
47                     self.next_offset = "?" + page['href'].split("?")[1]
48                     return
49
50         if self.concatenate:
51             self.end = True
52             self.next_page = False
53             self.next_offset = None
54
55             return
56
57         #Otherwise, no next page and no concatenate, must be a single page
58         #Concatenate the single page and get out of here.
59         else:
60             self.next_page = False
61             self.concatenate = True
62             self.next_offset = None
63             return
64
65     @staticmethod
66     def ConcatenateJsonPages(saved_json_list):
67         """
68         If the json answer is too big to be contained in a single page,
69         all the pages have to be loaded and saved before being appended to the
70         first page.
71
72         :param saved_json_list: list of all the stored pages, including the
73             first page.
74         :type saved_json_list: list
75         :returns: Returns a dictionary with all the pages saved in the
76             saved_json_list. The key of the dictionary is 'items'.
77         :rtype: dict
78
79
80         .. seealso:: SendRequest
81         .. warning:: Assumes the apilib is 0.2.10 (with the 'items' key in the
82             raw json dictionary)
83
84         """
85         #reset items list
86
87         tmp = {}
88         tmp['items'] = []
89
90         for page in saved_json_list:
91             tmp['items'].extend(page['items'])
92         return tmp
93
94     def ResetNextPage(self):
95         """
96         Resets all the Json page attributes (next_page, next_offset,
97         concatenate, end). Has to be done before getting another json answer
98         so that the previous page status does not affect the new json load.
99
100         """
101         self.next_page = True
102         self.next_offset = None
103         self.concatenate = False
104         self.end = False
105
106
107 class OARrestapi:
108     """Class used to connect to the OAR server and to send GET and POST
109     requests.
110
111     """
112
113     # classes attributes
114
115     OAR_REQUEST_POST_URI_DICT = {'POST_job': {'uri': '/oarapi/jobs.json'},
116                                  'DELETE_jobs_id':
117                                  {'uri': '/oarapi/jobs/id.json'},
118                                  }
119
120     POST_FORMAT = {'json': {'content': "application/json", 'object': json}}
121
122     #OARpostdatareqfields = {'resource' :"/nodes=", 'command':"sleep", \
123                             #'workdir':"/home/", 'walltime':""}
124
125     def __init__(self, config_file='/etc/sfa/oar_config.py'):
126         self.oarserver = {}
127         self.oarserver['uri'] = None
128         self.oarserver['postformat'] = 'json'
129
130         try:
131             execfile(config_file, self.__dict__)
132
133             self.config_file = config_file
134             # path to configuration data
135             self.config_path = os.path.dirname(config_file)
136
137         except IOError:
138             raise IOError, "Could not find or load the configuration file: %s" \
139                             % config_file
140         #logger.setLevelDebug()
141         self.oarserver['ip'] = self.OAR_IP
142         self.oarserver['port'] = self.OAR_PORT
143         self.jobstates = ['Terminated', 'Hold', 'Waiting', 'toLaunch',
144                           'toError', 'toAckReservation', 'Launching',
145                           'Finishing', 'Running', 'Suspended', 'Resuming',
146                           'Error']
147
148         self.parser = OARGETParser(self)
149
150
151     def GETRequestToOARRestAPI(self, request, strval=None,
152                             next_page=None, username=None):
153
154         """Makes a GET request to OAR.
155
156         Fetch the uri associated with the resquest stored in
157         OARrequests_uri_dict, adds the username if needed and if available, adds
158         strval to the request uri if needed, connects to OAR and issues the GET
159         request. Gets the json reply.
160
161         :param request: One of the known get requests that are keys in the
162             OARrequests_uri_dict.
163         :param strval: used when a job id has to be specified.
164         :param next_page: used to tell OAR to send the next page for this
165             Get request. Is appended to the GET uri.
166         :param username: used when a username has to be specified, when looking
167             for jobs scheduled by a particular user  for instance.
168
169         :type request: string
170         :type strval: integer
171         :type next_page: boolean
172         :type username: string
173         :returns: a json dictionary if OAR successfully processed the GET
174             request.
175
176         .. seealso:: OARrequests_uri_dict
177         """
178         self.oarserver['uri'] = \
179             OARGETParser.OARrequests_uri_dict[request]['uri']
180         #Get job details with username
181         if 'owner' in OARGETParser.OARrequests_uri_dict[request] and username:
182             self.oarserver['uri'] += \
183                 OARGETParser.OARrequests_uri_dict[request]['owner'] + username
184         headers = {}
185         data = json.dumps({})
186         logger.debug("OARrestapi \tGETRequestToOARRestAPI %s" % (request))
187         if strval:
188             self.oarserver['uri'] = self.oarserver['uri'].\
189                                             replace("id",str(strval))
190
191         if next_page:
192             self.oarserver['uri'] += next_page
193
194         if username:
195             headers['X-REMOTE_IDENT'] = username
196
197         logger.debug("OARrestapi: \t  GETRequestToOARRestAPI  \
198                         self.oarserver['uri'] %s strval %s" \
199                         %(self.oarserver['uri'], strval))
200         try :
201             #seems that it does not work if we don't add this
202             headers['content-length'] = '0'
203
204             conn = HTTPConnection(self.oarserver['ip'], \
205                                                 self.oarserver['port'])
206             conn.request("GET", self.oarserver['uri'], data, headers)
207             resp = ( conn.getresponse()).read()
208             conn.close()
209
210         except HTTPException, error :
211             logger.log_exc("GET_OAR_SRVR : Problem with OAR server : %s " \
212                                                                     %(error))
213             #raise ServerError("GET_OAR_SRVR : Could not reach OARserver")
214         try:
215             js_dict = json.loads(resp)
216             #print "\r\n \t\t\t js_dict keys" , js_dict.keys(), " \r\n", js_dict
217             return js_dict
218
219         except ValueError, error:
220             logger.log_exc("Failed to parse Server Response: %s ERROR %s"\
221                                                             %(js_dict, error))
222             #raise ServerError("Failed to parse Server Response:" + js)
223
224
225     def POSTRequestToOARRestAPI(self, request, datadict, username=None):
226         """ Used to post a job on OAR , along with data associated
227         with the job.
228
229         """
230
231         #first check that all params for are OK
232         try:
233             self.oarserver['uri'] = \
234                 self.OAR_REQUEST_POST_URI_DICT[request]['uri']
235
236         except KeyError:
237             logger.log_exc("OARrestapi \tPOSTRequestToOARRestAPI request not \
238                              valid")
239             return
240         if datadict and 'strval' in datadict:
241             self.oarserver['uri'] = self.oarserver['uri'].replace("id", \
242                                                 str(datadict['strval']))
243             del datadict['strval']
244
245         data = json.dumps(datadict)
246         headers = {'X-REMOTE_IDENT':username, \
247                 'content-type': self.POST_FORMAT['json']['content'], \
248                 'content-length':str(len(data))}
249         try :
250
251             conn = HTTPConnection(self.oarserver['ip'], \
252                                         self.oarserver['port'])
253             conn.request("POST", self.oarserver['uri'], data, headers)
254             resp = (conn.getresponse()).read()
255             conn.close()
256         except NotConnected:
257             logger.log_exc("POSTRequestToOARRestAPI NotConnected ERROR: \
258                             data %s \r\n \t\n \t\t headers %s uri %s" \
259                             %(data,headers,self.oarserver['uri']))
260
261             #raise ServerError("POST_OAR_SRVR : error")
262
263         try:
264             answer = json.loads(resp)
265             logger.debug("POSTRequestToOARRestAPI : answer %s" % (answer))
266             return answer
267
268         except ValueError, error:
269             logger.log_exc("Failed to parse Server Response: error %s  \
270                             %s" %(error))
271             #raise ServerError("Failed to parse Server Response:" + answer)
272
273
274 class ParsingResourcesFull():
275     """
276     Class dedicated to parse the json response from a GET_resources_full from
277     OAR.
278
279     """
280     def __init__(self):
281         """
282         Set the parsing dictionary. Works like a switch case, if the key is
283         found in the dictionary, then the associated function is called.
284         This is used in ParseNodes to create an usable dictionary from
285         the Json returned by OAR when issuing a GET resources full request.
286
287         .. seealso:: ParseNodes
288
289         """
290         self.resources_fulljson_dict = {
291         'network_address': self.AddNodeNetworkAddr,
292         'site':  self.AddNodeSite,
293         # 'radio':  self.AddNodeRadio,
294         'mobile':  self.AddMobility,
295         'x':  self.AddPosX,
296         'y':  self.AddPosY,
297         'z': self.AddPosZ,
298         'archi': self.AddHardwareType,
299         'state': self.AddBootState,
300         'id': self.AddOarNodeId,
301         }
302
303
304
305     def AddOarNodeId(self, tuplelist, value):
306         """Adds Oar internal node id to the nodes' attributes.
307
308         Appends tuple ('oar_id', node_id) to the tuplelist. Used by ParseNodes.
309
310         .. seealso:: ParseNodes
311
312         """
313
314         tuplelist.append(('oar_id', int(value)))
315
316
317     def AddNodeNetworkAddr(self, dictnode, value):
318         """First parsing function to be called to parse the json returned by OAR
319         answering a GET_resources (/oarapi/resources.json) request.
320
321         When a new node is found in the json, this function is responsible for
322         creating a new entry in the dictionary for storing information on this
323         specific node. The key is the node network address, which is also the
324         node's hostname.
325         The value associated with the key is a tuple list.It contains all
326         the nodes attributes. The tuplelist will later be turned into a dict.
327
328         :param dictnode: should be set to the OARGETParser atribute
329             node_dictlist. It will store the information on the nodes.
330         :param value: the node_id is the network_address in the raw json.
331         :type value: string
332         :type dictnode: dictionary
333
334         .. seealso: ParseResources, ParseNodes
335         """
336
337         node_id = value
338         dictnode[node_id] = [('node_id', node_id),('hostname', node_id) ]
339
340         return node_id
341
342     def AddNodeSite(self, tuplelist, value):
343         """Add the site's node to the dictionary.
344
345
346         :param tuplelist: tuple list on which to add the node's site.
347             Contains the other node attributes as well.
348         :param value: value to add to the tuple list, in this case the node's
349             site.
350         :type tuplelist: list
351         :type value: string
352
353         .. seealso:: AddNodeNetworkAddr
354
355         """
356         tuplelist.append(('site', str(value)))
357
358     # def AddNodeRadio(tuplelist, value):
359     #     """Add thenode's radio chipset type to the tuple list.
360
361     #     :param tuplelist: tuple list on which to add the node's mobility
362                 # status. The tuplelist is the value associated with the node's
363                 # id in the OARGETParser
364     #          's dictionary node_dictlist.
365     #     :param value: name of the radio chipset on the node.
366     #     :type tuplelist: list
367     #     :type value: string
368
369     #     .. seealso:: AddNodeNetworkAddr
370
371     #     """
372     #     tuplelist.append(('radio', str(value)))
373
374
375     def AddMobility(self, tuplelist, value):
376         """Add if the node is a mobile node or not to the tuple list.
377
378         :param tuplelist: tuple list on which to add the node's mobility status.
379             The tuplelist is the value associated with the node's id in the
380             OARGETParser's dictionary node_dictlist.
381         :param value: tells if a node is a mobile node or not. The value is found
382             in the json.
383
384         :type tuplelist: list
385         :type value: integer
386
387         .. seealso:: AddNodeNetworkAddr
388
389         """
390         if value is 0:
391             tuplelist.append(('mobile', 'False'))
392         else:
393             tuplelist.append(('mobile', 'True'))
394
395
396     def AddPosX(self, tuplelist, value):
397         """Add the node's position on the x axis.
398
399         :param tuplelist: tuple list on which to add the node's position . The
400             tuplelist is the value associated with the node's id in the
401             OARGETParser's dictionary node_dictlist.
402         :param value: the position x.
403
404         :type tuplelist: list
405         :type value: integer
406
407          .. seealso:: AddNodeNetworkAddr
408
409         """
410         tuplelist.append(('posx', value ))
411
412
413
414     def AddPosY(self, tuplelist, value):
415         """Add the node's position on the y axis.
416
417         :param tuplelist: tuple list on which to add the node's position . The
418             tuplelist is the value associated with the node's id in the
419             OARGETParser's dictionary node_dictlist.
420         :param value: the position y.
421
422         :type tuplelist: list
423         :type value: integer
424
425          .. seealso:: AddNodeNetworkAddr
426
427         """
428         tuplelist.append(('posy', value))
429
430
431
432     def AddPosZ(self, tuplelist, value):
433         """Add the node's position on the z axis.
434
435         :param tuplelist: tuple list on which to add the node's position . The
436             tuplelist is the value associated with the node's id in the
437             OARGETParser's dictionary node_dictlist.
438         :param value: the position z.
439
440         :type tuplelist: list
441         :type value: integer
442
443          .. seealso:: AddNodeNetworkAddr
444
445         """
446
447         tuplelist.append(('posz', value))
448
449
450
451     def AddBootState(tself, tuplelist, value):
452         """Add the node's state, Alive or Suspected.
453
454         :param tuplelist: tuple list on which to add the node's state . The
455             tuplelist is the value associated with the node's id in the
456             OARGETParser 's dictionary node_dictlist.
457         :param value: node's state.
458
459         :type tuplelist: list
460         :type value: string
461
462          .. seealso:: AddNodeNetworkAddr
463
464         """
465         tuplelist.append(('boot_state', str(value)))
466
467
468     def AddHardwareType(self, tuplelist, value):
469         """Add the node's hardware model and radio chipset type to the tuple
470         list.
471
472         :param tuplelist: tuple list on which to add the node's architecture
473             and radio chipset type.
474         :param value: hardware type: radio chipset. The value contains both the
475             architecture and the radio chipset, separated by a colon.
476         :type tuplelist: list
477         :type value: string
478
479         .. seealso:: AddNodeNetworkAddr
480
481         """
482
483         value_list = value.split(':')
484         tuplelist.append(('archi', value_list[0]))
485         tuplelist.append(('radio', value_list[1]))
486
487
488 class OARGETParser:
489     # resources_fulljson_dict = {
490     #     'network_address': AddNodeNetworkAddr,
491     #     'site': AddNodeSite,
492     #     # 'radio': AddNodeRadio,
493     #     'mobile': AddMobility,
494     #     'x': AddPosX,
495     #     'y': AddPosY,
496     #     'z':AddPosZ,
497     #     'archi':AddHardwareType,
498     #     'state':AddBootState,
499     #     'id' : AddOarNodeId,
500     #     }
501
502
503     def __init__(self, srv) :
504         self.version_json_dict = {
505             'api_version' : None , 'apilib_version' :None,\
506             'api_timezone': None, 'api_timestamp': None, 'oar_version': None ,}
507         self.config = Config()
508         self.interface_hrn = self.config.SFA_INTERFACE_HRN
509         self.timezone_json_dict = {
510             'timezone': None, 'api_timestamp': None, }
511         #self.jobs_json_dict = {
512             #'total' : None, 'links' : [],\
513             #'offset':None , 'items' : [], }
514         #self.jobs_table_json_dict = self.jobs_json_dict
515         #self.jobs_details_json_dict = self.jobs_json_dict
516         self.server = srv
517         self.node_dictlist = {}
518
519         self.json_page = JsonPage()
520         self.parsing_resourcesfull = ParsingResourcesFull()
521         self.site_dict = {}
522         self.SendRequest("GET_version")
523
524
525
526
527
528     def ParseVersion(self) :
529         """Parses the OAR answer to the GET_version ( /oarapi/version.json.)
530
531         Finds the OAR apilib version currently used. Has an impact on the json
532         structure returned by OAR, so the version has to be known before trying
533         to parse the jsons returned after a get request has been issued.
534         Updates the attribute version_json_dict.
535
536         """
537
538         if 'oar_version' in self.json_page.raw_json :
539             self.version_json_dict.update(api_version =
540                                         self.json_page.raw_json['api_version'],
541                     apilib_version=self.json_page.raw_json['apilib_version'],
542                     api_timezone=self.json_page.raw_json['api_timezone'],
543                     api_timestamp=self.json_page.raw_json['api_timestamp'],
544                     oar_version=self.json_page.raw_json['oar_version'])
545         else:
546             self.version_json_dict.update(api_version =
547                         self.json_page.raw_json['api'],
548                         apilib_version=self.json_page.raw_json['apilib'],
549                         api_timezone=self.json_page.raw_json['api_timezone'],
550                         api_timestamp=self.json_page.raw_json['api_timestamp'],
551                         oar_version=self.json_page.raw_json['oar'])
552
553         print self.version_json_dict['apilib_version']
554
555
556     def ParseTimezone(self):
557         """Get the timezone used by OAR.
558
559         Get the timezone from the answer to the GET_timezone request.
560         :return: api_timestamp and api timezone.
561         :rype: integer, integer
562
563         .. warning:: unused.
564         """
565         api_timestamp = self.json_page.raw_json['api_timestamp']
566         api_tz = self.json_page.raw_json['timezone']
567         return api_timestamp, api_tz
568
569     def ParseJobs(self):
570         """Called when a GET_jobs request has been issued to OAR.
571
572         Corresponds to /oarapi/jobs.json uri. Currently returns the raw json
573         information dict.
574         :returns: json_page.raw_json
575         :rtype: dictionary
576
577         .. warning:: Does not actually parse the information in the json. SA
578             15/07/13.
579
580         """
581         self.jobs_list = []
582         print " ParseJobs "
583         return self.json_page.raw_json
584
585     def ParseJobsTable(self):
586         """In case we need to use the job table in the future.
587
588         Associated with the GET_jobs_table : '/oarapi/jobs/table.json uri.
589         .. warning:: NOT USED. DOES NOTHING.
590         """
591         print "ParseJobsTable"
592
593     def ParseJobsDetails(self):
594         """Currently only returns the same json in self.json_page.raw_json.
595
596         .. todo:: actually parse the json
597         .. warning:: currently, this function is not used a lot, so I have no
598             idea what could  be useful to parse, returning the full json. NT
599         """
600
601         #logger.debug("ParseJobsDetails %s " %(self.json_page.raw_json))
602         return self.json_page.raw_json
603
604
605     def ParseJobsIds(self):
606         """Associated with the GET_jobs_id OAR request.
607
608         Parses the json dict (OAR answer) to the GET_jobs_id request
609         /oarapi/jobs/id.json.
610
611
612         :returns: dictionary whose keys are listed in the local variable
613             job_resources and values that are in the json dictionary returned
614             by OAR with the job information.
615         :rtype: dict
616
617         """
618         job_resources = ['wanted_resources', 'name', 'id', 'start_time',
619                         'state', 'owner', 'walltime', 'message']
620
621
622         job_resources_full = ['launching_directory', 'links',
623             'resubmit_job_id', 'owner', 'events', 'message',
624             'scheduled_start', 'id', 'array_id',  'exit_code',
625             'properties', 'state','array_index', 'walltime',
626             'type', 'initial_request', 'stop_time', 'project',
627             'start_time',  'dependencies','api_timestamp','submission_time',
628             'reservation', 'stdout_file', 'types', 'cpuset_name',
629             'name',  'wanted_resources','queue','stderr_file','command']
630
631
632         job_info = self.json_page.raw_json
633         #logger.debug("OARESTAPI ParseJobsIds %s" %(self.json_page.raw_json))
634         values = []
635         try:
636             for k in job_resources:
637                 values.append(job_info[k])
638             return dict(zip(job_resources, values))
639
640         except KeyError:
641             logger.log_exc("ParseJobsIds KeyError ")
642
643
644     def ParseJobsIdResources(self):
645         """ Parses the json produced by the request
646         /oarapi/jobs/id/resources.json.
647         Returns a list of oar node ids that are scheduled for the
648         given job id.
649
650         """
651         job_resources = []
652         for resource in self.json_page.raw_json['items']:
653             job_resources.append(resource['id'])
654
655         return job_resources
656
657     def ParseResources(self) :
658         """ Parses the json produced by a get_resources request on oar."""
659
660         #logger.debug("OARESTAPI \tParseResources " )
661         #resources are listed inside the 'items' list from the json
662         self.json_page.raw_json = self.json_page.raw_json['items']
663         self.ParseNodes()
664
665     def ParseReservedNodes(self):
666         """  Returns an array containing the list of the reserved nodes """
667
668         #resources are listed inside the 'items' list from the json
669         reservation_list = []
670         job = {}
671         #Parse resources info
672         for json_element in  self.json_page.raw_json['items']:
673             #In case it is a real reservation (not asap case)
674             if json_element['scheduled_start']:
675                 job['t_from'] = json_element['scheduled_start']
676                 job['t_until'] = int(json_element['scheduled_start']) + \
677                                                 int(json_element['walltime'])
678                 #Get resources id list for the job
679                 job['resource_ids'] = [ node_dict['id'] for node_dict
680                                         in json_element['resources']]
681             else:
682                 job['t_from'] = "As soon as possible"
683                 job['t_until'] = "As soon as possible"
684                 job['resource_ids'] = ["Undefined"]
685
686
687             job['state'] = json_element['state']
688             job['lease_id'] = json_element['id']
689
690
691             job['user'] = json_element['owner']
692             #logger.debug("OARRestapi \tParseReservedNodes job %s" %(job))
693             reservation_list.append(job)
694             #reset dict
695             job = {}
696         return reservation_list
697
698     def ParseRunningJobs(self):
699         """ Gets the list of nodes currently in use from the attributes of the
700         running jobs.
701
702         """
703         logger.debug("OARESTAPI \tParseRunningJobs__________________________ ")
704         #resources are listed inside the 'items' list from the json
705         nodes = []
706         for job in  self.json_page.raw_json['items']:
707             for node in job['nodes']:
708                 nodes.append(node['network_address'])
709         return nodes
710
711     def ChangeRawJsonDependingOnApilibVersion(self):
712
713         if self.version_json_dict['apilib_version'] != "0.2.10":
714             self.json_page.raw_json = self.json_page.raw_json['items']
715
716     def ParseDeleteJobs(self):
717         """ No need to parse anything in this function.A POST
718         is done to delete the job.
719
720         """
721         return
722
723     def ParseResourcesFull(self) :
724         """ This method is responsible for parsing all the attributes
725         of all the nodes returned by OAR when issuing a get resources full.
726         The information from the nodes and the sites are separated.
727         Updates the node_dictlist so that the dictionnary of the platform's
728         nodes is available afterwards.
729
730         """
731         logger.debug("OARRESTAPI ParseResourcesFull________________________ ")
732         #print self.json_page.raw_json[1]
733         #resources are listed inside the 'items' list from the json
734         self.ChangeRawJsonDependingOnApilibVersion()
735         self.ParseNodes()
736         self.ParseSites()
737         return self.node_dictlist
738
739     def ParseResourcesFullSites(self) :
740         """ UNUSED. Originally used to get information from the sites.
741         ParseResourcesFull is used instead.
742
743         """
744         self.ChangeRawJsonDependingOnApilibVersion()
745         self.ParseNodes()
746         self.ParseSites()
747         return self.site_dict
748
749
750     def ParseNodes(self):
751         """ Parse nodes properties from OAR
752         Put them into a dictionary with key = node id and value is a dictionary
753         of the node properties and properties'values.
754
755         """
756         node_id = None
757         _resources_fulljson_dict = \
758             self.parsing_resourcesfull.resources_fulljson_dict
759         keys = _resources_fulljson_dict.keys()
760         keys.sort()
761
762         for dictline in self.json_page.raw_json:
763             node_id = None
764             # dictionary is empty and/or a new node has to be inserted
765             node_id = _resources_fulljson_dict['network_address'](\
766                                 self.node_dictlist, dictline['network_address'])
767             for k in keys:
768                 if k in dictline:
769                     if k == 'network_address':
770                         continue
771
772                     _resources_fulljson_dict[k](\
773                                     self.node_dictlist[node_id], dictline[k])
774
775             #The last property has been inserted in the property tuple list,
776             #reset node_id
777             #Turn the property tuple list (=dict value) into a dictionary
778             self.node_dictlist[node_id] = dict(self.node_dictlist[node_id])
779             node_id = None
780
781     @staticmethod
782     def iotlab_hostname_to_hrn(root_auth,  hostname):
783         """
784         Transforms a node hostname into a SFA hrn.
785
786         :param root_auth: Name of the root authority of the SFA server. In
787             our case, it is set to iotlab.
788         :param hostname: node's hotname, given by OAR.
789         :type root_auth: string
790         :type hostname: string
791         :returns: inserts the root_auth and '.' before the hostname.
792         :rtype: string
793
794         """
795         return root_auth + '.' + hostname
796
797
798
799     def ParseSites(self):
800         """ Returns a list of dictionnaries containing the sites' attributes."""
801
802         nodes_per_site = {}
803         config = Config()
804         #logger.debug(" OARrestapi.py \tParseSites  self.node_dictlist %s"\
805                                                         #%(self.node_dictlist))
806         # Create a list of nodes per site_id
807         for node_id in self.node_dictlist:
808             node  = self.node_dictlist[node_id]
809
810             if node['site'] not in nodes_per_site:
811                 nodes_per_site[node['site']] = []
812                 nodes_per_site[node['site']].append(node['node_id'])
813             else:
814                 if node['node_id'] not in nodes_per_site[node['site']]:
815                     nodes_per_site[node['site']].append(node['node_id'])
816
817         #Create a site dictionary whose key is site_login_base
818         # (name of the site) and value is a dictionary of properties,
819         # including the list of the node_ids
820         for node_id in self.node_dictlist:
821             node  = self.node_dictlist[node_id]
822             node.update({'hrn':self.iotlab_hostname_to_hrn(self.interface_hrn,
823                                                            node['hostname'])})
824             self.node_dictlist.update({node_id:node})
825
826             if node['site'] not in self.site_dict:
827                 self.site_dict[node['site']] = {
828                     'site':node['site'],
829                     'node_ids':nodes_per_site[node['site']],
830                     'latitude':"48.83726",
831                     'longitude':"- 2.10336",
832                     'name': config.SFA_REGISTRY_ROOT_AUTH,
833                     'pcu_ids':[], 'max_slices':None, 'ext_consortium_id':None,
834                     'max_slivers':None, 'is_public':True, 'peer_site_id': None,
835                     'abbreviated_name':"iotlab", 'address_ids': [],
836                     'url':"http,//www.senslab.info", 'person_ids':[],
837                     'site_tag_ids':[], 'enabled': True,  'slice_ids':[],
838                     'date_created': None, 'peer_id': None }
839
840
841     OARrequests_uri_dict = {
842         'GET_version':
843                 {'uri':'/oarapi/version.json', 'parse_func': ParseVersion},
844         'GET_timezone':
845                 {'uri':'/oarapi/timezone.json' ,'parse_func': ParseTimezone },
846         'GET_jobs':
847                 {'uri':'/oarapi/jobs.json','parse_func': ParseJobs},
848         'GET_jobs_id':
849                 {'uri':'/oarapi/jobs/id.json','parse_func': ParseJobsIds},
850         'GET_jobs_id_resources':
851                 {'uri':'/oarapi/jobs/id/resources.json',\
852                 'parse_func': ParseJobsIdResources},
853         'GET_jobs_table':
854                 {'uri':'/oarapi/jobs/table.json','parse_func': ParseJobsTable},
855         'GET_jobs_details':
856                 {'uri':'/oarapi/jobs/details.json',\
857                 'parse_func': ParseJobsDetails},
858         'GET_reserved_nodes':
859                 {'uri':
860                 '/oarapi/jobs/details.json?state=Running,Waiting,Launching',\
861                 'owner':'&user=',
862                 'parse_func':ParseReservedNodes},
863
864
865         'GET_running_jobs':
866                 {'uri':'/oarapi/jobs/details.json?state=Running',\
867                 'parse_func':ParseRunningJobs},
868         'GET_resources_full':
869                 {'uri':'/oarapi/resources/full.json',\
870                 'parse_func': ParseResourcesFull},
871         'GET_sites':
872                 {'uri':'/oarapi/resources/full.json',\
873                 'parse_func': ParseResourcesFullSites},
874         'GET_resources':
875                 {'uri':'/oarapi/resources.json' ,'parse_func': ParseResources},
876         'DELETE_jobs_id':
877                 {'uri':'/oarapi/jobs/id.json' ,'parse_func': ParseDeleteJobs}
878         }
879
880
881
882
883     def SendRequest(self, request, strval = None , username = None):
884         """ Connects to OAR , sends the valid GET requests and uses
885         the appropriate json parsing functions.
886
887         """
888         save_json = None
889
890         self.json_page.ResetNextPage()
891         save_json = []
892
893         if request in self.OARrequests_uri_dict :
894             while self.json_page.next_page:
895                 self.json_page.raw_json = self.server.GETRequestToOARRestAPI(\
896                                                 request, \
897                                                 strval, \
898                                                 self.json_page.next_offset, \
899                                                 username)
900                 self.json_page.FindNextPage()
901                 if self.json_page.concatenate:
902                     save_json.append(self.json_page.raw_json)
903
904             if self.json_page.concatenate and self.json_page.end :
905                 self.json_page.raw_json = \
906                     self.json_page.ConcatenateJsonPages(save_json)
907
908             return self.OARrequests_uri_dict[request]['parse_func'](self)
909         else:
910             logger.error("OARRESTAPI OARGetParse __init__ : ERROR_REQUEST " \
911                                                                  %(request))