Merge with head
[nepi.git] / src / nepi / testbeds / planetlab / plcapi.py
1 import xmlrpclib
2 import functools
3 import socket
4 import time
5 import threading
6
7 def _retry(fn):
8     def rv(*p, **kw):
9         for x in xrange(5):
10             try:
11                 return fn(*p, **kw)
12             except (socket.error, IOError, OSError):
13                 time.sleep(x*5+5)
14         else:
15             return fn (*p, **kw)
16     return rv
17
18 class PLCAPI(object):
19     _expected_methods = set(
20         ['AddNodeTag', 'AddConfFile', 'DeletePersonTag', 'AddNodeType', 'DeleteBootState', 'SliceListNames', 'DeleteKey', 
21          'SliceGetTicket', 'SliceUsersList', 'SliceUpdate', 'GetNodeGroups', 'SliceCreate', 'GetNetworkMethods', 'GetNodeFlavour', 
22          'DeleteNode', 'BootNotifyOwners', 'AddPersonKey', 'AddNode', 'UpdateNodeGroup', 'GetAddressTypes', 'AddIlink', 'DeleteNetworkType', 
23          'GetInitScripts', 'GenerateNodeConfFile', 'AddSite', 'BindObjectToPeer', 'SliceListUserSlices', 'GetPeers', 'AddPeer', 'DeletePeer', 
24          'AddRole', 'DeleteRole', 'SetPersonPrimarySite', 'AddSiteAddress', 'SliceDelete', 'NotifyPersons', 'GetKeyTypes', 'GetConfFiles', 
25          'GetIlinks', 'AddTagType', 'GetNodes', 'DeleteNodeTag', 'DeleteSliceFromNodesWhitelist', 'UpdateAddress', 'ResetPassword', 
26          'AddSliceToNodesWhitelist', 'AddRoleToTagType', 'AddLeases', 'GetAddresses', 'AddInitScript', 'RebootNode', 'GetPCUTypes', 
27          'RefreshPeer', 'GetBootMedium', 'UpdateKey', 'UpdatePCU', 'GetSession', 'AddInterfaceTag', 'UpdatePCUType', 'GetInterfaces', 
28          'SliceExtendedInfo', 'SliceNodesList', 'DeleteRoleFromTagType', 'DeleteSlice', 'GetSites', 'DeleteMessage', 'GetSliceFamily', 
29          'GetPlcRelease', 'UpdateTagType', 'AddSliceInstantiation', 'ResolveSlices', 'GetSlices', 'DeleteRoleFromPerson', 'GetSessions', 
30          'UpdatePeer', 'VerifyPerson', 'GetPersonTags', 'DeleteKeyType', 'AddSlice', 'SliceUserAdd', 'DeleteSession', 'GetMessages', 
31          'DeletePCU', 'GetPeerData', 'DeletePersonFromSite', 'DeleteTagType', 'GetPCUs', 'UpdateLeases', 'AddMessage', 
32          'DeletePCUProtocolType', 'DeleteInterfaceTag', 'AddPersonToSite', 'GetSlivers', 'SliceNodesDel', 'DeleteAddressTypeFromAddress', 
33          'AddNodeGroup', 'GetSliceTags', 'DeleteSite', 'GetSiteTags', 'UpdateMessage', 'DeleteSliceFromNodes', 'SliceRenew', 
34          'UpdatePCUProtocolType', 'DeleteSiteTag', 'GetPCUProtocolTypes', 'GetEvents', 'GetSliceTicket', 'AddPersonTag', 'BootGetNodeDetails', 
35          'DeleteInterface', 'DeleteNodeGroup', 'AddPCUProtocolType', 'BootCheckAuthentication', 'AddSiteTag', 'AddAddressTypeToAddress', 
36          'DeleteConfFile', 'DeleteInitScript', 'DeletePerson', 'DeleteIlink', 'DeleteAddressType', 'AddBootState', 'AuthCheck', 
37          'NotifySupport', 'GetSliceInstantiations', 'AddPCUType', 'AddPCU', 'AddSession', 'GetEventObjects', 'UpdateSiteTag', 
38          'UpdateNodeTag', 'AddPerson', 'BlacklistKey', 'UpdateInitScript', 'AddSliceToNodes', 'RebootNodeWithPCU', 'GetNodeTags', 
39          'GetSliceKeys', 'GetSliceSshKeys', 'AddNetworkMethod', 'SliceNodesAdd', 'DeletePersonFromSlice', 'ReportRunlevel', 
40          'GetNetworkTypes', 'UpdateSite', 'DeleteConfFileFromNodeGroup', 'UpdateNode', 'DeleteSliceInstantiation', 'DeleteSliceTag', 
41          'BootUpdateNode', 'UpdatePerson', 'UpdateConfFile', 'SliceUserDel', 'DeleteLeases', 'AddConfFileToNodeGroup', 'UpdatePersonTag', 
42          'DeleteConfFileFromNode', 'AddPersonToSlice', 'UnBindObjectFromPeer', 'AddNodeToPCU', 'GetLeaseGranularity', 'DeletePCUType', 
43          'GetTagTypes', 'GetNodeTypes', 'UpdateInterfaceTag', 'GetRoles', 'UpdateSlice', 'UpdateSliceTag', 'AddSliceTag', 'AddNetworkType', 
44          'AddInterface', 'AddAddressType', 'AddRoleToPerson', 'DeleteNodeType', 'GetLeases', 'UpdateInterface', 'SliceInfo', 'DeleteAddress', 
45          'SliceTicketGet', 'GetPersons', 'GetWhitelist', 'AddKeyType', 'UpdateAddressType', 'GetPeerName', 'DeleteNetworkMethod', 
46          'UpdateIlink', 'AddConfFileToNode', 'GetKeys', 'DeleteNodeFromPCU', 'GetInterfaceTags', 'GetBootStates', 'SetInterfaceSens', 'SetNodeLoadm', 
47          'GetInterfaceRate', 'GetNodeLoadw', 'SetInterfaceKey', 'GetNodeSlices', 'GetNodeLoadm', 'SetSliceVref', 'GetInterfaceIwpriv', 'SetNodeLoadw', 
48          'SetNodeSerial', 'GetNodePlainBootstrapfs', 'SetNodeMEMw', 'GetNodeResponse', 'SetInterfaceRate', 'SetSliceInitscript', 
49          'SetNodeFcdistro', 'GetNodeLoady', 'SetNodeArch', 'SetNodeKargs', 'SetNodeMEMm', 'SetNodeBWy', 'SetNodeBWw', 
50          'SetInterfaceSecurityMode', 'SetNodeBWm', 'SetNodeASType', 'GetNodeKargs', 'GetPersonColumnconf', 'GetNodeResponsem', 
51          'GetNodeCPUy', 'GetNodeCramfs', 'SetNodeSlicesw', 'SetPersonColumnconf', 'SetNodeSlicesy', 'GetNodeCPUw', 'GetNodeBWy', 
52          'GetNodeCPUm', 'GetInterfaceDriver', 'GetNodeLoad', 'GetInterfaceMode', 'GetNodeSerial', 'SetNodeSlicesm', 'SetNodeLoady', 
53          'GetNodeReliabilityw', 'SetSliceFcdistro', 'GetNodeReliabilityy', 'SetInterfaceEssid', 'SetSliceInitscriptCode', 
54          'GetNodeExtensions', 'GetSliceOmfControl', 'SetNodeCity', 'SetInterfaceIfname', 'SetNodeHrn', 'SetNodeNoHangcheck', 
55          'GetNodeNoHangcheck', 'GetSliceFcdistro', 'SetNodeCountry', 'SetNodeKvariant', 'GetNodeKvariant', 'GetNodeMEMy', 
56          'SetInterfaceIwpriv', 'GetNodeMEMw', 'SetInterfaceBackdoor', 'GetInterfaceFreq', 'SetInterfaceChannel', 'SetInterfaceNw', 
57          'GetPersonShowconf', 'GetSliceInitscriptCode', 'SetNodeMEM', 'GetInterfaceEssid', 'GetNodeMEMm', 'SetInterfaceMode', 
58          'SetInterfaceIwconfig', 'GetNodeSlicesm', 'GetNodeBWm', 'SetNodePlainBootstrapfs', 'SetNodeRegion', 'SetNodeCPU', 
59          'GetNodeSlicesw', 'SetNodeBW', 'SetNodeSlices', 'SetNodeCramfs', 'GetNodeSlicesy', 'GetInterfaceKey', 'GetSliceInitscript', 
60          'SetNodeCPUm', 'SetSliceArch', 'SetNodeLoad', 'SetNodeResponse', 'GetSliceSliverHMAC', 'GetNodeBWw', 'GetNodeRegion', 
61          'SetNodeMEMy', 'GetNodeASType', 'SetNodePldistro', 'GetSliceArch', 'GetNodeCountry', 'SetSliceOmfControl', 'GetNodeHrn', 
62          'GetNodeCity', 'SetInterfaceAlias', 'GetNodeBW', 'GetNodePldistro', 'GetSlicePldistro', 'SetNodeASNumber', 'GetSliceHmac', 
63          'SetSliceHmac', 'GetNodeMEM', 'GetNodeASNumber', 'GetInterfaceAlias', 'GetSliceVref', 'GetNodeArch', 'GetSliceSshKey', 
64          'GetInterfaceKey4', 'GetInterfaceKey2', 'GetInterfaceKey3', 'GetInterfaceKey1', 'GetInterfaceBackdoor', 'GetInterfaceIfname', 
65          'SetSliceSliverHMAC', 'SetNodeReliability', 'GetNodeCPU', 'SetPersonShowconf', 'SetNodeExtensions', 'SetNodeCPUy', 
66          'SetNodeCPUw', 'GetNodeResponsew', 'SetNodeResponsey', 'GetInterfaceSens', 'SetNodeResponsew', 'GetNodeResponsey', 
67          'GetNodeReliability', 'GetNodeReliabilitym', 'SetNodeResponsem', 'SetInterfaceDriver', 'GetInterfaceSecurityMode', 
68          'SetNodeDeployment', 'SetNodeReliabilitym', 'GetNodeFcdistro', 'SetInterfaceFreq', 'GetInterfaceNw', 'SetNodeReliabilityy', 
69          'SetNodeReliabilityw', 'GetInterfaceIwconfig', 'SetSlicePldistro', 'SetSliceSshKey', 'GetNodeDeployment', 'GetInterfaceChannel', 
70          'SetInterfaceKey2', 'SetInterfaceKey3', 'SetInterfaceKey1', 'SetInterfaceKey4'])
71      
72     _required_methods = set()
73
74     def __init__(self, username=None, password=None, sessionkey=None,
75             hostname = "www.planet-lab.eu",
76             urlpattern = "https://%(hostname)s:443/PLCAPI/",
77             localPeerName = "PLE"):
78         if sessionkey is not None:
79             self.auth = dict(AuthMethod='session', session=sessionkey)
80         elif username is not None and password is not None:
81             self.auth = dict(AuthMethod='password', Username=username, AuthString=password)
82         else:
83             self.auth = dict(AuthMethod='anonymous')
84         
85         self._localPeerName = localPeerName
86         self._url = urlpattern % {'hostname':hostname}
87         
88         self.threadlocal = threading.local()
89     
90     @property
91     def api(self):
92         # Cannot reuse same proxy in all threads, py2.7 is not threadsafe
93         return xmlrpclib.ServerProxy(
94             self._url ,
95             allow_none = True)
96         
97     @property
98     def mcapi(self):
99         try:
100             return self.threadlocal.mc
101         except AttributeError:
102             return self.api
103         
104     def test(self):
105         import warnings
106         
107         # validate XMLRPC server checking supported API calls
108         methods = set(_retry(self.mcapi.system.listMethods)())
109         if self._required_methods - methods:
110             warnings.warn("Unsupported REQUIRED methods: %s" % ( ", ".join(sorted(self._required_methods - methods)), ) )
111             return False
112         if self._expected_methods - methods:
113             warnings.warn("Unsupported EXPECTED methods: %s" % ( ", ".join(sorted(self._expected_methods - methods)), ) )
114         
115         try:
116             # test authorization
117             network_types = _retry(self.mcapi.GetNetworkTypes)(self.auth)
118         except (xmlrpclib.ProtocolError, xmlrpclib.Fault),e:
119             warnings.warn(str(e))
120         
121         return True
122     
123     
124     @property
125     def network_types(self):
126         try:
127             return self._network_types
128         except AttributeError:
129             self._network_types = _retry(self.mcapi.GetNetworkTypes)(self.auth)
130             return self._network_types
131     
132     @property
133     def peer_map(self):
134         try:
135             return self._peer_map
136         except AttributeError:
137             peers = _retry(self.mcapi.GetPeers)(self.auth, {}, ['shortname','peername','peer_id'])
138             self._peer_map = dict(
139                 (peer['shortname'], peer['peer_id'])
140                 for peer in peers
141             )
142             self._peer_map.update(
143                 (peer['peername'], peer['peer_id'])
144                 for peer in peers
145             )
146             self._peer_map.update(
147                 (peer['peer_id'], peer['shortname'])
148                 for peer in peers
149             )
150             self._peer_map[None] = self._localPeerName
151             return self._peer_map
152     
153
154     def GetNodeFlavour(self, node):
155         """
156         Returns detailed information on a given node's flavour, i.e. its base installation.
157
158         This depends on the global PLC settings in the PLC_FLAVOUR area, optionnally overridden by any of the following tags if set on that node:
159         'arch', 'pldistro', 'fcdistro', 'deployment', 'extensions'
160         
161         Params:
162         
163             * node : int or string
164                 - int, Node identifier
165                 - string, Fully qualified hostname
166         
167         Returns:
168
169             struct
170                 * extensions : array of string, extensions to add to the base install
171                 * fcdistro : string, the fcdistro this node should be based upon
172                 * nodefamily : string, the nodefamily this node should be based upon
173                 * plain : boolean, use plain bootstrapfs image if set (for tests)  
174         """
175         if not isinstance(node, (str, int, long)):
176             raise ValueError, "Node must be either a non-unicode string or an int"
177         return _retry(self.mcapi.GetNodeFlavour)(self.auth, node)
178     
179     def GetNodes(self, nodeIdOrName=None, fields=None, **kw):
180         """
181         Returns an array of structs containing details about nodes. 
182         If nodeIdOrName is specified and is an array of node identifiers or hostnames, 
183         or the filters keyword argument with struct of node attributes, 
184         or node attributes by keyword argument,
185         only nodes matching the filter will be returned.
186
187         If fields is specified, only the specified details will be returned. 
188         NOTE that if fields is unspecified, the complete set of native fields are returned, 
189         which DOES NOT include tags at this time.
190
191         Some fields may only be viewed by admins.
192         
193         Special params:
194             
195             fields: an optional list of fields to retrieve. The default is all.
196             
197             filters: an optional mapping with custom filters, which is the only
198                 way to support complex filters like negation and numeric comparisons.
199                 
200             peer: a string (or sequence of strings) with the name(s) of peers
201                 to filter - or None for local nodes.
202         """
203         if fields is not None:
204             fieldstuple = (fields,)
205         else:
206             fieldstuple = ()
207         if nodeIdOrName is not None:
208             return _retry(self.mcapi.GetNodes)(self.auth, nodeIdOrName, *fieldstuple)
209         else:
210             filters = kw.pop('filters',{})
211             
212             if 'peer' in kw:
213                 peer = kw.pop('peer')
214                 
215                 nameToId = self.peer_map.get
216                 
217                 if hasattr(peer, '__iter__'):
218                     # we can't mix local and external nodes, so
219                     # split and re-issue recursively in that case
220                     if None in peer or self._localPeerName in peer:
221                         if None in peer:    
222                             peer.remove(None)
223                         if self._localPeerName in peer:
224                             peer.remove(self._localPeerName)
225                         return (
226                             self.GetNodes(nodeIdOrName, fields, filters=filters, peer=peer, **kw)
227                             + self.GetNodes(nodeIdOrName, fields, filters=filters, peer=None, **kw)
228                         )
229                     else:
230                         peer_filter = map(nameToId, peer)
231                 elif peer is None or peer == self._localPeerName:
232                     peer_filter = None
233                 else:
234                     peer_filter = nameToId(peer)
235                 
236                 filters['peer_id'] = peer_filter
237             
238             filters.update(kw)
239             return _retry(self.mcapi.GetNodes)(self.auth, filters, *fieldstuple)
240     
241     def GetNodeTags(self, nodeTagId=None, fields=None, **kw):
242         if fields is not None:
243             fieldstuple = (fields,)
244         else:
245             fieldstuple = ()
246         if nodeTagId is not None:
247             return _retry(self.mcapi.GetNodeTags)(self.auth, nodeTagId, *fieldstuple)
248         else:
249             filters = kw.pop('filters',{})
250             filters.update(kw)
251             return _retry(self.mcapi.GetNodeTags)(self.auth, filters, *fieldstuple)
252
253     def GetSliceTags(self, sliceTagId=None, fields=None, **kw):
254         if fields is not None:
255             fieldstuple = (fields,)
256         else:
257             fieldstuple = ()
258         if sliceTagId is not None:
259             return _retry(self.mcapi.GetSliceTags)(self.auth, sliceTagId, *fieldstuple)
260         else:
261             filters = kw.pop('filters',{})
262             filters.update(kw)
263             return _retry(self.mcapi.GetSliceTags)(self.auth, filters, *fieldstuple)
264         
265     
266     def GetInterfaces(self, interfaceIdOrIp=None, fields=None, **kw):
267         if fields is not None:
268             fieldstuple = (fields,)
269         else:
270             fieldstuple = ()
271         if interfaceIdOrIp is not None:
272             return _retry(self.mcapi.GetInterfaces)(self.auth, interfaceIdOrIp, *fieldstuple)
273         else:
274             filters = kw.pop('filters',{})
275             filters.update(kw)
276             return _retry(self.mcapi.GetInterfaces)(self.auth, filters, *fieldstuple)
277         
278     def GetSlices(self, sliceIdOrName=None, fields=None, **kw):
279         if fields is not None:
280             fieldstuple = (fields,)
281         else:
282             fieldstuple = ()
283         if sliceIdOrName is not None:
284             return _retry(self.mcapi.GetSlices)(self.auth, sliceIdOrName, *fieldstuple)
285         else:
286             filters = kw.pop('filters',{})
287             filters.update(kw)
288             return _retry(self.mcapi.GetSlices)(self.auth, filters, *fieldstuple)
289         
290     def UpdateSlice(self, sliceIdOrName, **kw):
291         return _retry(self.mcapi.UpdateSlice)(self.auth, sliceIdOrName, kw)
292         
293
294     def StartMulticall(self):
295         self.threadlocal.mc = xmlrpclib.MultiCall(self.mcapi)
296     
297     def FinishMulticall(self):
298         mc = self.threadlocal.mc
299         del self.threadlocal.mc
300         return _retry(mc)()