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