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/>.
18 # Author: Lucia Guevgeozian <lucia.guevgeozian_odizzio@inria.fr>
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.resource import ResourceManager, clsinit_copy, \
23 from nepi.resources.omf.node import OMFNode
24 from nepi.util.sfaapi import SFAAPIFactory
25 from nepi.util.execfuncs import lexec
26 from nepi.util import sshfuncs
28 from random import randint
37 class WilabtSfaNode(OMFNode):
38 _rtype = "wilabt::sfa::Node"
39 _help = "Controls a Wilabt host accessible using a SSH key " \
40 "and provisioned using SFA"
44 def _register_attributes(cls):
46 username = Attribute("username", "Local account username",
47 flags = Flags.Credential)
49 identity = Attribute("identity", "SSH identity file",
50 flags = Flags.Credential)
52 server_key = Attribute("serverKey", "Server public key",
55 sfa_user = Attribute("sfauser", "SFA user",
56 flags = Flags.Credential)
58 sfa_private_key = Attribute("sfaPrivateKey", "SFA path to the private key \
59 used to generate the user credential",
60 flags = Flags.Credential)
62 slicename = Attribute("slicename", "SFA slice for the experiment",
63 flags = Flags.Credential)
65 gateway_user = Attribute("gatewayUser", "Gateway account username",
68 gateway = Attribute("gateway", "Hostname of the gateway machine",
71 host = Attribute("host", "Name of the physical machine",
74 disk_image = Attribute("disk_image", "Specify a specific disk image for a node",
77 cls._register_attribute(username)
78 cls._register_attribute(identity)
79 cls._register_attribute(server_key)
80 cls._register_attribute(sfa_user)
81 cls._register_attribute(sfa_private_key)
82 cls._register_attribute(slicename)
83 cls._register_attribute(gateway_user)
84 cls._register_attribute(gateway)
85 cls._register_attribute(host)
86 cls._register_attribute(disk_image)
88 def __init__(self, ec, guid):
89 super(WilabtSfaNode, self).__init__(ec, guid)
91 self._ecobj = weakref.ref(ec)
93 self._node_to_provision = None
94 self._slicenode = False
98 def _skip_provision(self):
99 sfa_user = self.get("sfauser")
107 Property to instanciate the SFA API based in sfi client.
108 For each SFA method called this instance is used.
111 sfa_user = self.get("sfauser")
112 sfa_sm = "http://www.wilab2.ilabt.iminds.be:12369/protogeni/xmlrpc/am/3.0"
113 sfa_auth = '.'.join(sfa_user.split('.')[:2])
114 sfa_registry = "http://sfa3.planet-lab.eu:12345/"
115 sfa_private_key = self.get("sfaPrivateKey")
118 _sfaapi = SFAAPIFactory.get_api(sfa_user, sfa_auth,
119 sfa_registry, sfa_sm, sfa_private_key, self._ecobj(), batch, WilabtSfaNode._rtype)
124 self._sfaapi = weakref.ref(_sfaapi)
126 return self._sfaapi()
128 def do_discover(self):
130 Based on the attributes defined by the user, discover the suitable
133 nodes = self.sfaapi.get_resources_hrn()
135 host = self._get_host()
137 # the user specified one particular node to be provisioned
139 host_hrn = nodes[host]
141 # check that the node is not blacklisted or being provisioned
143 if not self._blacklisted(host_hrn):
144 if not self._reserved(host_hrn):
145 if self._check_if_in_slice([host_hrn]):
146 self.debug("Node already in slice %s" % host_hrn)
147 self._slicenode = True
148 host = host + '.wilab2.ilabt.iminds.be'
149 self.set('host', host)
150 self._node_to_provision = host_hrn
151 super(WilabtSfaNode, self).do_discover()
153 def do_provision(self):
155 Add node to user's slice and verifing that the node is functioning
156 correctly. Check ssh, omf rc running, hostname, file system.
163 while not provision_ok:
164 node = self._node_to_provision
166 # self._delete_from_slice()
167 # self.debug("Waiting 480 sec for re-adding to slice")
168 # time.sleep(480) # Timout for the testbed to allow a new reservation
169 self._add_node_to_slice(node)
171 while not self._check_if_in_slice([node]) and t < timeout \
172 and not self._ecobj().abort:
175 self.debug("Waiting 5 sec for resources to be added")
178 if not self._check_if_in_slice([node]):
179 self.debug("Couldn't add node %s to slice" % node)
180 self.fail_node_not_available(node)
183 ssh_ok = self._check_ssh_loop()
186 # the timeout was reach without establishing ssh connection
187 # the node is blacklisted, and a new
188 # node to provision is discovered
189 self._blacklist_node(node)
193 # check /proc directory is mounted (ssh_ok = True)
194 # file system is not read only, hostname is correct
195 # and omf_rc process is up
197 if not self._check_fs():
200 if not self._check_omfrc():
203 if not self._check_hostname():
209 if not self.get('host'):
210 self._set_host_attr(node)
211 self.info(" Node provisioned ")
213 super(WilabtSfaNode, self).do_provision()
216 if self.state == ResourceState.NEW:
217 self.info("Deploying w-iLab.t node")
220 super(WilabtSfaNode, self).do_deploy()
222 def do_release(self):
223 super(WilabtSfaNode, self).do_release()
224 if self.state == ResourceState.RELEASED and not self._skip_provision():
225 self.debug(" Releasing SFA API ")
226 self.sfaapi.release()
228 def _blacklisted(self, host_hrn):
230 Check in the SFA API that the node is not in the blacklist.
232 if self.sfaapi.blacklisted(host_hrn):
233 self.fail_node_not_available(host_hrn)
236 def _reserved(self, host_hrn):
238 Check in the SFA API that the node is not in the reserved
241 if self.sfaapi.reserved(host_hrn):
242 self.fail_node_not_available(host_hrn)
245 def _get_username(self):
247 Get the username for login in to the nodes from RSpec.
248 Wilabt username is not made out of any convention, it
249 has to be retrived from the manifest RSpec.
251 slicename = self.get("slicename")
252 if self._username is None:
253 slice_info = self.sfaapi.get_slice_resources(slicename)
254 username = slice_info['resource'][0]['services'][0]['login'][0]['username']
255 self.set('username', username)
256 self.debug("Retriving username information from RSpec %s" % username)
257 self._username = username
259 def _check_ssh_loop(self):
261 Check that the ssh login is possible. In wilabt is done
262 through the gateway because is private testbed.
267 while t < timeout and not ssh_ok:
268 cmd = 'echo \'GOOD NODE\''
269 ((out, err), proc) = self.execute(cmd)
270 if out.find("GOOD NODE") < 0:
271 self.debug( "No SSH connection, waiting 20s" )
276 self.debug( "SSH OK" )
283 Check file system, /proc well mounted.
285 cmd = 'mount |grep proc'
286 ((out, err), proc) = self.execute(cmd)
287 if out.find("/proc type proc") < 0:
288 self.warning(" Corrupted file system ")
289 self._blacklist_node(node)
293 def _check_omfrc(self):
295 Check that OMF 6 resource controller is running.
297 cmd = 'ps aux|grep omf'
298 ((out, err), proc) = self.execute(cmd)
299 if out.find("/usr/local/rvm/gems/ruby-1.9.3-p286@omf/bin/omf_rc") < 0:
303 def _check_hostname(self):
305 Check that the hostname in the image is not set to localhost.
308 ((out, err), proc) = self.execute(cmd)
309 if 'localhost' in out.lower():
312 self.set('hostname', out.strip())
315 def execute(self, command,
321 connect_timeout = 30,
322 strict_host_checking = False,
326 """ Notice that this invocation will block until the
327 execution finishes. If this is not the desired behavior,
328 use 'run' instead."""
329 (out, err), proc = sshfuncs.rexec(
331 host = self.get("host"),
332 user = self.get("username"),
334 gwuser = self.get("gatewayUser"),
335 gw = self.get("gateway"),
338 identity = self.get("identity"),
339 server_key = self.get("serverKey"),
342 forward_x11 = forward_x11,
344 connect_timeout = connect_timeout,
345 persistent = persistent,
347 strict_host_checking = strict_host_checking
350 return (out, err), proc
353 def _add_node_to_slice(self, host_hrn):
355 Add node to slice, using SFA API. Actually Wilabt testbed
356 doesn't allow adding nodes, in fact in the API there is method
357 to group all the nodes instanciated as WilabtSfaNodes and the
358 Allocate and Provision is done with the last call at
359 sfaapi.add_resource_to_slice_batch.
361 self.info(" Adding node to slice ")
362 slicename = self.get("slicename")
363 disk_image = self.get("disk_image")
364 if disk_image is not None:
365 properties = {'disk_image': disk_image}
366 else: properties = None
368 self.sfaapi.add_resource_to_slice_batch(slicename, host_hrn, properties=properties)
370 def _delete_from_slice(self):
372 Delete every node from slice, using SFA API.
373 Wilabt doesn't allow to remove one sliver so this method
374 remove every slice from the slice.
377 self.warning(" Deleting all slivers from slice ")
378 slicename = self.get("slicename")
379 self.sfaapi.remove_all_from_slice(slicename)
383 Get the attribute hostname.
385 host = self.get("host")
391 def _set_host_attr(self, node):
393 Query SFAAPI for the hostname of a certain host hrn and sets the
394 attribute hostname, it will over write the previous value.
396 hosts_hrn = self.sfaapi.get_resources_hrn()
397 for host, hrn in hosts_hrn.iteritems():
399 host = host + '.wilab2.ilabt.iminds.be'
400 self.set("host", host)
402 def _check_if_in_slice(self, hosts_hrn):
404 Check using SFA API if any host hrn from hosts_hrn is in the user's
407 slicename = self.get("slicename")
408 slice_nodes = self.sfaapi.get_slice_resources(slicename)['resource']
410 if len(slice_nodes[0]['services']) != 0:
411 slice_nodes_hrn = self.sfaapi.get_resources_hrn(slice_nodes).values()
412 else: slice_nodes_hrn = []
413 nodes_inslice = list(set(hosts_hrn) & set(slice_nodes_hrn))
416 def _blacklist_node(self, host_hrn):
418 Add mal functioning node to blacklist (in SFA API).
420 self.warning(" Blacklisting malfunctioning node ")
421 self.sfaapi.blacklist_resource(host_hrn)
423 self.set('host', None)
425 self.set('host', host_hrn.split('.').pop())
427 def _put_node_in_provision(self, host_hrn):
429 Add node to the list of nodes being provisioned, in order for other RMs
430 to not try to provision the same one again.
432 self.sfaapi.reserve_resource(host_hrn)
434 def _get_ip(self, host):
436 Query cache for the IP of a node with certain hostname
439 ip = sshfuncs.gethostbyname(host)
441 # Fail while trying to find the IP
445 def fail_discovery(self):
446 msg = "Discovery failed. No candidates found for node"
448 raise RuntimeError, msg
450 def fail_node_not_alive(self, host=None):
451 msg = "Node %s not alive" % host
452 raise RuntimeError, msg
454 def fail_node_not_available(self, host):
455 msg = "Some nodes not available for provisioning"
456 raise RuntimeError, msg
458 def fail_not_enough_nodes(self):
459 msg = "Not enough nodes available for provisioning"
460 raise RuntimeError, msg
462 def fail_sfaapi(self):
463 msg = "Failing while trying to instanciate the SFA API."
464 raise RuntimeError, msg
466 def valid_connection(self, guid):