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.ExecReadOnly)
154 username = Attribute("username", "Local account username",
155 flags = Flags.Credential)
157 port = Attribute("port", "SSH port", flags = Flags.ExecReadOnly)
159 home = Attribute("home",
160 "Experiment home directory to store all experiment related files",
161 flags = Flags.ExecReadOnly)
163 identity = Attribute("identity", "SSH identity file",
164 flags = Flags.Credential)
166 server_key = Attribute("serverKey", "Server public key",
167 flags = Flags.ExecReadOnly)
169 clean_home = Attribute("cleanHome", "Remove all nepi files and directories "
170 " from node home folder before starting experiment",
173 flags = Flags.ExecReadOnly)
175 clean_experiment = Attribute("cleanExperiment", "Remove all files and directories "
176 " from a previous same experiment, before the new experiment starts",
179 flags = Flags.ExecReadOnly)
181 clean_processes = Attribute("cleanProcesses",
182 "Kill all running processes before starting experiment",
185 flags = Flags.ExecReadOnly)
187 tear_down = Attribute("tearDown", "Bash script to be executed before " + \
188 "releasing the resource",
189 flags = Flags.ExecReadOnly)
191 gateway_user = Attribute("gatewayUser", "Gateway account username",
192 flags = Flags.ExecReadOnly)
194 gateway = Attribute("gateway", "Hostname of the gateway machine",
195 flags = Flags.ExecReadOnly)
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 (not self.get("hostname") or not self.get("username")):
278 msg = "Can't resolve OS, insufficient data "
280 raise RuntimeError, msg
284 if out.find("Fedora release 8") == 0:
285 self._os = OSType.FEDORA_8
286 elif out.find("Fedora release 12") == 0:
287 self._os = OSType.FEDORA_12
288 elif out.find("Fedora release 14") == 0:
289 self._os = OSType.FEDORA_14
290 elif out.find("Fedora release") == 0:
291 self._os = OSType.FEDORA
292 elif out.find("Debian") == 0:
293 self._os = OSType.DEBIAN
294 elif out.find("Ubuntu") ==0:
295 self._os = OSType.UBUNTU
297 msg = "Unsupported OS"
299 raise RuntimeError, "%s - %s " %( msg, out )
304 # The underlying SSH layer will sometimes return an empty
305 # output (even if the command was executed without errors).
306 # To work arround this, repeat the operation N times or
307 # until the result is not empty string
310 (out, err), proc = self.execute("cat /etc/issue",
314 trace = traceback.format_exc()
315 msg = "Error detecting OS: %s " % trace
316 self.error(msg, out, err)
322 return self.os in [OSType.DEBIAN, OSType.UBUNTU]
326 return self.os in [OSType.FEDORA_12, OSType.FEDORA_14, OSType.FEDORA_8,
331 return self.get("hostname") in ['localhost', '127.0.0.7', '::1']
333 def do_provision(self):
334 # check if host is alive
335 if not self.is_alive():
336 msg = "Deploy failed. Unresponsive node %s" % self.get("hostname")
338 raise RuntimeError, msg
342 if self.get("cleanProcesses"):
343 self.clean_processes()
345 if self.get("cleanHome"):
348 if self.get("cleanExperiment"):
349 self.clean_experiment()
351 # Create shared directory structure
352 self.mkdir(self.lib_dir)
353 self.mkdir(self.bin_dir)
354 self.mkdir(self.src_dir)
355 self.mkdir(self.share_dir)
357 # Create experiment node home directory
358 self.mkdir(self.node_home)
360 super(LinuxNode, self).do_provision()
363 if self.state == ResourceState.NEW:
364 self.info("Deploying node")
368 # Node needs to wait until all associated interfaces are
369 # ready before it can finalize deployment
370 from nepi.resources.linux.interface import LinuxInterface
371 ifaces = self.get_connected(LinuxInterface.get_rtype())
373 if iface.state < ResourceState.READY:
374 self.ec.schedule(reschedule_delay, self.deploy)
377 super(LinuxNode, self).do_deploy()
379 def do_release(self):
380 rms = self.get_connected()
382 # Node needs to wait until all associated RMs are released
383 # before it can be released
384 if rm.state != ResourceState.RELEASED:
385 self.ec.schedule(reschedule_delay, self.release)
388 tear_down = self.get("tearDown")
390 self.execute(tear_down)
392 self.clean_processes()
394 super(LinuxNode, self).do_release()
396 def valid_connection(self, guid):
400 def clean_processes(self):
401 self.info("Cleaning up processes")
403 if self.get("username") != 'root':
404 cmd = ("sudo -S killall tcpdump || /bin/true ; " +
405 "sudo -S kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; " +
406 "sudo -S killall -u %s || /bin/true ; " % self.get("username"))
408 if self.state >= ResourceState.READY:
410 pids = pickle.load(open("/tmp/save.proc", "rb"))
412 ps_aux = "ps aux |awk '{print $2,$11}'"
413 (out, err), proc = self.execute(ps_aux)
414 for line in out.strip().split("\n"):
415 parts = line.strip().split(" ")
416 pids_temp[parts[0]] = parts[1]
417 kill_pids = set(pids_temp.items()) - set(pids.items())
418 kill_pids = ' '.join(dict(kill_pids).keys())
420 cmd = ("killall tcpdump || /bin/true ; " +
421 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; " +
422 "kill %s || /bin/true ; " % kill_pids)
424 cmd = ("killall tcpdump || /bin/true ; " +
425 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; ")
428 (out, err), proc = self.execute(cmd, retry = 1, with_lock = True)
430 def clean_home(self):
431 """ Cleans all NEPI related folders in the Linux host
433 self.info("Cleaning up home")
435 cmd = "cd %s ; find . -maxdepth 1 \( -name 'nepi-usr' -o -name 'nepi-exp' \) -execdir rm -rf {} + " % (
438 return self.execute(cmd, with_lock = True)
440 def clean_experiment(self):
441 """ Cleans all experiment related files in the Linux host.
442 It preserves NEPI files and folders that have a multi experiment
445 self.info("Cleaning up experiment files")
447 cmd = "cd %s ; find . -maxdepth 1 -name '%s' -execdir rm -rf {} + " % (
451 return self.execute(cmd, with_lock = True)
453 def execute(self, command,
461 err_on_timeout = True,
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
480 with self._node_lock:
481 (out, err), proc = sshfuncs.rexec(
483 host = self.get("hostname"),
484 user = self.get("username"),
485 port = self.get("port"),
486 gwuser = self.get("gatewayUser"),
487 gw = self.get("gateway"),
491 identity = self.get("identity"),
492 server_key = self.get("serverKey"),
495 forward_x11 = forward_x11,
498 err_on_timeout = err_on_timeout,
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"),
515 identity = self.get("identity"),
516 server_key = self.get("serverKey"),
519 forward_x11 = forward_x11,
522 err_on_timeout = err_on_timeout,
523 connect_timeout = connect_timeout,
524 persistent = persistent,
526 strict_host_checking = strict_host_checking
529 return (out, err), proc
531 def run(self, command, home,
540 self.debug("Running command '%s'" % command)
543 (out, err), proc = execfuncs.lspawn(command, pidfile,
548 create_home = create_home,
552 with self._node_lock:
553 (out, err), proc = sshfuncs.rspawn(
557 create_home = create_home,
558 stdin = stdin if stdin is not None else '/dev/null',
559 stdout = stdout if stdout else '/dev/null',
560 stderr = stderr if stderr else '/dev/null',
562 host = self.get("hostname"),
563 user = self.get("username"),
564 port = self.get("port"),
565 gwuser = self.get("gatewayUser"),
566 gw = self.get("gateway"),
568 identity = self.get("identity"),
569 server_key = self.get("serverKey"),
573 return (out, err), proc
575 def getpid(self, home, pidfile = "pidfile"):
577 pidtuple = execfuncs.lgetpid(os.path.join(home, pidfile))
579 with self._node_lock:
580 pidtuple = sshfuncs.rgetpid(
581 os.path.join(home, pidfile),
582 host = self.get("hostname"),
583 user = self.get("username"),
584 port = self.get("port"),
585 gwuser = self.get("gatewayUser"),
586 gw = self.get("gateway"),
588 identity = self.get("identity"),
589 server_key = self.get("serverKey")
594 def status(self, pid, ppid):
596 status = execfuncs.lstatus(pid, ppid)
598 with self._node_lock:
599 status = sshfuncs.rstatus(
601 host = self.get("hostname"),
602 user = self.get("username"),
603 port = self.get("port"),
604 gwuser = self.get("gatewayUser"),
605 gw = self.get("gateway"),
607 identity = self.get("identity"),
608 server_key = self.get("serverKey")
613 def kill(self, pid, ppid, sudo = False):
616 status = self.status(pid, ppid)
618 if status == sshfuncs.ProcStatus.RUNNING:
620 (out, err), proc = execfuncs.lkill(pid, ppid, sudo)
622 with self._node_lock:
623 (out, err), proc = sshfuncs.rkill(
625 host = self.get("hostname"),
626 user = self.get("username"),
627 port = self.get("port"),
628 gwuser = self.get("gatewayUser"),
629 gw = self.get("gateway"),
632 identity = self.get("identity"),
633 server_key = self.get("serverKey")
636 return (out, err), proc
638 def copy(self, src, dst):
640 (out, err), proc = execfuncs.lcopy(source, dest,
642 strict_host_checking = False)
644 with self._node_lock:
645 (out, err), proc = sshfuncs.rcopy(
647 port = self.get("port"),
648 gwuser = self.get("gatewayUser"),
649 gw = self.get("gateway"),
650 identity = self.get("identity"),
651 server_key = self.get("serverKey"),
653 strict_host_checking = False)
655 return (out, err), proc
657 def upload(self, src, dst, text = False, overwrite = True):
658 """ Copy content to destination
660 src string with the content to copy. Can be:
662 - a string with the path to a local file
663 - a string with a colon-separeted list of local files
664 - a string with a local directory
666 dst string with destination path on the remote host (remote is
669 text src is text input, it must be stored into a temp file before
672 # If source is a string input
674 if text and not os.path.isfile(src):
675 # src is text input that should be uploaded as file
676 # create a temporal file with the content to upload
677 f = tempfile.NamedTemporaryFile(delete=False)
682 # If dst files should not be overwritten, check that the files do not
684 if isinstance(src, str):
685 src = map(str.strip, src.split(";"))
687 if overwrite == False:
688 src = self.filter_existing_files(src, dst)
690 return ("", ""), None
692 if not self.localhost:
693 # Build destination as <user>@<server>:<path>
694 dst = "%s@%s:%s" % (self.get("username"), self.get("hostname"), dst)
696 result = self.copy(src, dst)
704 def download(self, src, dst):
705 if not self.localhost:
706 # Build destination as <user>@<server>:<path>
707 src = "%s@%s:%s" % (self.get("username"), self.get("hostname"), src)
708 return self.copy(src, dst)
710 def install_packages_command(self, packages):
713 command = rpmfuncs.install_packages_command(self.os, packages)
715 command = debfuncs.install_packages_command(self.os, packages)
717 msg = "Error installing packages ( OS not known ) "
718 self.error(msg, self.os)
719 raise RuntimeError, msg
723 def install_packages(self, packages, home, run_home = None):
724 """ Install packages in the Linux host.
726 'home' is the directory to upload the package installation script.
727 'run_home' is the directory from where to execute the script.
729 command = self.install_packages_command(packages)
731 run_home = run_home or home
733 (out, err), proc = self.run_and_wait(command, run_home,
734 shfile = os.path.join(home, "instpkg.sh"),
735 pidfile = "instpkg_pidfile",
736 ecodefile = "instpkg_exitcode",
737 stdout = "instpkg_stdout",
738 stderr = "instpkg_stderr",
740 raise_on_error = True)
742 return (out, err), proc
744 def remove_packages(self, packages, home, run_home = None):
745 """ Uninstall packages from the Linux host.
747 'home' is the directory to upload the package un-installation script.
748 'run_home' is the directory from where to execute the script.
751 command = rpmfuncs.remove_packages_command(self.os, packages)
753 command = debfuncs.remove_packages_command(self.os, packages)
755 msg = "Error removing packages ( OS not known ) "
757 raise RuntimeError, msg
759 run_home = run_home or home
761 (out, err), proc = self.run_and_wait(command, run_home,
762 shfile = os.path.join(home, "rmpkg.sh"),
763 pidfile = "rmpkg_pidfile",
764 ecodefile = "rmpkg_exitcode",
765 stdout = "rmpkg_stdout",
766 stderr = "rmpkg_stderr",
768 raise_on_error = True)
770 return (out, err), proc
772 def mkdir(self, path, clean = False):
776 return self.execute("mkdir -p %s" % path, with_lock = True)
778 def rmdir(self, path):
779 return self.execute("rm -rf %s" % path, with_lock = True)
781 def run_and_wait(self, command, home,
786 ecodefile = "exitcode",
792 raise_on_error = False):
794 Uploads the 'command' to a bash script in the host.
795 Then runs the script detached in background in the host, and
796 busy-waites until the script finishes executing.
799 if not shfile.startswith("/"):
800 shfile = os.path.join(home, shfile)
802 self.upload_command(command,
804 ecodefile = ecodefile,
806 overwrite = overwrite)
808 command = "bash %s" % shfile
809 # run command in background in remote host
810 (out, err), proc = self.run(command, home,
818 # check no errors occurred
820 msg = " Failed to run command '%s' " % command
821 self.error(msg, out, err)
823 raise RuntimeError, msg
825 # Wait for pid file to be generated
826 pid, ppid = self.wait_pid(
829 raise_on_error = raise_on_error)
831 # wait until command finishes to execute
832 self.wait_run(pid, ppid)
834 (eout, err), proc = self.check_errors(home,
835 ecodefile = ecodefile,
838 # Out is what was written in the stderr file
840 msg = " Failed to run command '%s' " % command
841 self.error(msg, eout, err)
844 raise RuntimeError, msg
846 (out, oerr), proc = self.check_output(home, stdout)
848 return (out, err), proc
850 def exitcode(self, home, ecodefile = "exitcode"):
852 Get the exit code of an application.
853 Returns an integer value with the exit code
855 (out, err), proc = self.check_output(home, ecodefile)
857 # Succeeded to open file, return exit code in the file
860 return int(out.strip())
862 # Error in the content of the file!
863 return ExitCode.CORRUPTFILE
865 # No such file or directory
866 if proc.returncode == 1:
867 return ExitCode.FILENOTFOUND
869 # Other error from 'cat'
870 return ExitCode.ERROR
872 def upload_command(self, command,
874 ecodefile = "exitcode",
877 """ Saves the command as a bash script file in the remote host, and
878 forces to save the exit code of the command execution to the ecodefile
881 if not (command.strip().endswith(";") or command.strip().endswith("&")):
884 # The exit code of the command will be stored in ecodefile
885 command = " { %(command)s } ; echo $? > %(ecodefile)s ;" % {
887 'ecodefile': ecodefile,
891 environ = self.format_environment(env)
893 # Add environ to command
894 command = environ + command
896 return self.upload(command, shfile, text = True, overwrite = overwrite)
898 def format_environment(self, env, inline = False):
899 """ Formats the environment variables for a command to be executed
900 either as an inline command
901 (i.e. export PYTHONPATH=src/..; export LALAL= ..;python script.py) or
902 as a bash script (i.e. export PYTHONPATH=src/.. \n export LALA=.. \n)
904 if not env: return ""
906 # Remove extra white spaces
907 env = re.sub(r'\s+', ' ', env.strip())
909 sep = ";" if inline else "\n"
910 return sep.join(map(lambda e: " export %s" % e, env.split(" "))) + sep
912 def check_errors(self, home,
913 ecodefile = "exitcode",
915 """ Checks whether errors occurred while running a command.
916 It first checks the exit code for the command, and only if the
917 exit code is an error one it returns the error output.
923 # get exit code saved in the 'exitcode' file
924 ecode = self.exitcode(home, ecodefile)
926 if ecode in [ ExitCode.CORRUPTFILE, ExitCode.ERROR ]:
927 err = "Error retrieving exit code status from file %s/%s" % (home, ecodefile)
928 elif ecode > 0 or ecode == ExitCode.FILENOTFOUND:
929 # The process returned an error code or didn't exist.
930 # Check standard error.
931 (err, eerr), proc = self.check_output(home, stderr)
933 # If the stderr file was not found, assume nothing bad happened,
934 # and just ignore the error.
935 # (cat returns 1 for error "No such file or directory")
936 if ecode == ExitCode.FILENOTFOUND and proc.poll() == 1:
939 return ("", err), proc
941 def wait_pid(self, home, pidfile = "pidfile", raise_on_error = False):
942 """ Waits until the pid file for the command is generated,
943 and returns the pid and ppid of the process """
948 pidtuple = self.getpid(home = home, pidfile = pidfile)
957 msg = " Failed to get pid for pidfile %s/%s " % (
962 raise RuntimeError, msg
966 def wait_run(self, pid, ppid, trial = 0):
967 """ wait for a remote process to finish execution """
971 status = self.status(pid, ppid)
973 if status is ProcStatus.FINISHED:
975 elif status is not ProcStatus.RUNNING:
978 # If it takes more than 20 seconds to start, then
979 # asume something went wrong
983 # The app is running, just wait...
986 def check_output(self, home, filename):
987 """ Retrives content of file """
988 (out, err), proc = self.execute("cat %s" %
989 os.path.join(home, filename), retry = 1, with_lock = True)
990 return (out, err), proc
993 """ Checks if host is responsive
999 msg = "Unresponsive host. Wrong answer. "
1001 # The underlying SSH layer will sometimes return an empty
1002 # output (even if the command was executed without errors).
1003 # To work arround this, repeat the operation N times or
1004 # until the result is not empty string
1006 (out, err), proc = self.execute("echo 'ALIVE'",
1010 if out.find("ALIVE") > -1:
1013 trace = traceback.format_exc()
1014 msg = "Unresponsive host. Error reaching host: %s " % trace
1016 self.error(msg, out, err)
1019 def find_home(self):
1020 """ Retrieves host home directory
1022 # The underlying SSH layer will sometimes return an empty
1023 # output (even if the command was executed without errors).
1024 # To work arround this, repeat the operation N times or
1025 # until the result is not empty string
1026 msg = "Impossible to retrieve HOME directory"
1028 (out, err), proc = self.execute("echo ${HOME}",
1032 if out.strip() != "":
1033 self._home_dir = out.strip()
1035 trace = traceback.format_exc()
1036 msg = "Impossible to retrieve HOME directory %s" % trace
1038 if not self._home_dir:
1040 raise RuntimeError, msg
1042 def filter_existing_files(self, src, dst):
1043 """ Removes files that already exist in the Linux host from src list
1045 # construct a dictionary with { dst: src }
1047 lambda s: (os.path.join(dst, os.path.basename(s)), s ), s)) \
1048 if len(src) > 1 else dict({dst: src[0]})
1051 for d in dests.keys():
1052 command.append(" [ -f %(dst)s ] && echo '%(dst)s' " % {'dst' : d} )
1054 command = ";".join(command)
1056 (out, err), proc = self.execute(command, retry = 1, with_lock = True)
1058 for d in dests.keys():
1059 if out.find(d) > -1:
1065 return dests.values()