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',
_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"):
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
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))
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
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
(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
"""
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):
"""
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',{})
if None in peer or self._localPeerName in peer:
if None in peer:
peer.remove(None)
- if self._localPeerName:
+ if self._localPeerName in peer:
peer.remove(self._localPeerName)
return (
self.GetNodes(nodeIdOrName, fields, filters=filters, peer=peer, **kw)
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:
+ fieldstuple = (fields,)
+ else:
+ fieldstuple = ()
+ if nodeTagId is not None:
+ return _retry(self.mcapi.GetNodeTags)(self.auth, nodeTagId, *fieldstuple)
+ else:
+ filters = kw.pop('filters',{})
+ filters.update(kw)
+ 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:
+ fieldstuple = (fields,)
+ else:
+ fieldstuple = ()
+ if interfaceIdOrIp is not None:
+ 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 _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
+
+