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