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")
242 return os.path.join(self.nepi_home, "nepi-usr")
246 return os.path.join(self.usr_dir, "lib")
250 return os.path.join(self.usr_dir, "bin")
254 return os.path.join(self.usr_dir, "src")
258 return os.path.join(self.usr_dir, "share")
262 return os.path.join(self.nepi_home, "nepi-exp")
266 return os.path.join(self.exp_dir, self.ec.exp_id)
270 return os.path.join(self.exp_home, "node-%d" % self.guid)
274 return os.path.join(self.node_home, self.ec.run_id)
281 if self.get("hostname") not in ["localhost", "127.0.0.1"] and \
282 not self.get("username"):
283 msg = "Can't resolve OS, insufficient data "
285 raise RuntimeError, msg
289 if out.find("Fedora release 8") == 0:
290 self._os = OSType.FEDORA_8
291 elif out.find("Fedora release 12") == 0:
292 self._os = OSType.FEDORA_12
293 elif out.find("Fedora release 14") == 0:
294 self._os = OSType.FEDORA_14
295 elif out.find("Fedora release") == 0:
296 self._os = OSType.FEDORA
297 elif out.find("Debian") == 0:
298 self._os = OSType.DEBIAN
299 elif out.find("Ubuntu") ==0:
300 self._os = OSType.UBUNTU
302 msg = "Unsupported OS"
304 raise RuntimeError, "%s - %s " %( msg, out )
309 # The underlying SSH layer will sometimes return an empty
310 # output (even if the command was executed without errors).
311 # To work arround this, repeat the operation N times or
312 # until the result is not empty string
315 (out, err), proc = self.execute("cat /etc/issue",
319 trace = traceback.format_exc()
320 msg = "Error detecting OS: %s " % trace
321 self.error(msg, out, err)
327 return self.os in [OSType.DEBIAN, OSType.UBUNTU]
331 return self.os in [OSType.FEDORA_12, OSType.FEDORA_14, OSType.FEDORA_8,
336 return self.get("hostname") in ['localhost', '127.0.0.7', '::1']
338 def do_provision(self):
339 # check if host is alive
340 if not self.is_alive():
341 msg = "Deploy failed. Unresponsive node %s" % self.get("hostname")
343 raise RuntimeError, msg
347 if self.get("cleanProcesses"):
348 self.clean_processes()
350 if self.get("cleanHome"):
353 if self.get("cleanExperiment"):
354 self.clean_experiment()
356 # Create shared directory structure and node home directory
357 paths = [self.lib_dir,
365 super(LinuxNode, self).do_provision()
368 if self.state == ResourceState.NEW:
369 self.info("Deploying node")
373 # Node needs to wait until all associated interfaces are
374 # ready before it can finalize deployment
375 from nepi.resources.linux.interface import LinuxInterface
376 ifaces = self.get_connected(LinuxInterface.get_rtype())
378 if iface.state < ResourceState.READY:
379 self.ec.schedule(reschedule_delay, self.deploy)
382 super(LinuxNode, self).do_deploy()
384 def do_release(self):
385 rms = self.get_connected()
387 # Node needs to wait until all associated RMs are released
388 # before it can be released
389 if rm.state != ResourceState.RELEASED:
390 self.ec.schedule(reschedule_delay, self.release)
393 tear_down = self.get("tearDown")
395 self.execute(tear_down)
397 self.clean_processes()
399 super(LinuxNode, self).do_release()
401 def valid_connection(self, guid):
405 def clean_processes(self):
406 self.info("Cleaning up processes")
408 if self.get("hostname") in ["localhost", "127.0.0.2"]:
411 if self.get("username") != 'root':
412 cmd = ("sudo -S killall tcpdump || /bin/true ; " +
413 "sudo -S killall -u %s || /bin/true ; " % self.get("username"))
415 if self.state >= ResourceState.READY:
417 pids = pickle.load(open("/tmp/save.proc", "rb"))
419 ps_aux = "ps aux |awk '{print $2,$11}'"
420 (out, err), proc = self.execute(ps_aux)
422 for line in out.strip().split("\n"):
423 parts = line.strip().split(" ")
424 pids_temp[parts[0]] = parts[1]
425 kill_pids = set(pids_temp.items()) - set(pids.items())
426 kill_pids = ' '.join(dict(kill_pids).keys())
428 cmd = ("killall tcpdump || /bin/true ; " +
429 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; " +
430 "kill %s || /bin/true ; " % kill_pids)
432 cmd = ("killall tcpdump || /bin/true ; " +
433 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; ")
435 cmd = ("killall tcpdump || /bin/true ; " +
436 "kill $(ps aux | grep '[n]epi' | awk '{print $2}') || /bin/true ; ")
438 (out, err), proc = self.execute(cmd, retry = 1, with_lock = True)
440 def clean_home(self):
441 """ Cleans all NEPI related folders in the Linux host
443 self.info("Cleaning up home")
445 cmd = "cd %s ; find . -maxdepth 1 -name \.nepi -execdir rm -rf {} + " % (
448 return self.execute(cmd, with_lock = True)
450 def clean_experiment(self):
451 """ Cleans all experiment related files in the Linux host.
452 It preserves NEPI files and folders that have a multi experiment
455 self.info("Cleaning up experiment files")
457 cmd = "cd %s ; find . -maxdepth 1 -name '%s' -execdir rm -rf {} + " % (
461 return self.execute(cmd, with_lock = True)
463 def execute(self, command,
469 connect_timeout = 30,
470 strict_host_checking = False,
475 """ Notice that this invocation will block until the
476 execution finishes. If this is not the desired behavior,
477 use 'run' instead."""
480 (out, err), proc = execfuncs.lexec(command,
481 user = self.get("username"), # still problem with localhost
486 # If the execute command is blocking, we don't want to keep
487 # the node lock. This lock is used to avoid race conditions
488 # when creating the ControlMaster sockets. A more elegant
489 # solution is needed.
490 with self._node_lock:
491 (out, err), proc = sshfuncs.rexec(
493 host = self.get("hostname"),
494 user = self.get("username"),
495 port = self.get("port"),
496 gwuser = self.get("gatewayUser"),
497 gw = self.get("gateway"),
500 identity = self.get("identity"),
501 server_key = self.get("serverKey"),
504 forward_x11 = forward_x11,
506 connect_timeout = connect_timeout,
507 persistent = persistent,
509 strict_host_checking = strict_host_checking
512 (out, err), proc = sshfuncs.rexec(
514 host = self.get("hostname"),
515 user = self.get("username"),
516 port = self.get("port"),
517 gwuser = self.get("gatewayUser"),
518 gw = self.get("gateway"),
521 identity = self.get("identity"),
522 server_key = self.get("serverKey"),
525 forward_x11 = forward_x11,
527 connect_timeout = connect_timeout,
528 persistent = persistent,
530 strict_host_checking = strict_host_checking
533 return (out, err), proc
535 def run(self, command, home,
544 self.debug("Running command '%s'" % command)
547 (out, err), proc = execfuncs.lspawn(command, pidfile,
549 create_home = create_home,
550 stdin = stdin or '/dev/null',
551 stdout = stdout or '/dev/null',
552 stderr = stderr or '/dev/null',
555 with self._node_lock:
556 (out, err), proc = sshfuncs.rspawn(
560 create_home = create_home,
561 stdin = stdin or '/dev/null',
562 stdout = stdout or '/dev/null',
563 stderr = stderr or '/dev/null',
565 host = self.get("hostname"),
566 user = self.get("username"),
567 port = self.get("port"),
568 gwuser = self.get("gatewayUser"),
569 gw = self.get("gateway"),
571 identity = self.get("identity"),
572 server_key = self.get("serverKey"),
576 return (out, err), proc
578 def getpid(self, home, pidfile = "pidfile"):
580 pidtuple = execfuncs.lgetpid(os.path.join(home, pidfile))
582 with self._node_lock:
583 pidtuple = sshfuncs.rgetpid(
584 os.path.join(home, pidfile),
585 host = self.get("hostname"),
586 user = self.get("username"),
587 port = self.get("port"),
588 gwuser = self.get("gatewayUser"),
589 gw = self.get("gateway"),
591 identity = self.get("identity"),
592 server_key = self.get("serverKey")
597 def status(self, pid, ppid):
599 status = execfuncs.lstatus(pid, ppid)
601 with self._node_lock:
602 status = sshfuncs.rstatus(
604 host = self.get("hostname"),
605 user = self.get("username"),
606 port = self.get("port"),
607 gwuser = self.get("gatewayUser"),
608 gw = self.get("gateway"),
610 identity = self.get("identity"),
611 server_key = self.get("serverKey")
616 def kill(self, pid, ppid, sudo = False):
619 status = self.status(pid, ppid)
621 if status == sshfuncs.ProcStatus.RUNNING:
623 (out, err), proc = execfuncs.lkill(pid, ppid, sudo)
625 with self._node_lock:
626 (out, err), proc = sshfuncs.rkill(
628 host = self.get("hostname"),
629 user = self.get("username"),
630 port = self.get("port"),
631 gwuser = self.get("gatewayUser"),
632 gw = self.get("gateway"),
635 identity = self.get("identity"),
636 server_key = self.get("serverKey")
639 return (out, err), proc
641 def copy(self, src, dst):
643 (out, err), proc = execfuncs.lcopy(src, dst,
646 with self._node_lock:
647 (out, err), proc = sshfuncs.rcopy(
649 port = self.get("port"),
650 gwuser = self.get("gatewayUser"),
651 gw = self.get("gateway"),
652 identity = self.get("identity"),
653 server_key = self.get("serverKey"),
655 strict_host_checking = False)
657 return (out, err), proc
659 def upload(self, src, dst, text = False, overwrite = True,
660 raise_on_error = True):
661 """ Copy content to destination
663 src string with the content to copy. Can be:
665 - a string with the path to a local file
666 - a string with a semi-colon separeted list of local files
667 - a string with a local directory
669 dst string with destination path on the remote host (remote is
672 text src is text input, it must be stored into a temp file before
675 # If source is a string input
677 if text and not os.path.isfile(src):
678 # src is text input that should be uploaded as file
679 # create a temporal file with the content to upload
680 f = tempfile.NamedTemporaryFile(delete=False)
685 # If dst files should not be overwritten, check that the files do not
687 if isinstance(src, str):
688 src = map(str.strip, src.split(";"))
690 if overwrite == False:
691 src = self.filter_existing_files(src, dst)
693 return ("", ""), None
695 if not self.localhost:
696 # Build destination as <user>@<server>:<path>
697 dst = "%s@%s:%s" % (self.get("username"), self.get("hostname"), dst)
699 ((out, err), proc) = self.copy(src, dst)
706 msg = " Failed to upload files - src: %s dst: %s" % (";".join(src), dst)
707 self.error(msg, out, err)
709 msg = "%s out: %s err: %s" % (msg, out, err)
711 raise RuntimeError, msg
713 return ((out, err), proc)
715 def download(self, src, dst, raise_on_error = True):
716 if not self.localhost:
717 # Build destination as <user>@<server>:<path>
718 src = "%s@%s:%s" % (self.get("username"), self.get("hostname"), src)
720 ((out, err), proc) = self.copy(src, dst)
723 msg = " Failed to download files - src: %s dst: %s" % (";".join(src), dst)
724 self.error(msg, out, err)
727 raise RuntimeError, msg
729 return ((out, err), proc)
731 def install_packages_command(self, packages):
734 command = rpmfuncs.install_packages_command(self.os, packages)
736 command = debfuncs.install_packages_command(self.os, packages)
738 msg = "Error installing packages ( OS not known ) "
739 self.error(msg, self.os)
740 raise RuntimeError, msg
744 def install_packages(self, packages, home, run_home = None,
745 raise_on_error = True):
746 """ Install packages in the Linux host.
748 'home' is the directory to upload the package installation script.
749 'run_home' is the directory from where to execute the script.
751 command = self.install_packages_command(packages)
753 run_home = run_home or home
755 (out, err), proc = self.run_and_wait(command, run_home,
756 shfile = os.path.join(home, "instpkg.sh"),
757 pidfile = "instpkg_pidfile",
758 ecodefile = "instpkg_exitcode",
759 stdout = "instpkg_stdout",
760 stderr = "instpkg_stderr",
762 raise_on_error = raise_on_error)
764 return (out, err), proc
766 def remove_packages(self, packages, home, run_home = None,
767 raise_on_error = True):
768 """ Uninstall packages from the Linux host.
770 'home' is the directory to upload the package un-installation script.
771 'run_home' is the directory from where to execute the script.
774 command = rpmfuncs.remove_packages_command(self.os, packages)
776 command = debfuncs.remove_packages_command(self.os, packages)
778 msg = "Error removing packages ( OS not known ) "
780 raise RuntimeError, msg
782 run_home = run_home or home
784 (out, err), proc = self.run_and_wait(command, run_home,
785 shfile = os.path.join(home, "rmpkg.sh"),
786 pidfile = "rmpkg_pidfile",
787 ecodefile = "rmpkg_exitcode",
788 stdout = "rmpkg_stdout",
789 stderr = "rmpkg_stderr",
791 raise_on_error = raise_on_error)
793 return (out, err), proc
795 def mkdir(self, paths, clean = False):
796 """ Paths is either a single remote directory path to create,
797 or a list of directories to create.
802 if isinstance(paths, str):
805 cmd = " ; ".join(map(lambda path: "mkdir -p %s" % path, paths))
807 return self.execute(cmd, with_lock = True)
809 def rmdir(self, paths):
810 """ Paths is either a single remote directory path to delete,
811 or a list of directories to delete.
814 if isinstance(paths, str):
817 cmd = " ; ".join(map(lambda path: "rm -rf %s" % path, paths))
819 return self.execute(cmd, with_lock = True)
821 def run_and_wait(self, command, home,
826 ecodefile = "exitcode",
832 raise_on_error = True):
834 Uploads the 'command' to a bash script in the host.
835 Then runs the script detached in background in the host, and
836 busy-waites until the script finishes executing.
839 if not shfile.startswith("/"):
840 shfile = os.path.join(home, shfile)
842 self.upload_command(command,
844 ecodefile = ecodefile,
846 overwrite = overwrite)
848 command = "bash %s" % shfile
849 # run command in background in remote host
850 (out, err), proc = self.run(command, home,
858 # check no errors occurred
860 msg = " Failed to run command '%s' " % command
861 self.error(msg, out, err)
863 raise RuntimeError, msg
865 # Wait for pid file to be generated
866 pid, ppid = self.wait_pid(
869 raise_on_error = raise_on_error)
871 # wait until command finishes to execute
872 self.wait_run(pid, ppid)
874 (eout, err), proc = self.check_errors(home,
875 ecodefile = ecodefile,
878 # Out is what was written in the stderr file
880 msg = " Failed to run command '%s' " % command
881 self.error(msg, eout, err)
884 raise RuntimeError, msg
886 (out, oerr), proc = self.check_output(home, stdout)
888 return (out, err), proc
890 def exitcode(self, home, ecodefile = "exitcode"):
892 Get the exit code of an application.
893 Returns an integer value with the exit code
895 (out, err), proc = self.check_output(home, ecodefile)
897 # Succeeded to open file, return exit code in the file
900 return int(out.strip())
902 # Error in the content of the file!
903 return ExitCode.CORRUPTFILE
905 # No such file or directory
906 if proc.returncode == 1:
907 return ExitCode.FILENOTFOUND
909 # Other error from 'cat'
910 return ExitCode.ERROR
912 def upload_command(self, command,
914 ecodefile = "exitcode",
917 """ Saves the command as a bash script file in the remote host, and
918 forces to save the exit code of the command execution to the ecodefile
921 if not (command.strip().endswith(";") or command.strip().endswith("&")):
924 # The exit code of the command will be stored in ecodefile
925 command = " { %(command)s } ; echo $? > %(ecodefile)s ;" % {
927 'ecodefile': ecodefile,
931 environ = self.format_environment(env)
933 # Add environ to command
934 command = environ + command
936 return self.upload(command, shfile, text = True, overwrite = overwrite)
938 def format_environment(self, env, inline = False):
939 """ Formats the environment variables for a command to be executed
940 either as an inline command
941 (i.e. export PYTHONPATH=src/..; export LALAL= ..;python script.py) or
942 as a bash script (i.e. export PYTHONPATH=src/.. \n export LALA=.. \n)
944 if not env: return ""
946 # Remove extra white spaces
947 env = re.sub(r'\s+', ' ', env.strip())
949 sep = ";" if inline else "\n"
950 return sep.join(map(lambda e: " export %s" % e, env.split(" "))) + sep
952 def check_errors(self, home,
953 ecodefile = "exitcode",
955 """ Checks whether errors occurred while running a command.
956 It first checks the exit code for the command, and only if the
957 exit code is an error one it returns the error output.
963 # get exit code saved in the 'exitcode' file
964 ecode = self.exitcode(home, ecodefile)
966 if ecode in [ ExitCode.CORRUPTFILE, ExitCode.ERROR ]:
967 err = "Error retrieving exit code status from file %s/%s" % (home, ecodefile)
968 elif ecode > 0 or ecode == ExitCode.FILENOTFOUND:
969 # The process returned an error code or didn't exist.
970 # Check standard error.
971 (err, eerr), proc = self.check_output(home, stderr)
973 # If the stderr file was not found, assume nothing bad happened,
974 # and just ignore the error.
975 # (cat returns 1 for error "No such file or directory")
976 if ecode == ExitCode.FILENOTFOUND and proc.poll() == 1:
979 return ("", err), proc
981 def wait_pid(self, home, pidfile = "pidfile", raise_on_error = False):
982 """ Waits until the pid file for the command is generated,
983 and returns the pid and ppid of the process """
988 pidtuple = self.getpid(home = home, pidfile = pidfile)
997 msg = " Failed to get pid for pidfile %s/%s " % (
1002 raise RuntimeError, msg
1006 def wait_run(self, pid, ppid, trial = 0):
1007 """ wait for a remote process to finish execution """
1011 status = self.status(pid, ppid)
1013 if status is ProcStatus.FINISHED:
1015 elif status is not ProcStatus.RUNNING:
1018 # If it takes more than 20 seconds to start, then
1019 # asume something went wrong
1023 # The app is running, just wait...
1026 def check_output(self, home, filename):
1027 """ Retrives content of file """
1028 (out, err), proc = self.execute("cat %s" %
1029 os.path.join(home, filename), retry = 1, with_lock = True)
1030 return (out, err), proc
1033 """ Checks if host is responsive
1039 msg = "Unresponsive host. Wrong answer. "
1041 # The underlying SSH layer will sometimes return an empty
1042 # output (even if the command was executed without errors).
1043 # To work arround this, repeat the operation N times or
1044 # until the result is not empty string
1046 (out, err), proc = self.execute("echo 'ALIVE'",
1050 if out.find("ALIVE") > -1:
1053 trace = traceback.format_exc()
1054 msg = "Unresponsive host. Error reaching host: %s " % trace
1056 self.error(msg, out, err)
1059 def find_home(self):
1060 """ Retrieves host home directory
1062 # The underlying SSH layer will sometimes return an empty
1063 # output (even if the command was executed without errors).
1064 # To work arround this, repeat the operation N times or
1065 # until the result is not empty string
1066 msg = "Impossible to retrieve HOME directory"
1068 (out, err), proc = self.execute("echo ${HOME}",
1072 if out.strip() != "":
1073 self._home_dir = out.strip()
1075 trace = traceback.format_exc()
1076 msg = "Impossible to retrieve HOME directory %s" % trace
1078 if not self._home_dir:
1080 raise RuntimeError, msg
1082 def filter_existing_files(self, src, dst):
1083 """ Removes files that already exist in the Linux host from src list
1085 # construct a dictionary with { dst: src }
1086 dests = dict(map(lambda s: (os.path.join(dst, os.path.basename(s)), s), src)) \
1087 if len(src) > 1 else dict({dst: src[0]})
1090 for d in dests.keys():
1091 command.append(" [ -f %(dst)s ] && echo '%(dst)s' " % {'dst' : d} )
1093 command = ";".join(command)
1095 (out, err), proc = self.execute(command, retry = 1, with_lock = True)
1097 for d in dests.keys():
1098 if out.find(d) > -1:
1104 return dests.values()