2 NEPI, a framework to manage network experiments
3 Copyright (C) 2013 INRIA
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
32 except (socket.error, IOError, OSError):
40 _expected_methods = set(
41 ['AddNodeTag', 'AddConfFile', 'DeletePersonTag', 'AddNodeType',
42 'DeleteBootState', 'SliceListNames', 'DeleteKey','SliceGetTicket',
43 'SliceUsersList', 'SliceUpdate', 'GetNodeGroups', 'SliceCreate',
44 'GetNetworkMethods', 'GetNodeFlavour', 'DeleteNode', 'BootNotifyOwners',
45 'AddPersonKey', 'AddNode', 'UpdateNodeGroup', 'GetAddressTypes',
46 'AddIlink', 'DeleteNetworkType', 'GetInitScripts', 'GenerateNodeConfFile',
47 'AddSite', 'BindObjectToPeer', 'SliceListUserSlices', 'GetPeers',
48 'AddPeer', 'DeletePeer', 'AddRole', 'DeleteRole', 'SetPersonPrimarySite',
49 'AddSiteAddress', 'SliceDelete', 'NotifyPersons', 'GetKeyTypes',
50 'GetConfFiles', 'GetIlinks', 'AddTagType', 'GetNodes', 'DeleteNodeTag',
51 'DeleteSliceFromNodesWhitelist', 'UpdateAddress', 'ResetPassword',
52 'AddSliceToNodesWhitelist', 'AddRoleToTagType', 'AddLeases',
53 'GetAddresses', 'AddInitScript', 'RebootNode', 'GetPCUTypes',
54 'RefreshPeer', 'GetBootMedium', 'UpdateKey', 'UpdatePCU', 'GetSession',
55 'AddInterfaceTag', 'UpdatePCUType', 'GetInterfaces', 'SliceExtendedInfo',
56 'SliceNodesList', 'DeleteRoleFromTagType', 'DeleteSlice', 'GetSites',
57 'DeleteMessage', 'GetSliceFamily', 'GetPlcRelease', 'UpdateTagType',
58 'AddSliceInstantiation', 'ResolveSlices', 'GetSlices',
59 'DeleteRoleFromPerson', 'GetSessions', 'UpdatePeer', 'VerifyPerson',
60 'GetPersonTags', 'DeleteKeyType', 'AddSlice', 'SliceUserAdd',
61 'DeleteSession', 'GetMessages', 'DeletePCU', 'GetPeerData',
62 'DeletePersonFromSite', 'DeleteTagType', 'GetPCUs', 'UpdateLeases',
63 'AddMessage', 'DeletePCUProtocolType', 'DeleteInterfaceTag',
64 'AddPersonToSite', 'GetSlivers', 'SliceNodesDel',
65 'DeleteAddressTypeFromAddress', 'AddNodeGroup', 'GetSliceTags',
66 'DeleteSite', 'GetSiteTags', 'UpdateMessage', 'DeleteSliceFromNodes',
67 'SliceRenew', 'UpdatePCUProtocolType', 'DeleteSiteTag',
68 'GetPCUProtocolTypes', 'GetEvents', 'GetSliceTicket', 'AddPersonTag',
69 'BootGetNodeDetails', 'DeleteInterface', 'DeleteNodeGroup',
70 'AddPCUProtocolType', 'BootCheckAuthentication', 'AddSiteTag',
71 'AddAddressTypeToAddress', 'DeleteConfFile', 'DeleteInitScript',
72 'DeletePerson', 'DeleteIlink', 'DeleteAddressType', 'AddBootState',
73 'AuthCheck', 'NotifySupport', 'GetSliceInstantiations', 'AddPCUType',
74 'AddPCU', 'AddSession', 'GetEventObjects', 'UpdateSiteTag',
75 'UpdateNodeTag', 'AddPerson', 'BlacklistKey', 'UpdateInitScript',
76 'AddSliceToNodes', 'RebootNodeWithPCU', 'GetNodeTags', 'GetSliceKeys',
77 'GetSliceSshKeys', 'AddNetworkMethod', 'SliceNodesAdd',
78 'DeletePersonFromSlice', 'ReportRunlevel', 'GetNetworkTypes',
79 'UpdateSite', 'DeleteConfFileFromNodeGroup', 'UpdateNode',
80 'DeleteSliceInstantiation', 'DeleteSliceTag', 'BootUpdateNode',
81 'UpdatePerson', 'UpdateConfFile', 'SliceUserDel', 'DeleteLeases',
82 'AddConfFileToNodeGroup', 'UpdatePersonTag', 'DeleteConfFileFromNode',
83 'AddPersonToSlice', 'UnBindObjectFromPeer', 'AddNodeToPCU',
84 'GetLeaseGranularity', 'DeletePCUType', 'GetTagTypes', 'GetNodeTypes',
85 'UpdateInterfaceTag', 'GetRoles', 'UpdateSlice', 'UpdateSliceTag',
86 'AddSliceTag', 'AddNetworkType', 'AddInterface', 'AddAddressType',
87 'AddRoleToPerson', 'DeleteNodeType', 'GetLeases', 'UpdateInterface',
88 'SliceInfo', 'DeleteAddress', 'SliceTicketGet', 'GetPersons',
89 'GetWhitelist', 'AddKeyType', 'UpdateAddressType', 'GetPeerName',
90 'DeleteNetworkMethod', 'UpdateIlink', 'AddConfFileToNode', 'GetKeys',
91 'DeleteNodeFromPCU', 'GetInterfaceTags', 'GetBootStates',
92 'SetInterfaceSens', 'SetNodeLoadm', 'GetInterfaceRate', 'GetNodeLoadw',
93 'SetInterfaceKey', 'GetNodeSlices', 'GetNodeLoadm', 'SetSliceVref',
94 'GetInterfaceIwpriv', 'SetNodeLoadw', 'SetNodeSerial',
95 'GetNodePlainBootstrapfs', 'SetNodeMEMw', 'GetNodeResponse',
96 'SetInterfaceRate', 'SetSliceInitscript', 'SetNodeFcdistro',
97 'GetNodeLoady', 'SetNodeArch', 'SetNodeKargs', 'SetNodeMEMm',
98 'SetNodeBWy', 'SetNodeBWw', 'SetInterfaceSecurityMode', 'SetNodeBWm',
99 'SetNodeASType', 'GetNodeKargs', 'GetPersonColumnconf',
100 'GetNodeResponsem', 'GetNodeCPUy', 'GetNodeCramfs', 'SetNodeSlicesw',
101 'SetPersonColumnconf', 'SetNodeSlicesy', 'GetNodeCPUw', 'GetNodeBWy',
102 'GetNodeCPUm', 'GetInterfaceDriver', 'GetNodeLoad', 'GetInterfaceMode',
103 'GetNodeSerial', 'SetNodeSlicesm', 'SetNodeLoady', 'GetNodeReliabilityw',
104 'SetSliceFcdistro', 'GetNodeReliabilityy', 'SetInterfaceEssid',
105 'SetSliceInitscriptCode', 'GetNodeExtensions', 'GetSliceOmfControl',
106 'SetNodeCity', 'SetInterfaceIfname', 'SetNodeHrn', 'SetNodeNoHangcheck',
107 'GetNodeNoHangcheck', 'GetSliceFcdistro', 'SetNodeCountry',
108 'SetNodeKvariant', 'GetNodeKvariant', 'GetNodeMEMy', 'SetInterfaceIwpriv',
109 'GetNodeMEMw', 'SetInterfaceBackdoor', 'GetInterfaceFreq',
110 'SetInterfaceChannel', 'SetInterfaceNw', 'GetPersonShowconf',
111 'GetSliceInitscriptCode', 'SetNodeMEM', 'GetInterfaceEssid', 'GetNodeMEMm',
112 'SetInterfaceMode', 'SetInterfaceIwconfig', 'GetNodeSlicesm', 'GetNodeBWm',
113 'SetNodePlainBootstrapfs', 'SetNodeRegion', 'SetNodeCPU', 'GetNodeSlicesw',
114 'SetNodeBW', 'SetNodeSlices', 'SetNodeCramfs', 'GetNodeSlicesy',
115 'GetInterfaceKey', 'GetSliceInitscript', 'SetNodeCPUm', 'SetSliceArch',
116 'SetNodeLoad', 'SetNodeResponse', 'GetSliceSliverHMAC', 'GetNodeBWw',
117 'GetNodeRegion', 'SetNodeMEMy', 'GetNodeASType', 'SetNodePldistro',
118 'GetSliceArch', 'GetNodeCountry', 'SetSliceOmfControl', 'GetNodeHrn',
119 'GetNodeCity', 'SetInterfaceAlias', 'GetNodeBW', 'GetNodePldistro',
120 'GetSlicePldistro', 'SetNodeASNumber', 'GetSliceHmac', 'SetSliceHmac',
121 'GetNodeMEM', 'GetNodeASNumber', 'GetInterfaceAlias', 'GetSliceVref',
122 'GetNodeArch', 'GetSliceSshKey', 'GetInterfaceKey4', 'GetInterfaceKey2',
123 'GetInterfaceKey3', 'GetInterfaceKey1', 'GetInterfaceBackdoor',
124 'GetInterfaceIfname', 'SetSliceSliverHMAC', 'SetNodeReliability',
125 'GetNodeCPU', 'SetPersonShowconf', 'SetNodeExtensions', 'SetNodeCPUy',
126 'SetNodeCPUw', 'GetNodeResponsew', 'SetNodeResponsey', 'GetInterfaceSens',
127 'SetNodeResponsew', 'GetNodeResponsey', 'GetNodeReliability',
128 'GetNodeReliabilitym', 'SetNodeResponsem', 'SetInterfaceDriver',
129 'GetInterfaceSecurityMode', 'SetNodeDeployment', 'SetNodeReliabilitym',
130 'GetNodeFcdistro', 'SetInterfaceFreq', 'GetInterfaceNw',
131 'SetNodeReliabilityy', 'SetNodeReliabilityw', 'GetInterfaceIwconfig',
132 'SetSlicePldistro', 'SetSliceSshKey', 'GetNodeDeployment',
133 'GetInterfaceChannel', 'SetInterfaceKey2', 'SetInterfaceKey3',
134 'SetInterfaceKey1', 'SetInterfaceKey4'])
136 _required_methods = set()
138 def __init__(self, username = None, password = None, session_key = None,
140 hostname = "www.planet-lab.eu",
141 urlpattern = "https://%(hostname)s:443/PLCAPI/",
144 self._blacklist = set()
145 self._reserved = set()
146 self._nodes_cache = None
148 if session_key is not None:
149 self.auth = dict(AuthMethod='session', session=session_key)
150 elif username is not None and password is not None:
151 self.auth = dict(AuthMethod='password', Username=username, AuthString=password)
153 self.auth = dict(AuthMethod='anonymous')
155 self._local_peer = local_peer
156 self._url = urlpattern % {'hostname':hostname}
158 if (proxy is not None):
160 class HTTPSProxyTransport(xmlrpclib.Transport):
161 def __init__(self, proxy, use_datetime=0):
162 opener = urllib2.build_opener(urllib2.ProxyHandler({"https" : proxy}))
163 xmlrpclib.Transport.__init__(self, use_datetime)
166 def request(self, host, handler, request_body, verbose=0):
167 req = urllib2.Request('https://%s%s' % (host, handler), request_body)
168 req.add_header('User-agent', self.user_agent)
169 self.verbose = verbose
170 return self.parse_response(self.opener.open(req))
172 self._proxy_transport = lambda : HTTPSProxyTransport(proxy)
174 self._proxy_transport = lambda : None
176 self.threadlocal = threading.local()
180 # Cannot reuse same proxy in all threads, py2.7 is not threadsafe
181 return xmlrpclib.ServerProxy(
183 transport = self._proxy_transport(),
189 return self.threadlocal.mc
190 except AttributeError:
196 # validate XMLRPC server checking supported API calls
197 methods = set(_retry(self.mcapi.system.listMethods)())
198 if self._required_methods - methods:
199 warnings.warn("Unsupported REQUIRED methods: %s" % (
200 ", ".join(sorted(self._required_methods - methods)), ) )
203 if self._expected_methods - methods:
204 warnings.warn("Unsupported EXPECTED methods: %s" % (
205 ", ".join(sorted(self._expected_methods - methods)), ) )
209 network_types = _retry(self.mcapi.GetNetworkTypes)(self.auth)
210 except (xmlrpclib.ProtocolError, xmlrpclib.Fault),e:
211 warnings.warn(str(e))
216 def network_types(self):
218 return self._network_types
219 except AttributeError:
220 self._network_types = _retry(self.mcapi.GetNetworkTypes)(self.auth)
221 return self._network_types
226 return self._peer_map
227 except AttributeError:
228 peers = _retry(self.mcapi.GetPeers)(self.auth, {}, ['shortname','peername','peer_id'])
230 self._peer_map = dict(
231 (peer['shortname'], peer['peer_id'])
235 self._peer_map.update(
236 (peer['peername'], peer['peer_id'])
240 self._peer_map.update(
241 (peer['peer_id'], peer['shortname'])
245 self._peer_map[None] = self._local_peer
246 return self._peer_map
248 def get_node_flavour(self, node):
250 Returns detailed information on a given node's flavour,
251 i.e. its base installation.
253 This depends on the global PLC settings in the PLC_FLAVOUR area,
254 optionnally overridden by any of the following tags if set on that node:
255 'arch', 'pldistro', 'fcdistro', 'deployment', 'extensions'
259 * node : int or string
260 - int, Node identifier
261 - string, Fully qualified hostname
266 * extensions : array of string, extensions to add to the base install
267 * fcdistro : string, the fcdistro this node should be based upon
268 * nodefamily : string, the nodefamily this node should be based upon
269 * plain : boolean, use plain bootstrapfs image if set (for tests)
271 if not isinstance(node, (str, int, long)):
272 raise ValueError, "Node must be either a non-unicode string or an int"
273 return _retry(self.mcapi.GetNodeFlavour)(self.auth, node)
275 def get_nodes(self, node_id_or_name = None, fields = None, **kw):
277 Returns an array of structs containing details about nodes.
278 If node_id_or_name is specified and is an array of node identifiers
279 or hostnames, or the filters keyword argument with struct of node
280 attributes, or node attributes by keyword argument,
281 only nodes matching the filter will be returned.
283 If fields is specified, only the specified details will be returned.
284 NOTE that if fields is unspecified, the complete set of native fields are
285 returned, which DOES NOT include tags at this time.
287 Some fields may only be viewed by admins.
291 fields: an optional list of fields to retrieve. The default is all.
293 filters: an optional mapping with custom filters, which is the only
294 way to support complex filters like negation and numeric comparisons.
296 peer: a string (or sequence of strings) with the name(s) of peers
297 to filter - or None for local nodes.
299 if fields is not None:
300 fieldstuple = (fields,)
304 if node_id_or_name is not None:
305 return _retry(self.mcapi.GetNodes)(self.auth, node_id_or_name, *fieldstuple)
307 filters = kw.pop('filters',{})
310 peer = kw.pop('peer')
312 name_to_id = self.peer_map.get
314 if hasattr(peer, '__iter__'):
315 # we can't mix local and external nodes, so
316 # split and re-issue recursively in that case
317 if None in peer or self._local_peer in peer:
321 if self._local_peer in peer:
322 peer.remove(self._local_peer)
325 self.get_nodes(node_id_or_name, fields,
326 filters = filters, peer=peer, **kw) + \
327 self.get_nodes(node_id_or_name, fields,
328 filters = filters, peer=None, **kw)
331 peer_filter = map(name_to_id, peer)
333 elif peer is None or peer == self._local_peer:
336 peer_filter = name_to_id(peer)
338 filters['peer_id'] = peer_filter
342 if not filters and not fieldstuple:
343 if not self._nodes_cache:
344 self._nodes_cache = _retry(self.mcapi.GetNodes)(self.auth)
345 return self._nodes_cache
347 return _retry(self.mcapi.GetNodes)(self.auth, filters, *fieldstuple)
349 def get_node_tags(self, node_tag_id = None, fields = None, **kw):
350 if fields is not None:
351 fieldstuple = (fields,)
355 if node_tag_id is not None:
356 return _retry(self.mcapi.GetNodeTags)(self.auth, node_tag_id,
359 filters = kw.pop('filters',{})
361 return _retry(self.mcapi.GetNodeTags)(self.auth, filters,
364 def get_slice_tags(self, slice_tag_id = None, fields = None, **kw):
365 if fields is not None:
366 fieldstuple = (fields,)
370 if slice_tag_id is not None:
371 return _retry(self.mcapi.GetSliceTags)(self.auth, slice_tag_id,
374 filters = kw.pop('filters',{})
376 return _retry(self.mcapi.GetSliceTags)(self.auth, filters,
379 def get_interfaces(self, interface_id_or_ip = None, fields = None, **kw):
380 if fields is not None:
381 fieldstuple = (fields,)
385 if interface_id_or_ip is not None:
386 return _retry(self.mcapi.GetInterfaces)(self.auth,
387 interface_id_or_ip, *fieldstuple)
389 filters = kw.pop('filters',{})
391 return _retry(self.mcapi.GetInterfaces)(self.auth, filters,
394 def get_slices(self, slice_id_or_name = None, fields = None, **kw):
395 if fields is not None:
396 fieldstuple = (fields,)
400 if slice_id_or_name is not None:
401 return _retry(self.mcapi.GetSlices)(self.auth, slice_id_or_name,
404 filters = kw.pop('filters',{})
406 return _retry(self.mcapi.GetSlices)(self.auth, filters,
409 def update_slice(self, slice_id_or_name, **kw):
410 return _retry(self.mcapi.UpdateSlice)(self.auth, slice_id_or_name, kw)
412 def start_multicall(self):
413 self.threadlocal.mc = xmlrpclib.MultiCall(self.mcapi)
415 def finish_multicall(self):
416 mc = self.threadlocal.mc
417 del self.threadlocal.mc
420 def get_slice_nodes(self, slicename):
421 return self.get_slices(slicename, ['node_ids'])[0]['node_ids']
423 def add_slice_nodes(self, slicename, nodes = None):
424 self.update_slice(slicename, nodes = nodes)
426 def get_node_info(self, node_id):
427 self.start_multicall()
428 info = self.get_nodes(node_id)
429 tags = self.get_node_tags(node_id=node_id, fields=('tagname','value'))
430 info, tags = self.finish_multicall()
433 def get_slice_id(self, slicename):
435 slices = self.get_slices(slicename, fields=('slice_id',))
437 slice_id = slices[0]['slice_id']
439 # If it wasn't found, don't remember this failure, keep trying
442 def get_slice_vnet_sys_tag(self, slicename):
443 slicetags = self.get_slice_tags(
445 tagname = 'vsys_vnet',
449 return slicetags[0]['value']
453 def blacklist_host(self, hostname):
454 self._blacklist.add(hostname)
456 def blacklisted(self):
457 return self._blacklist
459 def unblacklist_host(self, hostname):
460 del self._blacklist[hostname]
462 def reserve_host(self, hostname):
463 self._reserved.add(hostname)
466 return self._reserved
468 def unreserve_host(self, hostname):
469 del self._reserved[hostname]
472 class PLCAPIFactory(object):
476 It allows PlanetLab RMs sharing a same slice, to use a same plcapi instance,
477 and to sincronize blacklisted and reserved hosts.
480 # use lock to avoid concurrent access to the Api list at the same times by 2 different threads
481 _lock = threading.Lock()
485 def get_api(cls, slicename, pl_pass, pl_host,
486 pl_ptn = "https://%(hostname)s:443/PLCAPI/",
488 """ Get existing PLCAPI instance
490 :param slicename: Planelab slice name
492 :param pl_pass: Planetlab password (used for web login)
494 :param pl_host: Planetlab registry host (e.g. "www.planet-lab.eu")
496 :param pl_ptn: XMLRPC service pattern (e.g. https://%(hostname)s:443/PLCAPI/)
498 :param proxy: Proxy service url
501 if slice and pl_pass and pl_host:
502 key = cls._make_key(slicename, pl_host)
504 api = cls._apis.get(key)
506 api = cls.create_api(slicename, pl_pass, pl_host, pl_ptn, proxy)
511 def create_api(cls, slicename, pl_pass, pl_host,
512 pl_ptn = "https://%(hostname)s:443/PLCAPI/",
514 """ Create an PLCAPI instance
516 :param slicename: Planelab slice name
518 :param pl_pass: Planetlab password (used for web login)
520 :param pl_host: Planetlab registry host (e.g. "www.planet-lab.eu")
522 :param pl_ptn: XMLRPC service pattern (e.g. https://%(hostname)s:443/PLCAPI/)
524 :param proxy: Proxy service url
528 username = slicename,
534 key = cls._make_key(slicename, pl_host)
539 def _make_key(cls, *args):
540 """ Hash the credentials in order to create a key
542 :param args: list of arguments used to create the hash (user, host, port, ...)
543 :type args: list of args
546 skey = "".join(map(str, args))
547 return hashlib.md5(skey).hexdigest()