Salome HOME
Restructuration des fonctions de gestion de l'environnement
[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 parser.add_option('', 'no_path_init', 'boolean', 'no_path_init',
44     _("Optional: Create a launcher that will not reinitilise all path variables\n\t"
45       "By default only PATH is not reinitialised (its value is inherited from user's environment)\n\t"
46       "Use no_path_init option to suppress the reinitilisation of every paths (LD_LIBRARY_PATH, PYTHONPATH, ...)"))
47
48 def generate_launch_file(config,
49                          logger,
50                          launcher_name,
51                          pathlauncher,
52                          display=True,
53                          additional_env={},
54                          no_path_init=False):
55     '''Generates the launcher file.
56     
57     :param config Config: The global configuration
58     :param logger Logger: The logger instance to use for the display 
59                           and logging
60     :param launcher_name str: The name of the launcher to generate
61     :param pathlauncher str: The path to the launcher to generate
62     :param display boolean: If False, do not print anything in the terminal
63     :param additional_env dict: The dict giving additional 
64                                 environment variables
65     :return: The launcher file path.
66     :rtype: str
67     '''
68     
69     # Compute the default launcher path if it is not provided in pathlauncher
70     # parameter
71     filepath = os.path.join(pathlauncher, launcher_name)
72
73     # Remove the file if it exists in order to replace it
74     if os.path.exists(filepath):
75         os.remove(filepath)
76
77
78     # get KERNEL bin installation path 
79     # (in order for the launcher to get python salomeContext API)
80     kernel_cfg = src.product.get_product_config(config, "KERNEL")
81     if not src.product.check_installation(config, kernel_cfg):
82         raise src.SatException(_("KERNEL is not installed"))
83     kernel_root_dir = kernel_cfg.install_dir
84
85     # set kernel bin dir (considering fhs property)
86     if src.get_property_in_product_cfg(kernel_cfg, "fhs"):
87         bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin") 
88     else:
89         bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin","salome") 
90
91     # check if the application contains an application module
92     l_product_info = src.product.get_products_infos(config.APPLICATION.products.keys(),
93                                                     config)
94     salome_application_name="Not defined" 
95     for prod_name, prod_info in l_product_info:
96         # look for a salome application
97         if src.get_property_in_product_cfg(prod_info, "is_salome_application") == "yes":
98             salome_application_name=prod_info.install_dir
99             continue
100
101     # if the application contains an application module, we set ABSOLUTE_APPLI_PATH to it.
102     # if not we set it to KERNEL_INSTALL_DIR, which is sufficient, except for salome test
103     if salome_application_name == "Not defined":
104         app_root_dir=kernel_root_dir
105     else:
106         app_root_dir=salome_application_name
107
108     # Add two sat variables used by fileEnviron to choose the right launcher header 
109     # and do substitutions
110     additional_env['sat_bin_kernel_install_dir'] = bin_kernel_install_dir
111     if "python3" in config.APPLICATION and config.APPLICATION.python3 == "yes":
112         additional_env['sat_python_version'] = 3
113     else:
114         additional_env['sat_python_version'] = 2
115
116     # Add the APPLI and ABSOLUTE_APPLI_PATH variable
117     additional_env['APPLI'] = filepath
118     additional_env['ABSOLUTE_APPLI_PATH'] = app_root_dir
119
120     # create an environment file writer
121     writer = src.environment.FileEnvWriter(config,
122                                            logger,
123                                            pathlauncher,
124                                            src_root=None,
125                                            env_info=None)
126
127     # Display some information
128     if display:
129         # Write the launcher file
130         logger.write(_("Generating launcher for %s :\n") % 
131                      src.printcolors.printcLabel(config.VARS.application), 1)
132         logger.write("  %s\n" % src.printcolors.printcLabel(filepath), 1)
133     
134     # Write the launcher
135     writer.write_env_file(filepath, 
136                           False,  # for launch
137                           "cfgForPy",
138                           additional_env=additional_env,
139                           no_path_init=no_path_init)
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     if catalog_path != new_catalog_path:
246         shutil.copy(catalog_path, new_catalog_path)
247     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}
248     return additional_environ
249
250
251
252 ##################################################
253
254 ##
255 # Describes the command
256 def description():
257     return _("The launcher command generates a SALOME launcher.\n\nexample:"
258              "\nsat launcher SALOME-master")
259
260 ##
261 # Runs the command.
262 def run(args, runner, logger):
263
264     # check for product
265     (options, args) = parser.parse_args(args)
266
267     # Verify that the command was called with an application
268     src.check_config_has_application( runner.cfg )
269     
270     # Determine the launcher name (from option, profile section or by default "salome")
271     if options.name:
272         launcher_name = options.name
273     else:
274         launcher_name = src.get_launcher_name(runner.cfg)
275
276     no_path_initialisation=False
277     if options.no_path_init:
278         no_path_initialisation = True
279         
280     # set the launcher path
281     launcher_path = runner.cfg.APPLICATION.workdir
282
283     # Copy a catalog if the option is called
284     additional_environ = {}
285     if options.catalog:
286         additional_environ = copy_catalog(runner.cfg, options.catalog)
287
288     # Generate a catalog of resources if the corresponding option was called
289     if options.gencat:
290         catalog_path  = generate_catalog(options.gencat.split(","),
291                                          runner.cfg,
292                                          logger)
293         additional_environ = copy_catalog(runner.cfg, catalog_path)
294
295     # activate mesa use in the generated launcher
296     if options.use_mesa:
297         src.activate_mesa_property(runner.cfg)
298
299     # Generate the launcher
300     launcherPath = generate_launch_file( runner.cfg,
301                                          logger,
302                                          launcher_name,
303                                          launcher_path,
304                                          additional_env = additional_environ,
305                                          no_path_init = no_path_initialisation )
306
307     return 0