applied the except and raise fixers to the master branch to close the gap with py3
[nepi.git] / src / nepi / util / manifoldapi.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
5 #    This program is free software: you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License version 2 as
7 #    published by the Free Software Foundation;
8 #
9 #    This program is distributed in the hope that it will be useful,
10 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #    GNU General Public License for more details.
13 #
14 #    You should have received a copy of the GNU General Public License
15 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # Author: Lucia Guevgeozian Odizzio <lucia.guevgeozian_odizzio@inria.fr>
18
19 from __future__ import print_function
20
21 import xmlrpclib
22 import hashlib
23 import threading
24
25
26 class MANIFOLDAPI(object):
27     """
28     API to query different data platforms as SFA, TopHat, OML Central Server,
29     using Manifold Framework, the backend of MySlice.
30     """
31     def __init__(self, username, password, hostname, urlpattern): 
32         
33         self.auth_pwd = dict(AuthMethod='password', Username=username, 
34             AuthString=password)
35         self._url = urlpattern % {'hostname':hostname}
36         self.lock = threading.Lock()
37         self.auth = self.get_session_key()
38
39     @property
40     def api(self):
41         return xmlrpclib.Server(self._url, allow_none = True)
42
43     def get_session_key(self):
44         """
45         Retrieves the session key, in order to use the same session for 
46         queries.
47         """
48         query = {'timestamp' : 'now', 'object': 'local:session',      
49             'filters' : [], 'fields' : [], 'action' : 'create'}
50
51         session = self.api.forward(self.auth_pwd, query)
52
53         if not session['value']:
54             msg = "Can not authenticate in Manifold API"
55             raise RuntimeError(msg)
56
57         session_key = session['value'][0]['session']
58         return dict(AuthMethod='session', session=session_key)
59
60     def get_resource_info(self, filters=None, fields=None):
61         """
62         Create and execute the Manifold API Query to get the resources 
63         according fields and filters.
64         :param filters: resource's constraints for the experiment
65         :type filters: dict
66         :param fields: desire fields in the result of the query
67         :type fields: list
68         """
69         query = {'action' : 'get', 'object' : 'resource'}
70
71         if filters:
72             filters = self._map_attr_to_resource_filters(filters)
73
74             qfilters = list()
75             for filtername, filtervalue in filters.iteritems():
76                 newfilter = [filtername, "==", filtervalue]
77                 qfilters.append(newfilter)
78             
79             query['filters'] = qfilters
80
81         if fields:
82             fields = self._check_valid_fields(fields)
83
84             if fields:
85                 query['fields'] = fields
86
87         return self.api.forward(self.auth, query)['value']
88         
89     def get_resource_urn(self, filters=None):
90         """
91         Retrieves the resources urn of the resources matching filters.
92         """
93         return self.get_resource_info(filters, 'urn')
94
95     def get_slice_resources(self, slicename):
96         """
97         Retrieves resources attached to user's slice.
98         return value: list of resources' urn
99         """
100         result = []
101         query = {'action' : 'get', 'object' : 'resource', 
102             'filters' : [['slice','==', slicename]],
103             'fields' : ['urn']}
104
105         with self.lock:
106             value = self.api.forward(self.auth, query)['value']
107
108         for resource in value:
109             result.append(resource['urn'])
110         
111         return result
112
113     def add_resource_to_slice(self, slicename, resource_urn):
114         """
115         Add resource to user's slice. The query needs to specify the new
116         resource plus the previous resources already in the slice.
117         """
118         resources = self.get_slice_resources(slicename)
119         resources.append(resource_urn) 
120
121         urn_list = list()
122         for r in resources:
123             urn_dict = dict()
124             urn_dict['urn'] = r
125             urn_list.append(urn_dict)
126             
127         query = {'action' : 'update', 'object' : 'slice', 
128             'filters' : [['slice_hrn','==', slicename]],
129             'params' : {'resource' : urn_list}}
130  
131         with self.lock:
132             self.api.forward(self.auth, query)
133
134         resources = self.get_slice_resources(slicename)
135         if resource_urn in resources:
136             return True
137         else:
138             msg = "Failed while trying to add %s to slice" % resource_urn
139             print(msg)
140             # check how to do warning
141             return False
142
143     def remove_resource_from_slice(self, slicename, resource_urn):
144         """
145         Remove resource from user's slice. The query needs to specify the list
146         of previous resources in the slice without the one to be remove.
147         """
148         resources = self.get_slice_resources(slicename)
149         resources.remove(resource_urn)
150
151         urn_list = list()
152         for r in resources:
153             urn_dict = dict()
154             urn_dict['urn'] = r
155             urn_list.append(urn_dict)
156
157         query = {'action' : 'update', 'object' : 'slice',
158             'filters' : [['slice_hrn','==', slicename]],
159             'params' : {'resource' : urn_list}}
160
161         with self.lock:
162             self.api.forward(self.auth, query)
163
164         resources = self.get_slice_resources(slicename)
165         if resource_urn not in resources:
166             return True
167         else:
168             msg = "Failed while trying to remove %s to slice" % resource_urn
169             # check how to do warning
170             return False
171
172     def _get_metadata(self):
173         """
174         This method is useful to retrive metadata from different platforms
175         in order to update fields and possible filters.
176         """
177         query = {'action' : 'get', 'object' : 'local:object', 
178             'filters' : [['table','=','resource']]}
179         
180         res = self.api.forward(self.auth, query)
181
182         valid_fields = list()
183         for i in res['value'][0]['column']:
184             valid_fields.append(i['name'])
185
186         return valid_fields
187         
188     def _map_attr_to_resource_filters(self, filters):
189         """
190         Depending on the object used for the Manifold query, the filters and 
191         fields can change its sintaxis. A resource field in a slice object
192         query adds 'resource.' to the field. Other changes don't follow any 
193         particular convention.
194         """
195         #TODO: find out useful filters
196         attr_to_filter = {
197             'hostname' : 'hostname',
198             'longitude' : 'longitude',
199             'latitude' : 'latitude',
200             'network' : 'network',
201             'component_id' : 'component_id'
202         }
203
204         mapped_filters = dict()
205         for filtername, filtervalue in filters.iteritems():
206             if attr_to_filter[filtername]:
207                 new_filtername = attr_to_filter[filtername]
208                 mapped_filters[new_filtername] = filtervalue
209         
210         return mapped_filters
211          
212     def _check_valid_fields(self, fields):
213         """
214         The fields can be a predefine set, define in the Manifold metadata.
215         """
216         valid_fields = self._get_metadata()
217
218         if not isinstance(fields, list):
219             fields = [fields]
220
221         for field in fields:
222             if field not in valid_fields:
223                 fields.remove(field)
224                 #self.warning(" Invalid Manifold field or filter ")
225         
226         return fields
227
228
229 class MANIFOLDAPIFactory(object):
230     """
231     API Factory to manage a map of MANIFOLDAPI instances as key-value pairs, it
232     instanciate a single instance per key. The key represents the same SFA, 
233     MF (ManiFold) credentials.
234     """
235
236     _lock = threading.Lock()
237     _apis = dict()
238
239     @classmethod
240     def get_api(cls, username, password, 
241             #hostname = "manifold.pl.sophia.inria.fr",
242             hostname ="test.myslice.info",
243             urlpattern = "http://%(hostname)s:7080"):
244         """
245         :param username: Manifold user (also used for MySlice web login)
246         :type username: str
247         :param password: Manifold password (also used for MySlice web login)
248         :type password: str
249         :param hostname: Hostname of the Manifold API to query SFA, TopHat, etc
250         :type hostname: str
251         :param urlpattern: Url of the Manifold API to query SFA, TopHat, etc
252         :type urlpattern: str
253         """
254
255         if username and password:
256             key = cls.make_key(username, password)
257             with cls._lock:
258                 api = cls._apis.get(key)
259     
260                 if not api:
261                     api = MANIFOLDAPI(username, password, hostname, urlpattern)
262                     cls._apis[key] = api
263
264                 return api
265
266         return None
267
268     @classmethod
269     def make_key(cls, *args):
270         skey = "".join(map(str, args))
271         return hashlib.md5(skey).hexdigest()
272
273