Salome HOME
updated copyright message
[modules/kernel.git] / bin / runSession.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2023  CEA, EDF, 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   parser.add_option('-l', '--launcher', dest="launcher", default=None,
122                     help="[Remote mode] The machine and the port to connect to."
123                     )
124
125   short_args, extra_args = getShortAndExtraArgs(args)
126   try:
127     (options, args) = parser.parse_args(short_args)
128   except Exception as e:
129     print(e)
130     return None, []
131
132   port = options.port
133   host = options.host
134   launcher = options.launcher
135   if launcher is not None:
136     pos = launcher.find(":")
137     if pos != -1:
138       host = launcher[0:pos]
139       port = launcher[pos+1:]
140
141   # :GLITCH: this code defines specific environment variables (OMNIORB_CONFIG, NSPORT,
142   # NSHOST) which are later used by other modules. Working, but not really "safe"...
143   if not port:
144     if not host:
145       # neither MACHINE nor PORT are given
146       # --- set omniORB configuration to current session if any
147       omniorbUserPath = os.environ['OMNIORB_USER_PATH']
148       fileOmniConfig = omniorbUserPath + '/.omniORB_' + getUserName() + '_last.cfg'
149       if os.path.isfile(fileOmniConfig):
150         os.environ['OMNIORB_CONFIG'] = fileOmniConfig
151         # --- set environment variables for port and hostname of NamingService
152         host, port = getNSparams()
153         try:
154             # keep short name for host, for a correct comparison with getShortHostName() later
155             host=host.split('.')[0]
156         except Exception:
157             pass
158       else:
159         # No running session
160         host = "no_host"
161         port = "no_port"
162     else:
163       # only MACHINE is given
164       port = '2810'
165       _writeConfigFile(port, host)
166     #
167   else:
168     if not host:
169       # only PORT is given
170       host = getShortHostName()
171     # both MACHINE and PORT are given
172     _writeConfigFile(port, host)
173   #
174   os.environ['NSPORT'] = port
175   os.environ['NSHOST'] = host
176
177   # determine running mode, that is either 'local' or 'remote'
178   here = getShortHostName()
179   mode = "local"
180   if host != here and host != "localhost" and host != "no_host" and launcher is None:
181     mode="remote"
182     pass
183   params = SessionParameters(mode, port, host, options.user, options.directory)
184   return params, args+extra_args
185 #
186
187 # --- set the OMNIORB_CONFIG file and environment relative to this run of SALOME
188 def _writeConfigFile(port, host):
189   path = os.environ['OMNIORB_USER_PATH']
190   kwargs = {'with_username' : getUserName()}
191
192   from ORBConfigFile import writeORBConfigFile
193   [ filename, msgSize ] = writeORBConfigFile(path, host, port, kwargs)
194
195   os.environ['OMNIORB_CONFIG'] = filename
196 #
197
198 # command looks like a Bash command-line:
199 # script1.py [args] ; script2.py [args] ; ...
200 def __runLocalSession(command):
201   if command:
202     sep = ";"
203     if sys.platform == "win32":
204       sep= "&"
205     command = command.split(sep)
206     outmsg = []
207     errmsg = []
208     for cmd in command:
209       single_cmd = cmd.strip().split(' ')
210       any_error = False
211       error_code = 1
212       try:
213         proc = subprocess.Popen(single_cmd)
214         (stdoutdata, stderrdata) = proc.communicate() # Wait for process to terminate
215         if stdoutdata:
216           outmsg.append(stdoutdata)
217         if stderrdata:
218           errmsg.append(stderrdata)
219
220         if proc.returncode != 0:
221           any_error = True
222           error_code = proc.returncode
223       except Exception:
224           any_error = True
225           pass
226
227       if any_error:
228         errmsg.append("Error raised when executing command: %s\n"%cmd)
229         if outmsg:
230           sys.stdout.write("".join(outmsg))
231         if errmsg:
232           sys.stderr.write("".join(errmsg))
233         sys.exit(error_code)
234
235     return 0
236   else:
237     absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
238     if sys.platform == "win32":
239       cmd = ["cmd", "/K", "set PROMPT=[SALOME] $P$G"]
240     else:
241       cmd = ["/bin/bash",  "--rcfile", absoluteAppliPath + "/.bashrc" ]
242     proc = subprocess.Popen(cmd, shell=False, close_fds=True)
243     proc.communicate()
244     return proc.returncode
245 #
246
247 def __copyFiles(user, machine, script, infiles, outfiles):
248   """Modify script, copy files to remote computer and return lists of copied files."""
249
250   namescript = os.path.basename(script)
251   import getpass
252   logname = getpass.getuser()
253   tmp_script = "/tmp/%s_%s_%s" % (logname, os.getpid(), namescript)
254   with open(script, 'r') as fscript:
255     script_text = fscript.read()
256
257   list_infiles = []
258   list_outfiles = []
259   n = 0
260   for infile in infiles:
261     # generate a temporary file name
262     namefile = os.path.basename(infile)
263     tmp_file = "/tmp/%s_%s_i%s_%s" % (logname, os.getpid(), n, namefile)
264
265     # modify the salome script
266     script_text = re.sub(infile, tmp_file, script_text)
267
268     # copy the infile to the remote server
269     cmd = "scp %s %s@%s:%s" % (infile, user, machine, tmp_file)
270     print("[  SCP  ]", cmd)
271     os.system(cmd)
272
273     list_infiles.append(tmp_file)
274     n = n + 1
275   #
276   n = 0
277   for outfile in outfiles:
278     # generate a temporary file name
279     namefile = os.path.basename(outfile)
280     tmp_file = "/tmp/%s_%s_o%s_%s" % (logname, os.getpid(), n, namefile)
281
282     # modify the salome script
283     script_text = re.sub(outfile, tmp_file, script_text)
284
285     list_outfiles.append(tmp_file)
286     n = n + 1
287   #
288
289   with open(tmp_script,'w') as fscript:
290     fscript.write(script_text)
291
292   # copy the salome script on the remote server
293   cmd = "scp %s %s@%s:%s" % (tmp_script, user, machine, tmp_script)
294   print("[  SCP  ]", cmd)
295   os.system(cmd)
296
297   return list_infiles, list_outfiles, tmp_script
298 #
299
300 # sa_obj is a ScriptAndArgs object (from salomeContextUtils)
301 def __runRemoteSession(sa_obj, params):
302   if not params.user:
303     print("ERROR: The user login on remote machine MUST be given.")
304     return 1
305   if not params.directory:
306     print("ERROR: The remote directory MUST be given.")
307     return 1
308
309   # sa_obj.script may be 'python script.py' --> only process .py file
310   header = " ".join(sa_obj.script.split()[:-1])
311   script = sa_obj.script.split()[-1]
312
313   tmp_in, tmp_out, tmp_script = __copyFiles(params.user, params.machine, script, sa_obj.args or [], sa_obj.out or [])
314
315   # execute command on the remote SALOME application
316   command = "%s/salome shell" % (params.directory)
317   if params.port:
318     command += " -p %s "%params.port
319   command += " %s %s args:%s"%(header, tmp_script, ",".join(tmp_in))
320   # salome shell command must run in a login shell because of module function
321   command = "ssh %s@%s -t 'bash -l -c \"%s\"'" % (params.user, params.machine, command)
322   print('[  SSH   ] ' + command)
323   os.system(command)
324
325   # Get remote files and clean
326   temp_files = tmp_in + tmp_out + [tmp_script]
327
328   # get the outfiles
329   for outfile in (sa_obj.out or []):
330     remote_outfile = tmp_out.pop(0)
331     command = "scp %s@%s:%s %s" %(params.user, params.machine, remote_outfile, outfile)
332     print("[  SCP  ] " + command)
333     os.system(command)
334
335   # clean temporary files
336   command = "ssh %s@%s \\rm -f %s" % (params.user, params.machine, " ".join(temp_files))
337   print('[  SSH   ] ' + command)
338   os.system(command)
339   os.remove(tmp_script)
340
341   return 0
342 #
343
344 def runSession(params, args):
345   scriptArgs = getScriptsAndArgs(args)
346
347   if params.mode == "local":
348     command = formatScriptsAndArgs(scriptArgs)
349     return __runLocalSession(command)
350
351   elif params.mode == "remote":
352     any_error = 0
353     for sa_obj in scriptArgs:
354       ok = __runRemoteSession(sa_obj, params)
355       if not ok:
356         any_error = 1
357     return any_error
358 #