LocalSubstrate.py as a managing tool
[tests.git] / system / TestSsh.py
1 # Thierry Parmentelat <thierry.parmentelat@inria.fr>
2 # Copyright (C) 2010 INRIA 
3 #
4 # class for issuing commands on a box, either local or remote
5 #
6 # the notion of 'buildname' is for providing each test run with a dir of its own
7 # buildname is generally the name of the build being tested, and can be considered unique
8 #
9 # thus 'run_in_buildname' mostly :
10 # (*) either runs locally in . - as on a local node we are already in a dedicated directory
11 # (*) or makes sure that there's a remote dir called 'buildname' and runs in it
12 #
13 # also, the copy operations
14 # (*) either do nothing if ran locally
15 # (*) or copy a local file into the remote 'buildname' 
16
17
18 import os.path
19 import utils
20 import shutil
21
22 class TestSsh:
23     
24     # inserts a backslash before each occurence of the following chars
25     # \ " ' < > & | ; ( ) $ * ~ 
26     @staticmethod
27     def backslash_shell_specials (command):
28         result=''
29         for char in command:
30             if char in "\\\"'<>&|;()$*~":
31                 result +='\\'+char
32             else:
33                 result +=char
34         return result
35
36     # check main IP address against the provided hostname
37     @staticmethod
38     def is_local_hostname (hostname):
39         if hostname == "localhost":
40             return True
41         import socket
42         try:
43             local_ip = socket.gethostbyname(socket.gethostname())
44             remote_ip = socket.gethostbyname(hostname)
45             return local_ip==remote_ip
46         except:
47             utils.header("WARNING : something wrong in is_local_hostname with hostname=%s"%hostname)
48             return False
49
50     def __init__(self,hostname,buildname=None,key=None, username=None,unknown_host=True):
51         self.hostname=hostname
52         self.buildname=buildname
53         self.key=key
54         self.username=username
55         self.unknown_host=unknown_host
56
57     def is_local(self):
58         return TestSsh.is_local_hostname(self.hostname)
59      
60     std_options="-o BatchMode=yes -o StrictHostKeyChecking=no -o CheckHostIP=no -o ConnectTimeout=5 "
61     unknown_option="-o UserKnownHostsFile=/dev/null "
62     
63     def key_part (self):
64         if not self.key:
65             return ""
66         return "-i %s "%self.key
67
68     def hostname_part (self):
69         if not self.username:
70             return self.hostname
71         else:
72             return "%s@%s"%(self.username,self.hostname)
73     
74     # command gets run on the right box
75     def actual_command (self, command,keep_stdin=False):
76         if self.is_local():
77             return command
78         ssh_command = "ssh "
79         if not keep_stdin:
80             ssh_command += "-n "
81         ssh_command += TestSsh.std_options
82         if self.unknown_host: ssh_command += TestSsh.unknown_option
83         ssh_command += self.key_part()
84         ssh_command += "%s %s" %(self.hostname_part(),TestSsh.backslash_shell_specials(command))
85         return ssh_command
86
87     # same in argv form
88     def actual_argv (self, argv,keep_stdin=False):
89         if self.is_local():
90             return argv
91         ssh_argv=[]
92         ssh_argv.append('ssh')
93         if not keep_stdin: ssh_argv.append('-n')
94         ssh_argv += TestSsh.std_options.split()
95         if self.unknown_host: ssh_argv += TestSsh.unknown_option.split()
96         ssh_argv += self.key_part().split()
97         ssh_argv.append(self.hostname_part())
98         ssh_argv += argv
99         return ssh_argv
100
101     def header (self,message):
102         if not message: return
103         print "===============",message
104         sys.stdout.flush()
105
106     def run(self, command,message=None,background=False,dry_run=False):
107         local_command = self.actual_command(command)
108         if dry_run:
109             utils.header("DRY RUN " + local_command)
110             return 0
111         else:
112             self.header(message)
113             return utils.system(local_command,background)
114
115     def clean_dir (self,dirname):
116         if self.is_local():
117             return 0
118         return self.run("rm -rf %s"%dirname)
119
120     def mkdir (self,dirname=None):
121         if self.is_local():
122             if dirname:
123                 return os.path.mkdir(dirname)
124             return 0
125         if dirname:
126             dirname="%s/%s"%(self.buildname,dirname)
127         else:
128             dirname=self.buildname
129         return self.run("mkdir -p %s"%dirname)
130
131     def rmdir (self,dirname=None):
132         if self.is_local():
133             if dirname:
134                 return shutil.rmtree(dirname)
135             return 0
136         if dirname:
137             dirname="%s/%s"%(self.buildname,dirname)
138         else:
139             dirname=self.buildname
140         return self.run("rm -rf %s"%dirname)
141
142     def create_buildname_once (self):
143         if self.is_local():
144             return
145         # create remote buildname on demand
146         try:
147             self.buildname_created
148         except:
149             self.mkdir()
150             self.buildname_created=True
151
152     def run_in_buildname (self,command, background=False):
153         if self.is_local():
154             return utils.system(command,background)
155         self.create_buildname_once()
156         return self.run("cd %s ; %s"%(self.buildname,command),background)
157
158     def copy (self,local_file,recursive=False):
159         if self.is_local():
160             return 0
161         self.create_buildname_once()
162         scp_command="scp "
163         scp_command += TestSsh.std_options
164         if recursive: scp_command += "-r "
165         scp_command += self.key_part()
166         scp_command += "%s %s:%s/%s"%(local_file,self.hostname_part(),
167                                       self.buildname,os.path.basename(local_file) or ".")
168         return utils.system(scp_command)
169
170     def copy_abs (self,local_file,remote_file,recursive=False):
171         if self.is_local():
172             dest=""
173         else:
174             dest= "%s:"%self.hostname_part()
175         scp_command="scp "
176         scp_command += TestSsh.std_options
177         if recursive: scp_command += "-r "
178         scp_command += self.key_part()
179         scp_command += "%s %s%s"%(local_file,dest,remote_file)
180         return utils.system(scp_command)
181
182     def copy_home (self, local_file, recursive=False):
183         return self.copy_abs(local_file,os.path.basename(local_file),recursive)
184
185     def fetch (self, remote_file, local_file, recursive=False):
186         if self.is_local():
187             command="cp "
188             if recursive: command += "-r "
189             command += "%s %s"%(remote_file,local_file)
190         else:
191             command="scp "
192             command += TestSsh.std_options
193             if recursive: command += "-r "
194             command += self.key_part()
195             # absolute path - do not preprend buildname
196             if remote_file.find("/")==0:
197                 remote_path=remote_file
198             else:
199                 remote_path="%s/%s"%(self.buildname,remote_file)
200             command += "%s:%s %s"%(self.hostname_part(),remote_path,local_file)
201         return utils.system(command)
202
203     # this is only to avoid harmless message when host cannot be identified
204     # convenience only
205     # the only place where this is needed is when tring to reach a slice in a node,
206     # which is done from the test master box
207     def clear_known_hosts (self):
208         known_hosts = "%s/.ssh/known_hosts"%os.getenv("HOME")
209         utils.header("Clearing entry for %s in %s"%(self.hostname,known_hosts))
210         return utils.system("sed -i -e /^%s/d %s"%(self.hostname,known_hosts))
211