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