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