changing mobility-type to mobility_type
[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()).read()
217             conn.close()
218
219         except HTTPException, error:
220             logger.log_exc("GET_OAR_SRVR : Problem with OAR server : %s "
221                            % (error))
222             #raise ServerError("GET_OAR_SRVR : Could not reach OARserver")
223         try:
224             js_dict = json.loads(resp)
225             #print "\r\n \t\t\t js_dict keys" , js_dict.keys(), " \r\n", js_dict
226             return js_dict
227
228         except ValueError, error:
229             logger.log_exc("Failed to parse Server Response: %s ERROR %s"
230                            % (js_dict, error))
231             #raise ServerError("Failed to parse Server Response:" + js)
232
233
234     def POSTRequestToOARRestAPI(self, request, datadict, username=None):
235         """ Used to post a job on OAR , along with data associated
236         with the job.
237
238         """
239
240         #first check that all params for are OK
241         try:
242             self.oarserver['uri'] = \
243                 self.OAR_REQUEST_POST_URI_DICT[request]['uri']
244
245         except KeyError:
246             logger.log_exc("OARrestapi \tPOSTRequestToOARRestAPI request not \
247                              valid")
248             return
249         if datadict and 'strval' in datadict:
250             self.oarserver['uri'] = self.oarserver['uri'].replace("id", \
251                                                 str(datadict['strval']))
252             del datadict['strval']
253
254         data = json.dumps(datadict)
255         headers = {'X-REMOTE_IDENT':username, \
256                 'content-type': self.POST_FORMAT['json']['content'], \
257                 'content-length':str(len(data))}
258         try :
259
260             conn = HTTPConnection(self.oarserver['ip'], \
261                                         self.oarserver['port'])
262             conn.request("POST", self.oarserver['uri'], data, headers)
263             resp = (conn.getresponse()).read()
264             conn.close()
265         except NotConnected:
266             logger.log_exc("POSTRequestToOARRestAPI NotConnected ERROR: \
267                             data %s \r\n \t\n \t\t headers %s uri %s" \
268                             %(data,headers,self.oarserver['uri']))
269
270             #raise ServerError("POST_OAR_SRVR : error")
271
272         try:
273             answer = json.loads(resp)
274             logger.debug("POSTRequestToOARRestAPI : answer %s" % (answer))
275             return answer
276
277         except ValueError, error:
278             logger.log_exc("Failed to parse Server Response: error %s  \
279                             %s" %(error))
280             #raise ServerError("Failed to parse Server Response:" + answer)
281
282
283 class ParsingResourcesFull():
284     """
285     Class dedicated to parse the json response from a GET_resources_full from
286     OAR.
287
288     """
289     def __init__(self):
290         """
291         Set the parsing dictionary. Works like a switch case, if the key is
292         found in the dictionary, then the associated function is called.
293         This is used in ParseNodes to create an usable dictionary from
294         the Json returned by OAR when issuing a GET resources full request.
295
296         .. seealso:: ParseNodes
297
298         """
299         self.resources_fulljson_dict = {
300         'network_address': self.AddNodeNetworkAddr,
301         'site':  self.AddNodeSite,
302         # 'radio':  self.AddNodeRadio,
303         'mobile':  self.AddMobility,
304         'x':  self.AddPosX,
305         'y':  self.AddPosY,
306         'z': self.AddPosZ,
307         'archi': self.AddHardwareType,
308         'state': self.AddBootState,
309         'id': self.AddOarNodeId,
310         'mobility_type': self.AddMobilityType,
311         }
312
313
314
315     def AddOarNodeId(self, tuplelist, value):
316         """Adds Oar internal node id to the nodes' attributes.
317
318         Appends tuple ('oar_id', node_id) to the tuplelist. Used by ParseNodes.
319
320         .. seealso:: ParseNodes
321
322         """
323
324         tuplelist.append(('oar_id', int(value)))
325
326
327     def AddNodeNetworkAddr(self, dictnode, value):
328         """First parsing function to be called to parse the json returned by OAR
329         answering a GET_resources (/oarapi/resources.json) request.
330
331         When a new node is found in the json, this function is responsible for
332         creating a new entry in the dictionary for storing information on this
333         specific node. The key is the node network address, which is also the
334         node's hostname.
335         The value associated with the key is a tuple list.It contains all
336         the nodes attributes. The tuplelist will later be turned into a dict.
337
338         :param dictnode: should be set to the OARGETParser atribute
339             node_dictlist. It will store the information on the nodes.
340         :param value: the node_id is the network_address in the raw json.
341         :type value: string
342         :type dictnode: dictionary
343
344         .. seealso: ParseResources, ParseNodes
345         """
346
347         node_id = value
348         dictnode[node_id] = [('node_id', node_id),('hostname', node_id) ]
349
350         return node_id
351
352     def AddNodeSite(self, tuplelist, value):
353         """Add the site's node to the dictionary.
354
355
356         :param tuplelist: tuple list on which to add the node's site.
357             Contains the other node attributes as well.
358         :param value: value to add to the tuple list, in this case the node's
359             site.
360         :type tuplelist: list
361         :type value: string
362
363         .. seealso:: AddNodeNetworkAddr
364
365         """
366         tuplelist.append(('site', str(value)))
367
368     # def AddNodeRadio(tuplelist, value):
369     #     """Add thenode's radio chipset type to the tuple list.
370
371     #     :param tuplelist: tuple list on which to add the node's mobility
372                 # status. The tuplelist is the value associated with the node's
373                 # id in the OARGETParser
374     #          's dictionary node_dictlist.
375     #     :param value: name of the radio chipset on the node.
376     #     :type tuplelist: list
377     #     :type value: string
378
379     #     .. seealso:: AddNodeNetworkAddr
380
381     #     """
382     #     tuplelist.append(('radio', str(value)))
383
384     def AddMobilityType(self, tuplelist, value):
385         """Adds  which kind of mobility it is, train or roomba robot.
386
387         :param tuplelist: tuple list on which to add the node's mobility status.
388             The tuplelist is the value associated with the node's id in the
389             OARGETParser's dictionary node_dictlist.
390         :param value: tells if a node is a mobile node or not. The value is
391             found in the json.
392
393         :type tuplelist: list
394         :type value: integer
395
396         """
397         tuplelist.append(('mobility_type', str(value)))
398
399
400     def AddMobility(self, tuplelist, value):
401         """Add if the node is a mobile node or not to the tuple list.
402
403         :param tuplelist: tuple list on which to add the node's mobility status.
404             The tuplelist is the value associated with the node's id in the
405             OARGETParser's dictionary node_dictlist.
406         :param value: tells if a node is a mobile node or not. The value is found
407             in the json.
408
409         :type tuplelist: list
410         :type value: integer
411
412         .. seealso:: AddNodeNetworkAddr
413
414         """
415         if value is 0:
416             tuplelist.append(('mobile', 'False'))
417         else:
418             tuplelist.append(('mobile', 'True'))
419
420
421     def AddPosX(self, tuplelist, value):
422         """Add the node's position on the x axis.
423
424         :param tuplelist: tuple list on which to add the node's position . The
425             tuplelist is the value associated with the node's id in the
426             OARGETParser's dictionary node_dictlist.
427         :param value: the position x.
428
429         :type tuplelist: list
430         :type value: integer
431
432          .. seealso:: AddNodeNetworkAddr
433
434         """
435         tuplelist.append(('posx', value ))
436
437
438
439     def AddPosY(self, tuplelist, value):
440         """Add the node's position on the y axis.
441
442         :param tuplelist: tuple list on which to add the node's position . The
443             tuplelist is the value associated with the node's id in the
444             OARGETParser's dictionary node_dictlist.
445         :param value: the position y.
446
447         :type tuplelist: list
448         :type value: integer
449
450          .. seealso:: AddNodeNetworkAddr
451
452         """
453         tuplelist.append(('posy', value))
454
455
456
457     def AddPosZ(self, tuplelist, value):
458         """Add the node's position on the z axis.
459
460         :param tuplelist: tuple list on which to add the node's position . The
461             tuplelist is the value associated with the node's id in the
462             OARGETParser's dictionary node_dictlist.
463         :param value: the position z.
464
465         :type tuplelist: list
466         :type value: integer
467
468          .. seealso:: AddNodeNetworkAddr
469
470         """
471
472         tuplelist.append(('posz', value))
473
474
475
476     def AddBootState(tself, tuplelist, value):
477         """Add the node's state, Alive or Suspected.
478
479         :param tuplelist: tuple list on which to add the node's state . The
480             tuplelist is the value associated with the node's id in the
481             OARGETParser 's dictionary node_dictlist.
482         :param value: node's state.
483
484         :type tuplelist: list
485         :type value: string
486
487          .. seealso:: AddNodeNetworkAddr
488
489         """
490         tuplelist.append(('boot_state', str(value)))
491
492
493     def AddHardwareType(self, tuplelist, value):
494         """Add the node's hardware model and radio chipset type to the tuple
495         list.
496
497         :param tuplelist: tuple list on which to add the node's architecture
498             and radio chipset type.
499         :param value: hardware type: radio chipset. The value contains both the
500             architecture and the radio chipset, separated by a colon.
501         :type tuplelist: list
502         :type value: string
503
504         .. seealso:: AddNodeNetworkAddr
505
506         """
507
508         value_list = value.split(':')
509         tuplelist.append(('archi', value_list[0]))
510         tuplelist.append(('radio', value_list[1]))
511
512
513 class OARGETParser:
514     """Class providing parsing methods associated to specific GET requests.
515
516     """
517
518     def __init__(self, srv):
519         self.version_json_dict = {
520             'api_version': None, 'apilib_version': None,
521             'api_timezone': None, 'api_timestamp': None, 'oar_version': None}
522         self.config = Config()
523         self.interface_hrn = self.config.SFA_INTERFACE_HRN
524         self.timezone_json_dict = {
525             'timezone': None, 'api_timestamp': None, }
526         #self.jobs_json_dict = {
527             #'total' : None, 'links' : [],\
528             #'offset':None , 'items' : [], }
529         #self.jobs_table_json_dict = self.jobs_json_dict
530         #self.jobs_details_json_dict = self.jobs_json_dict
531         self.server = srv
532         self.node_dictlist = {}
533
534         self.json_page = JsonPage()
535         self.parsing_resourcesfull = ParsingResourcesFull()
536         self.site_dict = {}
537         self.jobs_list = []
538         self.SendRequest("GET_version")
539
540
541     def ParseVersion(self):
542         """Parses the OAR answer to the GET_version ( /oarapi/version.json.)
543
544         Finds the OAR apilib version currently used. Has an impact on the json
545         structure returned by OAR, so the version has to be known before trying
546         to parse the jsons returned after a get request has been issued.
547         Updates the attribute version_json_dict.
548
549         """
550
551         if 'oar_version' in self.json_page.raw_json:
552             self.version_json_dict.update(
553                 api_version=self.json_page.raw_json['api_version'],
554                 apilib_version=self.json_page.raw_json['apilib_version'],
555                 api_timezone=self.json_page.raw_json['api_timezone'],
556                 api_timestamp=self.json_page.raw_json['api_timestamp'],
557                 oar_version=self.json_page.raw_json['oar_version'])
558         else:
559             self.version_json_dict.update(
560                 api_version=self.json_page.raw_json['api'],
561                 apilib_version=self.json_page.raw_json['apilib'],
562                 api_timezone=self.json_page.raw_json['api_timezone'],
563                 api_timestamp=self.json_page.raw_json['api_timestamp'],
564                 oar_version=self.json_page.raw_json['oar'])
565
566         print self.version_json_dict['apilib_version']
567
568
569     def ParseTimezone(self):
570         """Get the timezone used by OAR.
571
572         Get the timezone from the answer to the GET_timezone request.
573         :return: api_timestamp and api timezone.
574         :rype: integer, integer
575
576         .. warning:: unused.
577         """
578         api_timestamp = self.json_page.raw_json['api_timestamp']
579         api_tz = self.json_page.raw_json['timezone']
580         return api_timestamp, api_tz
581
582     def ParseJobs(self):
583         """Called when a GET_jobs request has been issued to OAR.
584
585         Corresponds to /oarapi/jobs.json uri. Currently returns the raw json
586         information dict.
587         :returns: json_page.raw_json
588         :rtype: dictionary
589
590         .. warning:: Does not actually parse the information in the json. SA
591             15/07/13.
592
593         """
594         self.jobs_list = []
595         print " ParseJobs "
596         return self.json_page.raw_json
597
598     def ParseJobsTable(self):
599         """In case we need to use the job table in the future.
600
601         Associated with the GET_jobs_table : '/oarapi/jobs/table.json uri.
602         .. warning:: NOT USED. DOES NOTHING.
603         """
604         print "ParseJobsTable"
605
606     def ParseJobsDetails(self):
607         """Currently only returns the same json in self.json_page.raw_json.
608
609         .. todo:: actually parse the json
610         .. warning:: currently, this function is not used a lot, so I have no
611             idea what could  be useful to parse, returning the full json. NT
612         """
613
614         #logger.debug("ParseJobsDetails %s " %(self.json_page.raw_json))
615         return self.json_page.raw_json
616
617
618     def ParseJobsIds(self):
619         """Associated with the GET_jobs_id OAR request.
620
621         Parses the json dict (OAR answer) to the GET_jobs_id request
622         /oarapi/jobs/id.json.
623
624
625         :returns: dictionary whose keys are listed in the local variable
626             job_resources and values that are in the json dictionary returned
627             by OAR with the job information.
628         :rtype: dict
629
630         """
631         job_resources = ['wanted_resources', 'name', 'id', 'start_time',
632                          'state', 'owner', 'walltime', 'message']
633
634         # Unused variable providing the contents of the json dict returned from
635         # get job resources full request
636         job_resources_full = [
637             'launching_directory', 'links',
638             'resubmit_job_id', 'owner', 'events', 'message',
639             'scheduled_start', 'id', 'array_id', 'exit_code',
640             'properties', 'state', 'array_index', 'walltime',
641             'type', 'initial_request', 'stop_time', 'project',
642             'start_time',  'dependencies', 'api_timestamp', 'submission_time',
643             'reservation', 'stdout_file', 'types', 'cpuset_name',
644             'name', 'wanted_resources', 'queue', 'stderr_file', 'command']
645
646
647         job_info = self.json_page.raw_json
648         #logger.debug("OARESTAPI ParseJobsIds %s" %(self.json_page.raw_json))
649         values = []
650         try:
651             for k in job_resources:
652                 values.append(job_info[k])
653             return dict(zip(job_resources, values))
654
655         except KeyError:
656             logger.log_exc("ParseJobsIds KeyError ")
657
658
659     def ParseJobsIdResources(self):
660         """ Parses the json produced by the request
661         /oarapi/jobs/id/resources.json.
662         Returns a list of oar node ids that are scheduled for the
663         given job id.
664
665         """
666         job_resources = []
667         for resource in self.json_page.raw_json['items']:
668             job_resources.append(resource['id'])
669
670         return job_resources
671
672     def ParseResources(self):
673         """ Parses the json produced by a get_resources request on oar."""
674
675         #logger.debug("OARESTAPI \tParseResources " )
676         #resources are listed inside the 'items' list from the json
677         self.json_page.raw_json = self.json_page.raw_json['items']
678         self.ParseNodes()
679
680     def ParseReservedNodes(self):
681         """  Returns an array containing the list of the jobs scheduled
682         with the reserved nodes if available.
683
684         :returns: list of job dicts, each dict containing the following keys:
685             t_from, t_until, resources_ids (of the reserved nodes for this job).
686             If the information is not available, default values will be set for
687             these keys. The other keys are : state, lease_id and user.
688         :rtype: list
689
690         """
691
692         #resources are listed inside the 'items' list from the json
693         reservation_list = []
694         job = {}
695         #Parse resources info
696         for json_element in self.json_page.raw_json['items']:
697             #In case it is a real reservation (not asap case)
698             if json_element['scheduled_start']:
699                 job['t_from'] = json_element['scheduled_start']
700                 job['t_until'] = int(json_element['scheduled_start']) + \
701                     int(json_element['walltime'])
702                 #Get resources id list for the job
703                 job['resource_ids'] = [node_dict['id'] for node_dict
704                                        in json_element['resources']]
705             else:
706                 job['t_from'] = "As soon as possible"
707                 job['t_until'] = "As soon as possible"
708                 job['resource_ids'] = ["Undefined"]
709
710             job['state'] = json_element['state']
711             job['lease_id'] = json_element['id']
712
713             job['user'] = json_element['owner']
714             #logger.debug("OARRestapi \tParseReservedNodes job %s" %(job))
715             reservation_list.append(job)
716             #reset dict
717             job = {}
718         return reservation_list
719
720     def ParseRunningJobs(self):
721         """ Gets the list of nodes currently in use from the attributes of the
722         running jobs.
723
724         :returns: list of hostnames, the nodes that are currently involved in
725             running jobs.
726         :rtype: list
727
728
729         """
730         logger.debug("OARESTAPI \tParseRunningJobs_________________ ")
731         #resources are listed inside the 'items' list from the json
732         nodes = []
733         for job in self.json_page.raw_json['items']:
734             for node in job['nodes']:
735                 nodes.append(node['network_address'])
736         return nodes
737
738     def ChangeRawJsonDependingOnApilibVersion(self):
739         """
740         Check if the OAR apilib version is different from 0.2.10, in which case
741         the Json answer is also dict instead as a plain list.
742
743         .. warning:: the whole code is assuming the json contains a 'items' key
744         .. seealso:: ConcatenateJsonPages, ParseJobs, ParseReservedNodes,
745             ParseJobsIdResources, ParseResources, ParseRunningJobs
746         .. todo:: Clean the whole code. Either suppose the  apilib will always
747             provide the 'items' key, or handle different options.
748         """
749
750         if self.version_json_dict['apilib_version'] != "0.2.10":
751             self.json_page.raw_json = self.json_page.raw_json['items']
752
753     def ParseDeleteJobs(self):
754         """ No need to parse anything in this function.A POST
755         is done to delete the job.
756
757         """
758         return
759
760     def ParseResourcesFull(self):
761         """ This method is responsible for parsing all the attributes
762         of all the nodes returned by OAR when issuing a get resources full.
763         The information from the nodes and the sites are separated.
764         Updates the node_dictlist so that the dictionnary of the platform's
765         nodes is available afterwards.
766
767         :returns: node_dictlist, a list of dictionaries about the nodes and
768             their properties.
769         :rtype: list
770
771         """
772         logger.debug("OARRESTAPI ParseResourcesFull___________ ")
773         #print self.json_page.raw_json[1]
774         #resources are listed inside the 'items' list from the json
775         self.ChangeRawJsonDependingOnApilibVersion()
776         self.ParseNodes()
777         self.ParseSites()
778         return self.node_dictlist
779
780     def ParseResourcesFullSites(self):
781         """ Called by GetSites which is unused.
782         Originally used to get information from the sites, with for each site
783         the list of nodes it has, along with their properties.
784
785         :return: site_dict, dictionary of sites
786         :rtype: dict
787
788         .. warning:: unused
789         .. seealso:: GetSites (IotlabTestbedAPI)
790
791         """
792         self.ChangeRawJsonDependingOnApilibVersion()
793         self.ParseNodes()
794         self.ParseSites()
795         return self.site_dict
796
797
798     def ParseNodes(self):
799         """ Parse nodes properties from OAR
800         Put them into a dictionary with key = node id and value is a dictionary
801         of the node properties and properties'values.
802
803         """
804         node_id = None
805         _resources_fulljson_dict = \
806             self.parsing_resourcesfull.resources_fulljson_dict
807         keys = _resources_fulljson_dict.keys()
808         keys.sort()
809
810         for dictline in self.json_page.raw_json:
811             node_id = None
812             # dictionary is empty and/or a new node has to be inserted
813             node_id = _resources_fulljson_dict['network_address'](
814                 self.node_dictlist, dictline['network_address'])
815             for k in keys:
816                 if k in dictline:
817                     if k == 'network_address':
818                         continue
819
820                     _resources_fulljson_dict[k](
821                         self.node_dictlist[node_id], dictline[k])
822
823             #The last property has been inserted in the property tuple list,
824             #reset node_id
825             #Turn the property tuple list (=dict value) into a dictionary
826             self.node_dictlist[node_id] = dict(self.node_dictlist[node_id])
827             node_id = None
828
829     @staticmethod
830     def iotlab_hostname_to_hrn(root_auth,  hostname):
831         """
832         Transforms a node hostname into a SFA hrn.
833
834         :param root_auth: Name of the root authority of the SFA server. In
835             our case, it is set to iotlab.
836         :param hostname: node's hotname, given by OAR.
837         :type root_auth: string
838         :type hostname: string
839         :returns: inserts the root_auth and '.' before the hostname.
840         :rtype: string
841
842         """
843         return root_auth + '.' + hostname
844
845     def ParseSites(self):
846         """ Returns a list of dictionnaries containing the sites' attributes."""
847
848         nodes_per_site = {}
849         config = Config()
850         #logger.debug(" OARrestapi.py \tParseSites  self.node_dictlist %s"\
851                                                         #%(self.node_dictlist))
852         # Create a list of nodes per site_id
853         for node_id in self.node_dictlist:
854             node = self.node_dictlist[node_id]
855
856             if node['site'] not in nodes_per_site:
857                 nodes_per_site[node['site']] = []
858                 nodes_per_site[node['site']].append(node['node_id'])
859             else:
860                 if node['node_id'] not in nodes_per_site[node['site']]:
861                     nodes_per_site[node['site']].append(node['node_id'])
862
863         #Create a site dictionary whose key is site_login_base
864         # (name of the site) and value is a dictionary of properties,
865         # including the list of the node_ids
866         for node_id in self.node_dictlist:
867             node = self.node_dictlist[node_id]
868             node.update({'hrn': self.iotlab_hostname_to_hrn(self.interface_hrn,
869                                                             node['hostname'])})
870             self.node_dictlist.update({node_id: node})
871
872             if node['site'] not in self.site_dict:
873                 self.site_dict[node['site']] = {
874                     'site': node['site'],
875                     'node_ids': nodes_per_site[node['site']],
876                     'latitude': "48.83726",
877                     'longitude': "- 2.10336",
878                     'name': config.SFA_REGISTRY_ROOT_AUTH,
879                     'pcu_ids': [], 'max_slices': None,
880                     'ext_consortium_id': None,
881                     'max_slivers': None, 'is_public': True,
882                     'peer_site_id': None,
883                     'abbreviated_name': "iotlab", 'address_ids': [],
884                     'url': "https://portal.senslab.info", 'person_ids': [],
885                     'site_tag_ids': [], 'enabled': True,  'slice_ids': [],
886                     'date_created': None, 'peer_id': None
887                 }
888
889     OARrequests_uri_dict = {
890         'GET_version':
891         {'uri': '/oarapi/version.json', 'parse_func': ParseVersion},
892
893         'GET_timezone':
894         {'uri': '/oarapi/timezone.json', 'parse_func': ParseTimezone},
895
896         'GET_jobs':
897         {'uri': '/oarapi/jobs.json', 'parse_func': ParseJobs},
898
899         'GET_jobs_id':
900         {'uri': '/oarapi/jobs/id.json', 'parse_func': ParseJobsIds},
901
902         'GET_jobs_id_resources':
903         {'uri': '/oarapi/jobs/id/resources.json',
904         'parse_func': ParseJobsIdResources},
905
906         'GET_jobs_table':
907         {'uri': '/oarapi/jobs/table.json', 'parse_func': ParseJobsTable},
908
909         'GET_jobs_details':
910         {'uri': '/oarapi/jobs/details.json', 'parse_func': ParseJobsDetails},
911
912         'GET_reserved_nodes':
913         {'uri':
914         '/oarapi/jobs/details.json?state=Running,Waiting,Launching',
915         'owner': '&user=', 'parse_func': ParseReservedNodes},
916
917         'GET_running_jobs':
918         {'uri': '/oarapi/jobs/details.json?state=Running',
919         'parse_func': ParseRunningJobs},
920
921         'GET_resources_full':
922         {'uri': '/oarapi/resources/full.json',
923         'parse_func': ParseResourcesFull},
924
925         'GET_sites':
926         {'uri': '/oarapi/resources/full.json',
927         'parse_func': ParseResourcesFullSites},
928
929         'GET_resources':
930         {'uri': '/oarapi/resources.json', 'parse_func': ParseResources},
931
932         'DELETE_jobs_id':
933         {'uri': '/oarapi/jobs/id.json', 'parse_func': ParseDeleteJobs}}
934
935
936     def SendRequest(self, request, strval=None, username=None):
937         """ Connects to OAR , sends the valid GET requests and uses
938         the appropriate json parsing functions.
939
940         :returns: calls to the appropriate parsing function, associated with the
941             GET request
942         :rtype: depends on the parsing function called.
943
944         .. seealso:: OARrequests_uri_dict
945         """
946         save_json = None
947
948         self.json_page.ResetNextPage()
949         save_json = []
950
951         if request in self.OARrequests_uri_dict:
952             while self.json_page.next_page:
953                 self.json_page.raw_json = self.server.GETRequestToOARRestAPI(
954                     request,
955                     strval,
956                     self.json_page.next_offset,
957                     username)
958                 self.json_page.FindNextPage()
959                 if self.json_page.concatenate:
960                     save_json.append(self.json_page.raw_json)
961
962             if self.json_page.concatenate and self.json_page.end:
963                 self.json_page.raw_json = \
964                     self.json_page.ConcatenateJsonPages(save_json)
965
966             return self.OARrequests_uri_dict[request]['parse_func'](self)
967         else:
968             logger.error("OARRESTAPI OARGetParse __init__ : ERROR_REQUEST "
969                          % (request))