Merging patch to support https proxies when running experiments using PlanetLab,...
[nepi.git] / src / nepi / testbeds / planetlab / plcapi.py
index ab8fd2e..d1d7980 100644 (file)
@@ -1,6 +1,22 @@
 import xmlrpclib
+import functools
+import socket
+import time
+import threading
+
+def _retry(fn):
+    def rv(*p, **kw):
+        for x in xrange(5):
+            try:
+                return fn(*p, **kw)
+            except (socket.error, IOError, OSError):
+                time.sleep(x*5+5)
+        else:
+            return fn (*p, **kw)
+    return rv
 
 class PLCAPI(object):
+
     _expected_methods = set(
         ['AddNodeTag', 'AddConfFile', 'DeletePersonTag', 'AddNodeType', 'DeleteBootState', 'SliceListNames', 'DeleteKey', 
          'SliceGetTicket', 'SliceUsersList', 'SliceUpdate', 'GetNodeGroups', 'SliceCreate', 'GetNetworkMethods', 'GetNodeFlavour', 
@@ -56,7 +72,7 @@ class PLCAPI(object):
      
     _required_methods = set()
 
-    def __init__(self, username=None, password=None, sessionkey=None,
+    def __init__(self, username=None, password=None, sessionkey=None, proxy=None,
             hostname = "www.planet-lab.eu",
             urlpattern = "https://%(hostname)s:443/PLCAPI/",
             localPeerName = "PLE"):
@@ -68,16 +84,45 @@ class PLCAPI(object):
             self.auth = dict(AuthMethod='anonymous')
         
         self._localPeerName = localPeerName
+        self._url = urlpattern % {'hostname':hostname}
+        if (proxy is not None):
+            import urllib2
+            class HTTPSProxyTransport(xmlrpclib.Transport):
+                def __init__(self, proxy, use_datetime=0):
+                    opener = urllib2.build_opener(urllib2.ProxyHandler({"https" : proxy}))
+                    xmlrpclib.Transport.__init__(self, use_datetime)
+                    self.opener = opener
+                def request(self, host, handler, request_body, verbose=0):
+                    req = urllib2.Request('https://%s%s' % (host, handler), request_body)
+                    req.add_header('User-agent', self.user_agent)
+                    self.verbose = verbose
+                    return self.parse_response(self.opener.open(req))
+            self._proxyTransport = lambda : HTTPSProxyTransport(proxy)
+        else:
+            self._proxyTransport = lambda : None
         
-        self.api = xmlrpclib.ServerProxy(
-            urlpattern % {'hostname':hostname},
+        self.threadlocal = threading.local()
+    
+    @property
+    def api(self):
+        # Cannot reuse same proxy in all threads, py2.7 is not threadsafe
+        return xmlrpclib.ServerProxy(
+            self._url ,
+            transport = self._proxyTransport(),
             allow_none = True)
         
+    @property
+    def mcapi(self):
+        try:
+            return self.threadlocal.mc
+        except AttributeError:
+            return self.api
+        
     def test(self):
         import warnings
         
         # validate XMLRPC server checking supported API calls
-        methods = set(self.api.system.listMethods())
+        methods = set(_retry(self.mcapi.system.listMethods)())
         if self._required_methods - methods:
             warnings.warn("Unsupported REQUIRED methods: %s" % ( ", ".join(sorted(self._required_methods - methods)), ) )
             return False
@@ -86,7 +131,7 @@ class PLCAPI(object):
         
         try:
             # test authorization
-            network_types = self.api.GetNetworkTypes(self.auth)
+            network_types = _retry(self.mcapi.GetNetworkTypes)(self.auth)
         except (xmlrpclib.ProtocolError, xmlrpclib.Fault),e:
             warnings.warn(str(e))
         
@@ -98,7 +143,7 @@ class PLCAPI(object):
         try:
             return self._network_types
         except AttributeError:
-            self._network_types = self.api.GetNetworkTypes(self.auth)
+            self._network_types = _retry(self.mcapi.GetNetworkTypes)(self.auth)
             return self._network_types
     
     @property
@@ -106,7 +151,7 @@ class PLCAPI(object):
         try:
             return self._peer_map
         except AttributeError:
-            peers = self.api.GetPeers(self.auth, {}, ['shortname','peername','peer_id'])
+            peers = _retry(self.mcapi.GetPeers)(self.auth, {}, ['shortname','peername','peer_id'])
             self._peer_map = dict(
                 (peer['shortname'], peer['peer_id'])
                 for peer in peers
@@ -115,6 +160,11 @@ class PLCAPI(object):
                 (peer['peername'], peer['peer_id'])
                 for peer in peers
             )
+            self._peer_map.update(
+                (peer['peer_id'], peer['shortname'])
+                for peer in peers
+            )
+            self._peer_map[None] = self._localPeerName
             return self._peer_map
     
 
@@ -141,7 +191,7 @@ class PLCAPI(object):
         """
         if not isinstance(node, (str, int, long)):
             raise ValueError, "Node must be either a non-unicode string or an int"
-        return self.api.GetNodeFlavour(self.auth, node)
+        return _retry(self.mcapi.GetNodeFlavour)(self.auth, node)
     
     def GetNodes(self, nodeIdOrName=None, fields=None, **kw):
         """
@@ -172,7 +222,7 @@ class PLCAPI(object):
         else:
             fieldstuple = ()
         if nodeIdOrName is not None:
-            return self.api.GetNodes(self.auth, nodeIdOrName, *fieldstuple)
+            return _retry(self.mcapi.GetNodes)(self.auth, nodeIdOrName, *fieldstuple)
         else:
             filters = kw.pop('filters',{})
             
@@ -203,7 +253,7 @@ class PLCAPI(object):
                 filters['peer_id'] = peer_filter
             
             filters.update(kw)
-            return self.api.GetNodes(self.auth, filters, *fieldstuple)
+            return _retry(self.mcapi.GetNodes)(self.auth, filters, *fieldstuple)
     
     def GetNodeTags(self, nodeTagId=None, fields=None, **kw):
         if fields is not None:
@@ -211,12 +261,23 @@ class PLCAPI(object):
         else:
             fieldstuple = ()
         if nodeTagId is not None:
-            return self.api.GetNodeTags(self.auth, nodeTagId, *fieldstuple)
+            return _retry(self.mcapi.GetNodeTags)(self.auth, nodeTagId, *fieldstuple)
         else:
             filters = kw.pop('filters',{})
             filters.update(kw)
-            return self.api.GetNodeTags(self.auth, filters, *fieldstuple)
-        
+            return _retry(self.mcapi.GetNodeTags)(self.auth, filters, *fieldstuple)
+
+    def GetSliceTags(self, sliceTagId=None, fields=None, **kw):
+        if fields is not None:
+            fieldstuple = (fields,)
+        else:
+            fieldstuple = ()
+        if sliceTagId is not None:
+            return _retry(self.mcapi.GetSliceTags)(self.auth, sliceTagId, *fieldstuple)
+        else:
+            filters = kw.pop('filters',{})
+            filters.update(kw)
+            return _retry(self.mcapi.GetSliceTags)(self.auth, filters, *fieldstuple)
     
     def GetInterfaces(self, interfaceIdOrIp=None, fields=None, **kw):
         if fields is not None:
@@ -224,10 +285,79 @@ class PLCAPI(object):
         else:
             fieldstuple = ()
         if interfaceIdOrIp is not None:
-            return self.api.GetNodeTags(self.auth, interfaceIdOrIp, *fieldstuple)
+            return _retry(self.mcapi.GetInterfaces)(self.auth, interfaceIdOrIp, *fieldstuple)
+        else:
+            filters = kw.pop('filters',{})
+            filters.update(kw)
+            return _retry(self.mcapi.GetInterfaces)(self.auth, filters, *fieldstuple)
+        
+    def GetSlices(self, sliceIdOrName=None, fields=None, **kw):
+        if fields is not None:
+            fieldstuple = (fields,)
+        else:
+            fieldstuple = ()
+        if sliceIdOrName is not None:
+            return _retry(self.mcapi.GetSlices)(self.auth, sliceIdOrName, *fieldstuple)
         else:
             filters = kw.pop('filters',{})
             filters.update(kw)
-            return self.api.GetNodeTags(self.auth, filters, *fieldstuple)
+            return _retry(self.mcapi.GetSlices)(self.auth, filters, *fieldstuple)
         
+    def UpdateSlice(self, sliceIdOrName, **kw):
+        return _retry(self.mcapi.UpdateSlice)(self.auth, sliceIdOrName, kw)
+
+    def StartMulticall(self):
+        self.threadlocal.mc = xmlrpclib.MultiCall(self.mcapi)
+    
+    def FinishMulticall(self):
+        mc = self.threadlocal.mc
+        del self.threadlocal.mc
+        return _retry(mc)()
+
+    def GetSliceNodes(self, slicename):
+        return self.GetSlices(slicename, ['node_ids'])[0]['node_ids']
+
+    def AddSliceNodes(self, slicename,  nodes = None):
+        self.UpdateSlice(slicename, nodes = nodes)
+
+    def GetNodeInfo(self, node_id):
+        self.StartMulticall()
+        info = self.GetNodes(node_id)
+        tags = self.GetNodeTags(node_id=node_id, fields=('tagname','value'))
+        info, tags = self.FinishMulticall()
+        return info, tags
+
+    def GetSliceId(self, slicename):
+        slice_id = None
+        slices = self.GetSlices(slicename, fields=('slice_id',))
+        if slices:
+            slice_id = slices[0]['slice_id']
+        # If it wasn't found, don't remember this failure, keep trying
+        return slice_id
+
+    def GetSliceVnetSysTag(self, slicename):
+        slicetags = self.GetSliceTags(
+            name = slicename,
+            tagname = 'vsys_vnet',
+            fields=('value',))
+        if slicetags:
+            return slicetags[0]['value']
+        else:
+            return None
+def plcapi(auth_user, auth_string, plc_host, plc_url, proxy):
+    api = None
+    if auth_user:
+        api = PLCAPI(
+            username = auth_user,
+            password = auth_string,
+            hostname = plc_host,
+            urlpattern = plc_url,
+            proxy = proxy
+        )
+    else:
+        # anonymous access - may not be enough for much
+        api = PLCAPI()
+    return api
+