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