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: Alina Quereilhac <alina.quereilhac@inria.fr>
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.resource import ResourceManager, clsinit_copy, \
22 ResourceState, reschedule_delay
23 from nepi.resources.linux import rpmfuncs, debfuncs
24 from nepi.util import sshfuncs, execfuncs
25 from nepi.util.sshfuncs import ProcStatus
36 # TODO: Unify delays!!
37 # TODO: Validate outcome of uploads!!
41 Error codes that the rexitcode function can return if unable to
42 check the exit code of a spawned process
51 Supported flavors of Linux OS
61 class LinuxNode(ResourceManager):
63 .. class:: Class Args :
65 :param ec: The Experiment controller
66 :type ec: ExperimentController
67 :param guid: guid of the RM
72 There are different ways in which commands can be executed using the
73 LinuxNode interface (i.e. 'execute' - blocking and non blocking, 'run',
78 * 'execute' (blocking mode) :
80 HOW IT WORKS: 'execute', forks a process and run the
81 command, synchronously, attached to the terminal, in
83 The execute method will block until the command returns
84 the result on 'out', 'err' (so until it finishes executing).
86 USAGE: short-lived commands that must be executed attached
87 to a terminal and in foreground, for which it IS necessary
88 to block until the command has finished (e.g. if you want
89 to run 'ls' or 'cat').
91 * 'execute' (NON blocking mode - blocking = False) :
93 HOW IT WORKS: Same as before, except that execute method
94 will return immediately (even if command still running).
96 USAGE: long-lived commands that must be executed attached
97 to a terminal and in foreground, but for which it is not
98 necessary to block until the command has finished. (e.g.
99 start an application using X11 forwarding)
103 HOW IT WORKS: Connects to the host ( using SSH if remote)
104 and launches the command in background, detached from any
105 terminal (daemonized), and returns. The command continues to
106 run remotely, but since it is detached from the terminal,
107 its pipes (stdin, stdout, stderr) can't be redirected to the
108 console (as normal non detached processes would), and so they
109 are explicitly redirected to files. The pidfile is created as
110 part of the process of launching the command. The pidfile
111 holds the pid and ppid of the process forked in background,
112 so later on it is possible to check whether the command is still
115 USAGE: long-lived commands that can run detached in background,
116 for which it is NOT necessary to block (wait) until the command
117 has finished. (e.g. start an application that is not using X11
118 forwarding. It can run detached and remotely in background)
122 HOW IT WORKS: Similar to 'run' except that it 'blocks' until
123 the command has finished execution. It also checks whether
124 errors occurred during runtime by reading the exitcode file,
125 which contains the exit code of the command that was run
126 (checking stderr only is not always reliable since many
127 commands throw debugging info to stderr and the only way to
128 automatically know whether an error really happened is to
129 check the process exit code).
131 Another difference with respect to 'run', is that instead
132 of directly executing the command as a bash command line,
133 it uploads the command to a bash script and runs the script.
134 This allows to use the bash script to debug errors, since
135 it remains at the remote host and can be run manually to
138 USAGE: medium-lived commands that can run detached in
139 background, for which it IS necessary to block (wait) until
140 the command has finished. (e.g. Package installation,
141 source compilation, file download, etc)
145 _help = "Controls Linux host machines ( either localhost or a host " \
146 "that can be accessed using a SSH key)"
147 _backend_type = "linux"
150 def _register_attributes(cls):
151 hostname = Attribute("hostname", "Hostname of the machine",
152 flags = Flags.Design)
154 username = Attribute("username", "Local account username",
155 flags = Flags.Credential)
157 port = Attribute("port", "SSH port", flags = Flags.Design)
159 home = Attribute("home",
160 "Experiment home directory to store all experiment related files",
161 flags = Flags.Design)
163 identity = Attribute("identity", "SSH identity file",
164 flags = Flags.Credential)
166 server_key = Attribute("serverKey", "Server public key",
167 flags = Flags.Design)
169 clean_home = Attribute("cleanHome", "Remove all nepi files and directories "
170 " from node home folder before starting experiment",
173 flags = Flags.Design)
175 clean_experiment = Attribute("cleanExperiment", "Remove all files and directories "
176 " from a previous same experiment, before the new experiment starts",
179 flags = Flags.Design)
181 clean_processes = Attribute("cleanProcesses",
182 "Kill all running processes before starting experiment",
185 flags = Flags.Design)
187 tear_down = Attribute("tearDown", "Bash script to be executed before " + \
188 "releasing the resource",
189 flags = Flags.Design)
191 gateway_user = Attribute("gatewayUser", "Gateway account username",
192 flags = Flags.Design)
194 gateway = Attribute("gateway", "Hostname of the gateway machine",
195 flags = Flags.Design)
197 cls._register_attribute(hostname)
198 cls._register_attribute(username)
199 cls._register_attribute(port)
200 cls._register_attribute(home)
201 cls._register_attribute(identity)
202 cls._register_attribute(server_key)
203 cls._register_attribute(clean_home)
204 cls._register_attribute(clean_experiment)
205 cls._register_attribute(clean_processes)
206 cls._register_attribute(tear_down)
207 cls._register_attribute(gateway_user)
208 cls._register_attribute(gateway)
210 def __init__(self, ec, guid):
211 super(LinuxNode, self).__init__(ec, guid)
213 # home directory at Linux host
216 # lock to prevent concurrent applications on the same node,
217 # to execute commands at the same time. There are potential
218 # concurrency issues when using SSH to a same host from
219 # multiple threads. There are also possible operational
220 # issues, e.g. an application querying the existence
221 # of a file or folder prior to its creation, and another
222 # application creating the same file or folder in between.
223 self._node_lock = threading.Lock()
225 def log_message(self, msg):
226 return " guid %d - host %s - %s " % (self.guid,
227 self.get("hostname"), msg)
231 home = self.get("home") or ""
232 if not home.startswith("/"):
233 home = os.path.join(self._home_dir, home)
238 return os.path.join(self.home_dir, "nepi-usr")
242 return os.path.join(self.usr_dir, "lib")
246 return os.path.join(self.usr_dir, "bin")
250 return os.path.join(self.usr_dir, "src")
254 return os.path.join(self.usr_dir, "share")
258 return os.path.join(self.home_dir, "nepi-exp")
262 return os.path.join(self.exp_dir, self.ec.exp_id)
266 return os.path.join(self.exp_home, "node-%d" % self.guid)
270 return os.path.join(self.node_home, self.ec.run_id)
277 if self.get("hostname") not in ["localhost", "127.0.0.1"] and \
278 not self.get("username"):
279 msg = "Can't resolve OS, insufficient data "
281 raise RuntimeError, msg
285 if out.find("Fedora release 8") == 0:
286 self._os = OSType.FEDORA_8
287 elif out.find("Fedora release 12") == 0:
288 self._os = OSType.FEDORA_12
289 elif out.find("Fedora release 14") == 0:
290 self._os = OSType.FEDORA_14
291 elif out.find("Fedora release") == 0:
292 self._os = OSType.FEDORA
293 elif out.find("Debian") == 0:
294 self._os = OSType.DEBIAN
295 elif out.find("Ubuntu") ==0:
296 self._os = OSType.UBUNTU
298 msg = "Unsupported OS"
300 raise RuntimeError, "%s - %s " %( msg, out )
305 # The underlying SSH layer will sometimes return an empty
306 # output (even if the command was executed without errors).
307 # To work arround this, repeat the operation N times or
308 # until the result is not empty string
311 (out, err), proc = self.execute("cat /etc/issue",
315 trace = traceback.format_exc()
316 msg = "Error detecting OS: %s " % trace
317 self.error(msg, out, err)
323 return self.os in [OSType.DEBIAN, OSType.UBUNTU]
327 return self.os in [OSType.FEDORA_12, OSType.FEDORA_14, OSType.FEDORA_8,
332 return self.get("hostname") in ['localhost', '127.0.0.7', '::1']
334 def do_provision(self):
335 # check if host is alive
336 if not self.is_alive():
337 msg = "Deploy failed. Unresponsive node %s" % self.get("hostname")
339 raise RuntimeError, msg
343 if self.get("cleanProcesses"):
344 self.clean_processes()
346 if self.get("cleanHome"):
349 if self.get("cleanExperiment"):
350 self.clean_experiment()
352 # Create shared directory structure
353 self.mkdir(self.lib_dir)
354 self.mkdir(self.bin_dir)
355 self.mkdir(self.src_dir)
356 self.mkdir(self.share_dir)
358 # Create experiment node home directory
359 self.mkdir(self.node_home)
361 super(LinuxNode, self).do_provision()
364 if self.state == ResourceState.NEW:
365 self.info("Deploying node")
369 # Node needs to wait until all associated interfaces are
370 # ready before it can finalize deployment
371 from nepi.resources.linux.interface import LinuxInterface
372 ifaces = self.get_connected(LinuxInterface.get_rtype())
374 if iface.state < ResourceState.READY:
375 self.ec.schedule(reschedule_delay, self.deploy)
378 super(LinuxNode, self).do_deploy()
380 def do_release(self):
381 rms = self.get_connected()
383 # Node needs to wait until all associated RMs are released
384 # before it can be released
385 if rm.state != ResourceState.RELEASED:
386 self.ec.schedule(reschedule_delay, self.release)
389 tear_down = self.get("tearDown")
391 self.execute(tear_down)
393 self.clean_processes()
395 super(LinuxNode, self).do_release()
397 def valid_connection(self, guid):
401 def clean_processes(self):
402 self.info("Cleaning up processes")
404 if self.get("hostname") in ["localhost", "127.0.0.2"]:
407 if self.get("username") != 'root':
408 cmd = ("sudo -S killall tcpdump || /bin/true ; " +
409 "sudo -S kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; " +
410 "sudo -S killall -u %s || /bin/true ; " % self.get("username"))
412 if self.state >= ResourceState.READY:
414 pids = pickle.load(open("/tmp/save.proc", "rb"))
416 ps_aux = "ps aux |awk '{print $2,$11}'"
417 (out, err), proc = self.execute(ps_aux)
418 for line in out.strip().split("\n"):
419 parts = line.strip().split(" ")
420 pids_temp[parts[0]] = parts[1]
421 kill_pids = set(pids_temp.items()) - set(pids.items())
422 kill_pids = ' '.join(dict(kill_pids).keys())
424 cmd = ("killall tcpdump || /bin/true ; " +
425 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; " +
426 "kill %s || /bin/true ; " % kill_pids)
428 cmd = ("killall tcpdump || /bin/true ; " +
429 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; ")
431 (out, err), proc = self.execute(cmd, retry = 1, with_lock = True)
433 def clean_home(self):
434 """ Cleans all NEPI related folders in the Linux host
436 self.info("Cleaning up home")
438 cmd = "cd %s ; find . -maxdepth 1 \( -name 'nepi-usr' -o -name 'nepi-exp' \) -execdir rm -rf {} + " % (
441 return self.execute(cmd, with_lock = True)
443 def clean_experiment(self):
444 """ Cleans all experiment related files in the Linux host.
445 It preserves NEPI files and folders that have a multi experiment
448 self.info("Cleaning up experiment files")
450 cmd = "cd %s ; find . -maxdepth 1 -name '%s' -execdir rm -rf {} + " % (
454 return self.execute(cmd, with_lock = True)
456 def execute(self, command,
462 connect_timeout = 30,
463 strict_host_checking = False,
468 """ Notice that this invocation will block until the
469 execution finishes. If this is not the desired behavior,
470 use 'run' instead."""
473 (out, err), proc = execfuncs.lexec(command,
474 user = self.get("username"), # still problem with localhost
479 # If the execute command is blocking, we don't want to keep
480 # the node lock. This lock is used to avoid race conditions
481 # when creating the ControlMaster sockets. A more elegant
482 # solution is needed.
483 with self._node_lock:
484 (out, err), proc = sshfuncs.rexec(
486 host = self.get("hostname"),
487 user = self.get("username"),
488 port = self.get("port"),
489 gwuser = self.get("gatewayUser"),
490 gw = self.get("gateway"),
493 identity = self.get("identity"),
494 server_key = self.get("serverKey"),
497 forward_x11 = forward_x11,
499 connect_timeout = connect_timeout,
500 persistent = persistent,
502 strict_host_checking = strict_host_checking
505 (out, err), proc = sshfuncs.rexec(
507 host = self.get("hostname"),
508 user = self.get("username"),
509 port = self.get("port"),
510 gwuser = self.get("gatewayUser"),
511 gw = self.get("gateway"),
514 identity = self.get("identity"),
515 server_key = self.get("serverKey"),
518 forward_x11 = forward_x11,
520 connect_timeout = connect_timeout,
521 persistent = persistent,
523 strict_host_checking = strict_host_checking
526 return (out, err), proc
528 def run(self, command, home,
537 self.debug("Running command '%s'" % command)
540 (out, err), proc = execfuncs.lspawn(command, pidfile,
542 create_home = create_home,
543 stdin = stdin or '/dev/null',
544 stdout = stdout or '/dev/null',
545 stderr = stderr or '/dev/null',
548 with self._node_lock:
549 (out, err), proc = sshfuncs.rspawn(
553 create_home = create_home,
554 stdin = stdin or '/dev/null',
555 stdout = stdout or '/dev/null',
556 stderr = stderr or '/dev/null',
558 host = self.get("hostname"),
559 user = self.get("username"),
560 port = self.get("port"),
561 gwuser = self.get("gatewayUser"),
562 gw = self.get("gateway"),
564 identity = self.get("identity"),
565 server_key = self.get("serverKey"),
569 return (out, err), proc
571 def getpid(self, home, pidfile = "pidfile"):
573 pidtuple = execfuncs.lgetpid(os.path.join(home, pidfile))
575 with self._node_lock:
576 pidtuple = sshfuncs.rgetpid(
577 os.path.join(home, pidfile),
578 host = self.get("hostname"),
579 user = self.get("username"),
580 port = self.get("port"),
581 gwuser = self.get("gatewayUser"),
582 gw = self.get("gateway"),
584 identity = self.get("identity"),
585 server_key = self.get("serverKey")
590 def status(self, pid, ppid):
592 status = execfuncs.lstatus(pid, ppid)
594 with self._node_lock:
595 status = sshfuncs.rstatus(
597 host = self.get("hostname"),
598 user = self.get("username"),
599 port = self.get("port"),
600 gwuser = self.get("gatewayUser"),
601 gw = self.get("gateway"),
603 identity = self.get("identity"),
604 server_key = self.get("serverKey")
609 def kill(self, pid, ppid, sudo = False):
612 status = self.status(pid, ppid)
614 if status == sshfuncs.ProcStatus.RUNNING:
616 (out, err), proc = execfuncs.lkill(pid, ppid, sudo)
618 with self._node_lock:
619 (out, err), proc = sshfuncs.rkill(
621 host = self.get("hostname"),
622 user = self.get("username"),
623 port = self.get("port"),
624 gwuser = self.get("gatewayUser"),
625 gw = self.get("gateway"),
628 identity = self.get("identity"),
629 server_key = self.get("serverKey")
632 return (out, err), proc
634 def copy(self, src, dst):
636 (out, err), proc = execfuncs.lcopy(src, dst,
639 with self._node_lock:
640 (out, err), proc = sshfuncs.rcopy(
642 port = self.get("port"),
643 gwuser = self.get("gatewayUser"),
644 gw = self.get("gateway"),
645 identity = self.get("identity"),
646 server_key = self.get("serverKey"),
648 strict_host_checking = False)
650 return (out, err), proc
652 def upload(self, src, dst, text = False, overwrite = True,
653 raise_on_error = True):
654 """ Copy content to destination
656 src string with the content to copy. Can be:
658 - a string with the path to a local file
659 - a string with a semi-colon separeted list of local files
660 - a string with a local directory
662 dst string with destination path on the remote host (remote is
665 text src is text input, it must be stored into a temp file before
668 # If source is a string input
670 if text and not os.path.isfile(src):
671 # src is text input that should be uploaded as file
672 # create a temporal file with the content to upload
673 f = tempfile.NamedTemporaryFile(delete=False)
678 # If dst files should not be overwritten, check that the files do not
680 if isinstance(src, str):
681 src = map(str.strip, src.split(";"))
683 if overwrite == False:
684 src = self.filter_existing_files(src, dst)
686 return ("", ""), None
688 if not self.localhost:
689 # Build destination as <user>@<server>:<path>
690 dst = "%s@%s:%s" % (self.get("username"), self.get("hostname"), dst)
692 ((out, err), proc) = self.copy(src, dst)
699 msg = " Failed to upload files - src: %s dst: %s" % (";".join(src), dst)
700 self.error(msg, out, err)
703 raise RuntimeError, msg
705 return ((out, err), proc)
707 def download(self, src, dst, raise_on_error = True):
708 if not self.localhost:
709 # Build destination as <user>@<server>:<path>
710 src = "%s@%s:%s" % (self.get("username"), self.get("hostname"), src)
712 ((out, err), proc) = self.copy(src, dst)
715 msg = " Failed to download files - src: %s dst: %s" % (";".join(src), dst)
716 self.error(msg, out, err)
719 raise RuntimeError, msg
721 return ((out, err), proc)
723 def install_packages_command(self, packages):
726 command = rpmfuncs.install_packages_command(self.os, packages)
728 command = debfuncs.install_packages_command(self.os, packages)
730 msg = "Error installing packages ( OS not known ) "
731 self.error(msg, self.os)
732 raise RuntimeError, msg
736 def install_packages(self, packages, home, run_home = None,
737 raise_on_error = True):
738 """ Install packages in the Linux host.
740 'home' is the directory to upload the package installation script.
741 'run_home' is the directory from where to execute the script.
743 command = self.install_packages_command(packages)
745 run_home = run_home or home
747 (out, err), proc = self.run_and_wait(command, run_home,
748 shfile = os.path.join(home, "instpkg.sh"),
749 pidfile = "instpkg_pidfile",
750 ecodefile = "instpkg_exitcode",
751 stdout = "instpkg_stdout",
752 stderr = "instpkg_stderr",
754 raise_on_error = raise_on_error)
756 return (out, err), proc
758 def remove_packages(self, packages, home, run_home = None,
759 raise_on_error = True):
760 """ Uninstall packages from the Linux host.
762 'home' is the directory to upload the package un-installation script.
763 'run_home' is the directory from where to execute the script.
766 command = rpmfuncs.remove_packages_command(self.os, packages)
768 command = debfuncs.remove_packages_command(self.os, packages)
770 msg = "Error removing packages ( OS not known ) "
772 raise RuntimeError, msg
774 run_home = run_home or home
776 (out, err), proc = self.run_and_wait(command, run_home,
777 shfile = os.path.join(home, "rmpkg.sh"),
778 pidfile = "rmpkg_pidfile",
779 ecodefile = "rmpkg_exitcode",
780 stdout = "rmpkg_stdout",
781 stderr = "rmpkg_stderr",
783 raise_on_error = raise_on_error)
785 return (out, err), proc
787 def mkdir(self, path, clean = False):
791 return self.execute("mkdir -p %s" % path, with_lock = True)
793 def rmdir(self, path):
794 return self.execute("rm -rf %s" % path, with_lock = True)
796 def run_and_wait(self, command, home,
801 ecodefile = "exitcode",
807 raise_on_error = True):
809 Uploads the 'command' to a bash script in the host.
810 Then runs the script detached in background in the host, and
811 busy-waites until the script finishes executing.
814 if not shfile.startswith("/"):
815 shfile = os.path.join(home, shfile)
817 self.upload_command(command,
819 ecodefile = ecodefile,
821 overwrite = overwrite)
823 command = "bash %s" % shfile
824 # run command in background in remote host
825 (out, err), proc = self.run(command, home,
833 # check no errors occurred
835 msg = " Failed to run command '%s' " % command
836 self.error(msg, out, err)
838 raise RuntimeError, msg
840 # Wait for pid file to be generated
841 pid, ppid = self.wait_pid(
844 raise_on_error = raise_on_error)
846 # wait until command finishes to execute
847 self.wait_run(pid, ppid)
849 (eout, err), proc = self.check_errors(home,
850 ecodefile = ecodefile,
853 # Out is what was written in the stderr file
855 msg = " Failed to run command '%s' " % command
856 self.error(msg, eout, err)
859 raise RuntimeError, msg
861 (out, oerr), proc = self.check_output(home, stdout)
863 return (out, err), proc
865 def exitcode(self, home, ecodefile = "exitcode"):
867 Get the exit code of an application.
868 Returns an integer value with the exit code
870 (out, err), proc = self.check_output(home, ecodefile)
872 # Succeeded to open file, return exit code in the file
875 return int(out.strip())
877 # Error in the content of the file!
878 return ExitCode.CORRUPTFILE
880 # No such file or directory
881 if proc.returncode == 1:
882 return ExitCode.FILENOTFOUND
884 # Other error from 'cat'
885 return ExitCode.ERROR
887 def upload_command(self, command,
889 ecodefile = "exitcode",
892 """ Saves the command as a bash script file in the remote host, and
893 forces to save the exit code of the command execution to the ecodefile
896 if not (command.strip().endswith(";") or command.strip().endswith("&")):
899 # The exit code of the command will be stored in ecodefile
900 command = " { %(command)s } ; echo $? > %(ecodefile)s ;" % {
902 'ecodefile': ecodefile,
906 environ = self.format_environment(env)
908 # Add environ to command
909 command = environ + command
911 return self.upload(command, shfile, text = True, overwrite = overwrite)
913 def format_environment(self, env, inline = False):
914 """ Formats the environment variables for a command to be executed
915 either as an inline command
916 (i.e. export PYTHONPATH=src/..; export LALAL= ..;python script.py) or
917 as a bash script (i.e. export PYTHONPATH=src/.. \n export LALA=.. \n)
919 if not env: return ""
921 # Remove extra white spaces
922 env = re.sub(r'\s+', ' ', env.strip())
924 sep = ";" if inline else "\n"
925 return sep.join(map(lambda e: " export %s" % e, env.split(" "))) + sep
927 def check_errors(self, home,
928 ecodefile = "exitcode",
930 """ Checks whether errors occurred while running a command.
931 It first checks the exit code for the command, and only if the
932 exit code is an error one it returns the error output.
938 # get exit code saved in the 'exitcode' file
939 ecode = self.exitcode(home, ecodefile)
941 if ecode in [ ExitCode.CORRUPTFILE, ExitCode.ERROR ]:
942 err = "Error retrieving exit code status from file %s/%s" % (home, ecodefile)
943 elif ecode > 0 or ecode == ExitCode.FILENOTFOUND:
944 # The process returned an error code or didn't exist.
945 # Check standard error.
946 (err, eerr), proc = self.check_output(home, stderr)
948 # If the stderr file was not found, assume nothing bad happened,
949 # and just ignore the error.
950 # (cat returns 1 for error "No such file or directory")
951 if ecode == ExitCode.FILENOTFOUND and proc.poll() == 1:
954 return ("", err), proc
956 def wait_pid(self, home, pidfile = "pidfile", raise_on_error = False):
957 """ Waits until the pid file for the command is generated,
958 and returns the pid and ppid of the process """
963 pidtuple = self.getpid(home = home, pidfile = pidfile)
972 msg = " Failed to get pid for pidfile %s/%s " % (
977 raise RuntimeError, msg
981 def wait_run(self, pid, ppid, trial = 0):
982 """ wait for a remote process to finish execution """
986 status = self.status(pid, ppid)
988 if status is ProcStatus.FINISHED:
990 elif status is not ProcStatus.RUNNING:
993 # If it takes more than 20 seconds to start, then
994 # asume something went wrong
998 # The app is running, just wait...
1001 def check_output(self, home, filename):
1002 """ Retrives content of file """
1003 (out, err), proc = self.execute("cat %s" %
1004 os.path.join(home, filename), retry = 1, with_lock = True)
1005 return (out, err), proc
1008 """ Checks if host is responsive
1014 msg = "Unresponsive host. Wrong answer. "
1016 # The underlying SSH layer will sometimes return an empty
1017 # output (even if the command was executed without errors).
1018 # To work arround this, repeat the operation N times or
1019 # until the result is not empty string
1021 (out, err), proc = self.execute("echo 'ALIVE'",
1025 if out.find("ALIVE") > -1:
1028 trace = traceback.format_exc()
1029 msg = "Unresponsive host. Error reaching host: %s " % trace
1031 self.error(msg, out, err)
1034 def find_home(self):
1035 """ Retrieves host home directory
1037 # The underlying SSH layer will sometimes return an empty
1038 # output (even if the command was executed without errors).
1039 # To work arround this, repeat the operation N times or
1040 # until the result is not empty string
1041 msg = "Impossible to retrieve HOME directory"
1043 (out, err), proc = self.execute("echo ${HOME}",
1047 if out.strip() != "":
1048 self._home_dir = out.strip()
1050 trace = traceback.format_exc()
1051 msg = "Impossible to retrieve HOME directory %s" % trace
1053 if not self._home_dir:
1055 raise RuntimeError, msg
1057 def filter_existing_files(self, src, dst):
1058 """ Removes files that already exist in the Linux host from src list
1060 # construct a dictionary with { dst: src }
1062 lambda s: (os.path.join(dst, os.path.basename(s)), s ), s)) \
1063 if len(src) > 1 else dict({dst: src[0]})
1066 for d in dests.keys():
1067 command.append(" [ -f %(dst)s ] && echo '%(dst)s' " % {'dst' : d} )
1069 command = ";".join(command)
1071 (out, err), proc = self.execute(command, retry = 1, with_lock = True)
1073 for d in dests.keys():
1074 if out.find(d) > -1:
1080 return dests.values()