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