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