Salome HOME
run scripts on remote machines (substitute for runSalomeScript)
[modules/kernel.git] / bin / runSession.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2014  CEA/DEN, EDF R&D, OPEN CASCADE
3 #
4 # Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
5 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
6 #
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
11 #
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 # Lesser General Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this library; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
20 #
21 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 #
23
24 import os
25 import sys
26 from optparse import OptionParser
27 from NSparam import getNSparams
28 import socket
29 import subprocess
30 import re
31 from salomeContextUtils import getScriptsAndArgs, formatScriptsAndArgs
32
33 # Use to display newlines (\n) in epilog
34 class MyParser(OptionParser):
35     def format_epilog(self, formatter):
36         return self.epilog
37 #
38
39 class SessionParameters:
40   def __init__(self, mode, port, machine, user, directory):
41     self.mode = mode
42     self.port = port
43     self.machine = machine
44     self.user = user
45     self.directory = directory
46 #
47
48 def configureSession(args=None):
49   if args is None:
50     args = []
51   usage = "Usage: %prog [options] [command]"
52   epilog  = """\n
53 If the command is not given a shell is opened; else execute the given command.
54 Command may be a series of Python scripts with arguments: [PYTHON_FILE [args] [PYTHON_FILE [args]...]]
55 Python file arguments, if any, must be comma-separated (without blank characters) and prefixed by "args:" (without quotes), e.g. myscript.py args:arg1,arg2=val,...
56 \n
57 If PORT and MACHINE are not given, try to connect to the last active session on the local machine.
58 If PORT and MACHINE are given, try to connect to the remote session associated with PORT on MACHINE.
59 If MACHINE is not given, try to connect to the session associated to PORT on the local machine.
60 If PORT is not given, try to connect to the remote session associated to port 2810 on MACHINE.
61 \n
62 If MACHINE is remote, the following options MUST be provided:
63      * DIRECTORY: The full path to the salome command on remote machine.
64      * USER: The user on the computer to connect to.
65 """
66   parser = MyParser(usage=usage, epilog=epilog)
67   parser.add_option("-p", "--port", metavar="<port>", default=0,
68                     action="store", type="string", dest="port",
69                     help="The port to connect to."
70                     )
71   parser.add_option("-m", "--machine", metavar="<machine>", default=0,
72                     action="store", type="string", dest="host",
73                     help="The machine to connect to."
74                     )
75   parser.add_option('-d', '--directory', dest="directory", default=None,
76                     help="[Remote mode] The full path to the salome command on remote machine."
77                     )
78   parser.add_option('-u', '--user', dest="user", default=None,
79                     help="[Remote mode] The user on the computer to connect to."
80                     )
81   try:
82     (options, args) = parser.parse_args(args)
83   except Exception, e:
84     print e
85     return
86
87   port = options.port
88   host = options.host
89
90   # :GLITCH: this code defines specific environment variables (OMNIORB_CONFIG, NSPORT,
91   # NSHOST) which are later used by other modules. Working, but not really "safe"...
92   if not port:
93     if not host:
94       # neither MACHINE nor PORT are given
95       # --- set omniORB configuration to current session if any
96       omniorbUserPath = os.environ['OMNIORB_USER_PATH']
97       fileOmniConfig = omniorbUserPath + '/.omniORB_' + os.environ['USER'] + '_last.cfg'
98       if os.path.isfile(fileOmniConfig):
99         os.environ['OMNIORB_CONFIG'] = fileOmniConfig
100         # --- set environment variables for port and hostname of NamingService
101         host, port = getNSparams()
102       else:
103         # No running session
104         host = "no_host"
105         port = "no_port"
106     else:
107       # only MACHINE is given
108       port = '2810'
109       _writeConfigFile(port, host)
110     #
111   else:
112     if not host:
113       # only PORT is given
114       host = socket.gethostname()
115     # both MACHINE and PORT are given
116     _writeConfigFile(port, host)
117   #
118   os.environ['NSPORT'] = port
119   os.environ['NSHOST'] = host
120
121   # determine running mode, taht is either 'local' or 'remote'
122   from salomeContextUtils import getHostname
123   here = getHostname()
124   mode = "local"
125   if host != here and host != "localhost" and host != "no_host":
126     mode="remote"
127     pass
128   params = SessionParameters(mode, port, host, options.user, options.directory)
129   return params, args
130 #
131
132 # --- set the OMNIORB_CONFIG file and environment relative to this run of SALOME
133 def _writeConfigFile(port, host):
134   path = os.environ['OMNIORB_USER_PATH']
135   kwargs = {'with_username' : os.environ['USER']}
136
137   from ORBConfigFile import writeORBConfigFile
138   [ filename, msgSize ] = writeORBConfigFile(path, host, port, kwargs)
139
140   os.environ['OMNIORB_CONFIG'] = filename
141 #
142
143 # command looks like a Bash command-line:
144 # script1.py [args] ; script2.py [args] ; ...
145 def __runLocalSession(command):
146   if command:
147     sep = ";"
148     if sys.platform == "win32":
149       sep= "&"
150     command = command.split(sep)
151     outmsg = []
152     errmsg = []
153     for cmd in command:
154       single_cmd = cmd.strip().split(' ')
155       proc = subprocess.Popen(single_cmd)
156       (stdoutdata, stderrdata) = proc.communicate() # Wait for process to terminate
157       if stdoutdata:
158         outmsg.append(stdoutdata)
159       if stderrdata:
160         errmsg.append(stderrdata)
161
162       if proc.returncode != 0:
163         errmsg.append("Error raised when executing command: %s\n"%cmd)
164         if outmsg:
165           sys.stdout.write("".join(outmsg))
166         if errmsg:
167           sys.stderr.write("".join(errmsg))
168         sys.exit(proc.returncode)
169
170     return ("".join(outmsg), "".join(errmsg))
171   else:
172     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
173     cmd = ["/bin/bash",  "--rcfile", absoluteAppliPath + "/.bashrc" ]
174     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
175     return proc.communicate()
176 #
177
178 def __copyFiles(user, machine, script, infiles, outfiles):
179   """Modify script, copy files to remote computer and return lists of copied files."""
180
181   namescript = os.path.basename(script)
182   import getpass
183   logname = getpass.getuser()
184   tmp_script = "/tmp/%s_%s_%s" % (logname, os.getpid(), namescript)
185   with open(script, 'r') as fscript:
186     script_text = fscript.read()
187
188   list_infiles = []
189   list_outfiles = []
190   n = 0
191   for infile in infiles:
192     # generate a temporary file name
193     namefile = os.path.basename(infile)
194     tmp_file = "/tmp/%s_%s_i%s_%s" % (logname, os.getpid(), n, namefile)
195
196     # modify the salome script
197     script_text = re.sub(infile, tmp_file, script_text)
198
199     # copy the infile to the remote server
200     cmd = "scp %s %s@%s:%s" % (infile, user, machine, tmp_file)
201     print "[  SCP  ]", cmd
202     os.system(cmd)
203
204     list_infiles.append(tmp_file)
205     n = n + 1
206   #
207   n = 0
208   for outfile in outfiles:
209     # generate a temporary file name
210     namefile = os.path.basename(outfile)
211     tmp_file = "/tmp/%s_%s_o%s_%s" % (logname, os.getpid(), n, namefile)
212
213     # modify the salome script
214     script_text = re.sub(outfile, tmp_file, script_text)
215
216     list_outfiles.append(tmp_file)
217     n = n + 1
218   #
219
220   with open(tmp_script,'w') as fscript:
221     fscript.write(script_text)
222
223   # copy the salome script on the remote server
224   cmd = "scp %s %s@%s:%s" % (tmp_script, user, machine, tmp_script)
225   print "[  SCP  ]", cmd
226   os.system(cmd)
227
228   return list_infiles, list_outfiles, tmp_script
229 #
230
231 # sa_obj is a ScriptAndArgs object (from salomeContextUtils)
232 def __runRemoteSession(sa_obj, params):
233   if not params.user:
234     print "ERROR: The user login on remote machine MUST be given."
235     return
236   if not params.directory:
237     print "ERROR: The remote directory MUST be given."
238     return
239
240   # sa_obj.script may be 'python script.py' --> only process .py file
241   header = " ".join(sa_obj.script.split()[:-1])
242   script = sa_obj.script.split()[-1]
243
244   tmp_in, tmp_out, tmp_script = __copyFiles(params.user, params.machine, script, sa_obj.args or [], sa_obj.out or [])
245
246   # execute command on the remote SALOME application
247   command = "ssh %s@%s %s/runSession " % (params.user, params.machine, params.directory)
248   if params.port:
249     command = command + "-p %s "%params.port
250   command = command + " ".join([header,tmp_script] + tmp_in)
251   print '[  SSH   ] ' + command
252   os.system(command)
253
254   # Get remote files and clean
255   temp_files = tmp_in + tmp_out + [tmp_script]
256
257   # get the outfiles
258   for outfile in (sa_obj.out or []):
259     remote_outfile = tmp_out.pop(0)
260     command = "scp %s@%s:%s %s" %(params.user, params.machine, remote_outfile, outfile)
261     print "[  SCP  ] " + command
262     os.system(command)
263
264   # clean temporary files
265   command = "ssh %s@%s \\rm -f %s" % (params.user, params.machine, " ".join(temp_files))
266   print '[  SSH   ] ' + command
267   os.system(command)
268   os.remove(tmp_script)
269
270 #
271
272 def runSession(params, args):
273   scriptArgs = getScriptsAndArgs(args)
274   command = formatScriptsAndArgs(scriptArgs)
275
276   if params.mode == "local":
277     command = formatScriptsAndArgs(scriptArgs)
278     return __runLocalSession(command)
279
280   elif params.mode == "remote":
281     for sa_obj in scriptArgs:
282       __runRemoteSession(sa_obj, params)
283 #