Salome HOME
0955d9544683c52d5afb8d304f3181d7ae3ccbfc
[tools/sat.git] / commands / launcher.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import os
20 import platform
21 import shutil
22 import getpass
23 import subprocess
24 import stat
25
26 import src
27 import src.debug as DBG
28
29 parser = src.options.Options()
30
31 parser.add_option('n', 'name', 'string', 'name', _('Optional: The name of the'
32                             ' launcher (default is '
33                             'APPLICATION.profile.launcher_name)'))
34 parser.add_option('c', 'catalog', 'string', 'catalog',
35     _('Optional: The resources catalog to use'))
36 parser.add_option('', 'gencat', 'string', 'gencat',
37     _("Optional: Create a resources catalog for the specified machines "
38       "(separated with ',') \n\tNOTICE: this command will ssh to retrieve"
39       " information to each machine in the list"))
40 parser.add_option('', 'use_mesa', 'boolean', 'use_mesa',
41     _("Optional: Create a launcher that will use mesa products\n\t"
42       "It can be usefull whan salome is used on a remote machine through ssh"))
43
44 def generate_launch_file(config,
45                          logger,
46                          launcher_name,
47                          pathlauncher,
48                          display=True,
49                          additional_env={}):
50     '''Generates the launcher file.
51     
52     :param config Config: The global configuration
53     :param logger Logger: The logger instance to use for the display 
54                           and logging
55     :param launcher_name str: The name of the launcher to generate
56     :param pathlauncher str: The path to the launcher to generate
57     :param display boolean: If False, do not print anything in the terminal
58     :param additional_env dict: The dict giving additional 
59                                 environment variables
60     :return: The launcher file path.
61     :rtype: str
62     '''
63     
64     # Compute the default launcher path if it is not provided in pathlauncher
65     # parameter
66     filepath = os.path.join(pathlauncher, launcher_name)
67
68     # Remove the file if it exists in order to replace it
69     if os.path.exists(filepath):
70         os.remove(filepath)
71
72     # Add the APPLI variable
73     additional_env['APPLI'] = filepath
74
75
76
77     # get KERNEL bin installation path 
78     # (in order for the launcher to get python salomeContext API)
79     kernel_cfg = src.product.get_product_config(config, "KERNEL")
80     if not src.product.check_installation(kernel_cfg):
81         raise src.SatException(_("KERNEL is not installed"))
82     kernel_root_dir = kernel_cfg.install_dir
83
84     # set kernel bin dir (considering fhs property)
85     if src.get_property_in_product_cfg(kernel_cfg, "fhs"):
86         bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin") 
87     else:
88         bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin","salome") 
89
90     # check if the application contains an application module
91     l_product_info = src.product.get_products_infos(config.APPLICATION.products.keys(),
92                                                     config)
93     salome_application_name="Not defined" 
94     for prod_name, prod_info in l_product_info:
95         # look for a salome application
96         if src.get_property_in_product_cfg(prod_info, "is_salome_application") == "yes":
97             salome_application_name=prod_info.install_dir
98             continue
99
100     # if the application contains an application module, we set ABSOLUTE_APPLI_PATH to it.
101     # if not we set it to KERNEL_INSTALL_DIR, which is sufficient, except for salome test
102     if salome_application_name == "Not defined":
103         app_root_dir=kernel_root_dir
104     else:
105         app_root_dir=salome_application_name
106
107     # Get the launcher template (python3 or python2)
108     if "python3" in config.APPLICATION and config.APPLICATION.python3 == "yes":
109         withProfile = src.fileEnviron.withProfile3\
110                          .replace("BIN_KERNEL_INSTALL_DIR", bin_kernel_install_dir)\
111                          .replace("KERNEL_INSTALL_DIR", app_root_dir)
112     else:
113         withProfile = src.fileEnviron.withProfile\
114                          .replace("BIN_KERNEL_INSTALL_DIR", bin_kernel_install_dir)\
115                          .replace("KERNEL_INSTALL_DIR", app_root_dir)
116
117     before, after = withProfile.split("# here your local standalone environment\n")
118
119     # create an environment file writer
120     writer = src.environment.FileEnvWriter(config,
121                                            logger,
122                                            pathlauncher,
123                                            src_root=None,
124                                            env_info=None)
125
126     # Display some information
127     if display:
128         # Write the launcher file
129         logger.write(_("Generating launcher for %s :\n") % 
130                      src.printcolors.printcLabel(config.VARS.application), 1)
131         logger.write("  %s\n" % src.printcolors.printcLabel(filepath), 1)
132     
133     # open the file and write into it
134     launch_file = open(filepath, "w")
135     launch_file.write(before)
136     # Write
137     writer.write_cfgForPy_file(launch_file, additional_env=additional_env)
138     launch_file.write(after)
139     launch_file.close()
140     
141     # change the rights in order to make the file executable for everybody
142     os.chmod(filepath,
143              stat.S_IRUSR |
144              stat.S_IRGRP |
145              stat.S_IROTH |
146              stat.S_IWUSR |
147              stat.S_IXUSR |
148              stat.S_IXGRP |
149              stat.S_IXOTH)
150     return filepath
151
152
153 def generate_catalog(machines, config, logger):
154     """Generates an xml catalog file from a list of machines.
155     
156     :param machines List: The list of machines to add in the catalog   
157     :param config Config: The global configuration
158     :param logger Logger: The logger instance to use for the display 
159                           and logging
160     :return: The catalog file path.
161     :rtype: str
162     """
163     # remove empty machines
164     machines = map(lambda l: l.strip(), machines)
165     machines = filter(lambda l: len(l) > 0, machines)
166     
167     # log something
168     src.printcolors.print_value(logger, _("Generate Resources Catalog"),
169                                 ", ".join(machines), 4)
170     
171     # The command to execute on each machine in order to get some information
172     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
173     user = getpass.getuser()
174
175     # Create the catalog path
176     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
177     catalog = file(catfile, "w")
178     
179     # Write into it
180     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
181     for k in machines:
182         if not src.architecture.is_windows(): 
183             logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
184             logger.flush()
185
186             # Verify that the machine is accessible
187             ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
188             p = subprocess.Popen(ssh_cmd, shell=True,
189                     stdin=subprocess.PIPE,
190                     stdout=subprocess.PIPE,
191                     stderr=subprocess.PIPE)
192             p.wait()
193
194             machine_access = (p.returncode == 0) 
195             if not machine_access: # The machine is not accessible
196                 logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
197                 logger.write("    " + 
198                              src.printcolors.printcWarning(p.stderr.read()), 2)
199             else:
200                 # The machine is accessible, write the corresponding section on
201                 # the xml file
202                 logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
203                 lines = p.stdout.readlines()
204                 freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
205                 nb_proc = len(lines) -1
206                 memory = lines[-1].split(':')[-1].split()[0].strip()
207                 memory = int(memory) / 1000
208
209         catalog.write("    <machine\n")
210         catalog.write("        protocol=\"ssh\"\n")
211         catalog.write("        nbOfNodes=\"1\"\n")
212         catalog.write("        mode=\"interactif\"\n")
213         catalog.write("        OS=\"LINUX\"\n")
214
215         if (not src.architecture.is_windows()) and machine_access :
216             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
217             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
218             catalog.write("        memInMB=\"%s\"\n" % memory)
219
220         catalog.write("        userName=\"%s\"\n" % user)
221         catalog.write("        name=\"%s\"\n" % k)
222         catalog.write("        hostname=\"%s\"\n" % k)
223         catalog.write("    >\n")
224         catalog.write("    </machine>\n")
225
226     catalog.write("</resources>\n")
227     catalog.close()
228     return catfile
229
230 def copy_catalog(config, catalog_path):
231     """Copy the xml catalog file into the right location
232     
233     :param config Config: The global configuration
234     :param catalog_path str: the catalog file path
235     :return: The environment dictionary corresponding to the file path.
236     :rtype: Dict
237     """
238     # Verify the existence of the file
239     if not os.path.exists(catalog_path):
240         raise IOError(_("Catalog not found: %s") % catalog_path)
241     # Get the application directory and copy catalog inside
242     out_dir = config.APPLICATION.workdir
243     new_catalog_path = os.path.join(out_dir, "CatalogResources.xml")
244     # Do the copy
245     shutil.copy(catalog_path, new_catalog_path)
246     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}
247     return additional_environ
248
249
250
251 ##################################################
252
253 ##
254 # Describes the command
255 def description():
256     return _("The launcher command generates a SALOME launcher.\n\nexample:"
257              "\nsat launcher SALOME-master")
258
259 ##
260 # Runs the command.
261 def run(args, runner, logger):
262
263     # check for product
264     (options, args) = parser.parse_args(args)
265
266     # Verify that the command was called with an application
267     src.check_config_has_application( runner.cfg )
268     
269     # Determine the launcher name (from option, profile section or by default "salome")
270     if options.name:
271         launcher_name = options.name
272     else:
273         launcher_name = src.get_launcher_name(runner.cfg)
274
275     # set the launcher path
276     launcher_path = runner.cfg.APPLICATION.workdir
277
278     # Copy a catalog if the option is called
279     additional_environ = {}
280     if options.catalog:
281         additional_environ = copy_catalog(runner.cfg, options.catalog)
282
283     # Generate a catalog of resources if the corresponding option was called
284     if options.gencat:
285         catalog_path  = generate_catalog(options.gencat.split(","),
286                                          runner.cfg,
287                                          logger)
288         additional_environ = copy_catalog(runner.cfg, catalog_path)
289
290     # activate mesa use in the generated launcher
291     if options.use_mesa:
292         src.activate_mesa_property(runner.cfg)
293
294     # Generate the launcher
295     launcherPath = generate_launch_file( runner.cfg,
296                                          logger,
297                                          launcher_name,
298                                          launcher_path,
299                                          additional_env = additional_environ )
300
301     return 0