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