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