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