Salome HOME
Merge tag 'V8_5_0' into omu/Launcher9
[modules/kernel.git] / bin / runSession.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2017  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, getShortAndExtraArgs
32 from salome_utils import getUserName, getShortHostName
33
34 # Use to display newlines (\n) in epilog
35 class MyParser(OptionParser):
36     def format_epilog(self, formatter):
37         return self.epilog
38 #
39
40 class SessionParameters:
41   def __init__(self, mode, port, machine, user, directory):
42     self.mode = mode
43     self.port = port
44     self.machine = machine
45     self.user = user
46     self.directory = directory
47   #
48   def __repr__(self):
49     msg = "\n# Session Parameters:\n"
50     msg += "     * mode: %s\n"%self.mode
51     msg += "     * port: %s\n"%self.port
52     msg += "     * machine: %s\n"%self.machine
53     msg += "     * user: %s\n"%self.user
54     msg += "     * directory: %s\n"%self.directory
55     return msg
56   #
57 #
58
59 def configureSession(args=None, exe=None):
60   if args is None:
61     args = []
62   if exe:
63       usage = "Usage: %s [options] [command] [-- <extra>]"%exe
64   else:
65       usage = "Usage: %prog [options] [command] [-- <extra>]"
66   epilog  = """\n
67 If command is not given a shell is opened; else execute the given command.\n
68 * Command may be an executable script or program, either identified by its
69   full path or located in a directory pointed by a system variable (e.g.
70   PATH).\n
71 * Command may also be a series of Python scripts with arguments:
72   [PYTHON_FILE [args] [PYTHON_FILE [args]...]]
73 Python file arguments, if any, must be comma-separated (without blank
74   characters) and prefixed by "args:" keyword (without quotes).
75 For example:
76        salome shell hello.py add.py args:1,2 hello.py args:you
77 will successively say hello, add 1+2, and say hello to you.\n
78 The double dash (--) syntax indicates an extra command to be run "as is". It
79   allows calling a extern program or system command with options and
80   arguments, using the syntax: -- <program> [options] [arguments].
81 For example:
82        salome shell -- ls -l *.py
83        salome shell -- python -tt hello.py
84 \n
85 If PORT and MACHINE are not given, try to connect to the last active session
86   on the local machine.
87 If PORT and MACHINE are given, try to connect to the remote session associated
88   with PORT on MACHINE.
89 If MACHINE is not given, try to connect to the session associated to PORT on
90   the local machine.
91 If PORT is not given, try to connect to the remote session associated to port
92   2810 on MACHINE.\n
93 If MACHINE is remote, the following options MUST be provided:
94      * DIRECTORY: The full path to the salome command on remote machine.
95      * USER: The user on the computer to connect to.\n
96 In case of remote call, syntax "out:res1,res2,..." can be used to get results
97   from remote machine.
98 For example:
99        salome shell -m remotemachine -p 2810 -u myself -d /full/path/to/salome
100   concatenate.py args:file1.txt,file2.txt out:result.txt
101 User "myself" connects to remotemachine to run the script concatenate.py in
102   a SALOME session on port 2810; the script takes two input parameters and
103   produces one result file.\n
104 """
105   parser = MyParser(usage=usage, epilog=epilog)
106   parser.add_option("-p", "--port", metavar="<port>", default=0,
107                     action="store", type="string", dest="port",
108                     help="The port to connect to."
109                     )
110   parser.add_option("-m", "--machine", metavar="<machine>", default=0,
111                     action="store", type="string", dest="host",
112                     help="The machine to connect to."
113                     )
114   parser.add_option('-d', '--directory', dest="directory", default=None,
115                     help="[Remote mode] The full path to the salome command on remote machine."
116                     )
117   parser.add_option('-u', '--user', dest="user", default=None,
118                     help="[Remote mode] The user on the computer to connect to."
119                     )
120
121   short_args, extra_args = getShortAndExtraArgs(args)
122   try:
123     (options, args) = parser.parse_args(short_args)
124   except Exception, e:
125     print e
126     return None, []
127
128   port = options.port
129   host = options.host
130
131   # :GLITCH: this code defines specific environment variables (OMNIORB_CONFIG, NSPORT,
132   # NSHOST) which are later used by other modules. Working, but not really "safe"...
133   if not port:
134     if not host:
135       # neither MACHINE nor PORT are given
136       # --- set omniORB configuration to current session if any
137       omniorbUserPath = os.environ['OMNIORB_USER_PATH']
138       fileOmniConfig = omniorbUserPath + '/.omniORB_' + getUserName() + '_last.cfg'
139       if os.path.isfile(fileOmniConfig):
140         os.environ['OMNIORB_CONFIG'] = fileOmniConfig
141         # --- set environment variables for port and hostname of NamingService
142         host, port = getNSparams()
143       else:
144         # No running session
145         host = "no_host"
146         port = "no_port"
147     else:
148       # only MACHINE is given
149       port = '2810'
150       _writeConfigFile(port, host)
151     #
152   else:
153     if not host:
154       # only PORT is given
155       host = getShortHostName()
156     # both MACHINE and PORT are given
157     _writeConfigFile(port, host)
158   #
159   os.environ['NSPORT'] = port
160   os.environ['NSHOST'] = host
161
162   # determine running mode, that is either 'local' or 'remote'
163   here = getShortHostName()
164   mode = "local"
165   if host != here and host != "localhost" and host != "no_host":
166     mode="remote"
167     pass
168   params = SessionParameters(mode, port, host, options.user, options.directory)
169   return params, args+extra_args
170 #
171
172 # --- set the OMNIORB_CONFIG file and environment relative to this run of SALOME
173 def _writeConfigFile(port, host):
174   path = os.environ['OMNIORB_USER_PATH']
175   kwargs = {'with_username' : getUserName()}
176
177   from ORBConfigFile import writeORBConfigFile
178   [ filename, msgSize ] = writeORBConfigFile(path, host, port, kwargs)
179
180   os.environ['OMNIORB_CONFIG'] = filename
181 #
182
183 # command looks like a Bash command-line:
184 # script1.py [args] ; script2.py [args] ; ...
185 def __runLocalSession(command):
186   if command:
187     sep = ";"
188     if sys.platform == "win32":
189       sep= "&"
190     command = command.split(sep)
191     outmsg = []
192     errmsg = []
193     for cmd in command:
194       single_cmd = cmd.strip().split(' ')
195       any_error = False
196       error_code = 1
197       try:
198         proc = subprocess.Popen(single_cmd)
199         (stdoutdata, stderrdata) = proc.communicate() # Wait for process to terminate
200         if stdoutdata:
201           outmsg.append(stdoutdata)
202         if stderrdata:
203           errmsg.append(stderrdata)
204
205         if proc.returncode != 0:
206           any_error = True
207           error_code = proc.returncode
208       except:
209           any_error = True
210           pass
211
212       if any_error:
213         errmsg.append("Error raised when executing command: %s\n"%cmd)
214         if outmsg:
215           sys.stdout.write("".join(outmsg))
216         if errmsg:
217           sys.stderr.write("".join(errmsg))
218         sys.exit(error_code)
219
220     return 0
221   else:
222     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
223     if sys.platform == "win32":
224       cmd = ["cmd", "/K", "set PROMPT=[SALOME] $P$G"]
225     else:
226       cmd = ["/bin/bash",  "--rcfile", absoluteAppliPath + "/.bashrc" ]
227     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
228     proc.communicate()
229     return proc.returncode
230 #
231
232 def __copyFiles(user, machine, script, infiles, outfiles):
233   """Modify script, copy files to remote computer and return lists of copied files."""
234
235   namescript = os.path.basename(script)
236   import getpass
237   logname = getpass.getuser()
238   tmp_script = "/tmp/%s_%s_%s" % (logname, os.getpid(), namescript)
239   with open(script, 'r') as fscript:
240     script_text = fscript.read()
241
242   list_infiles = []
243   list_outfiles = []
244   n = 0
245   for infile in infiles:
246     # generate a temporary file name
247     namefile = os.path.basename(infile)
248     tmp_file = "/tmp/%s_%s_i%s_%s" % (logname, os.getpid(), n, namefile)
249
250     # modify the salome script
251     script_text = re.sub(infile, tmp_file, script_text)
252
253     # copy the infile to the remote server
254     cmd = "scp %s %s@%s:%s" % (infile, user, machine, tmp_file)
255     print "[  SCP  ]", cmd
256     os.system(cmd)
257
258     list_infiles.append(tmp_file)
259     n = n + 1
260   #
261   n = 0
262   for outfile in outfiles:
263     # generate a temporary file name
264     namefile = os.path.basename(outfile)
265     tmp_file = "/tmp/%s_%s_o%s_%s" % (logname, os.getpid(), n, namefile)
266
267     # modify the salome script
268     script_text = re.sub(outfile, tmp_file, script_text)
269
270     list_outfiles.append(tmp_file)
271     n = n + 1
272   #
273
274   with open(tmp_script,'w') as fscript:
275     fscript.write(script_text)
276
277   # copy the salome script on the remote server
278   cmd = "scp %s %s@%s:%s" % (tmp_script, user, machine, tmp_script)
279   print "[  SCP  ]", cmd
280   os.system(cmd)
281
282   return list_infiles, list_outfiles, tmp_script
283 #
284
285 # sa_obj is a ScriptAndArgs object (from salomeContextUtils)
286 def __runRemoteSession(sa_obj, params):
287   if not params.user:
288     print "ERROR: The user login on remote machine MUST be given."
289     return 1
290   if not params.directory:
291     print "ERROR: The remote directory MUST be given."
292     return 1
293
294   # sa_obj.script may be 'python script.py' --> only process .py file
295   header = " ".join(sa_obj.script.split()[:-1])
296   script = sa_obj.script.split()[-1]
297
298   tmp_in, tmp_out, tmp_script = __copyFiles(params.user, params.machine, script, sa_obj.args or [], sa_obj.out or [])
299
300   # execute command on the remote SALOME application
301   command = "ssh %s@%s %s/salome shell " % (params.user, params.machine, params.directory)
302   if params.port:
303     command = command + "-p %s "%params.port
304   command = command + " %s %s args:%s"%(header, tmp_script, ",".join(tmp_in))
305   print '[  SSH   ] ' + command
306   os.system(command)
307
308   # Get remote files and clean
309   temp_files = tmp_in + tmp_out + [tmp_script]
310
311   # get the outfiles
312   for outfile in (sa_obj.out or []):
313     remote_outfile = tmp_out.pop(0)
314     command = "scp %s@%s:%s %s" %(params.user, params.machine, remote_outfile, outfile)
315     print "[  SCP  ] " + command
316     os.system(command)
317
318   # clean temporary files
319   command = "ssh %s@%s \\rm -f %s" % (params.user, params.machine, " ".join(temp_files))
320   print '[  SSH   ] ' + command
321   os.system(command)
322   os.remove(tmp_script)
323
324   return 0
325 #
326
327 def runSession(params, args):
328   scriptArgs = getScriptsAndArgs(args)
329
330   if params.mode == "local":
331     command = formatScriptsAndArgs(scriptArgs)
332     return __runLocalSession(command)
333
334   elif params.mode == "remote":
335     any_error = 0
336     for sa_obj in scriptArgs:
337       ok = __runRemoteSession(sa_obj, params)
338       if not ok:
339         any_error = 1
340     return any_error
341 #