Salome HOME
merge cvw/sprint_180319 for doc alabaster
[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
108     withProfile = src.fileEnviron.withProfile\
109                      .replace("BIN_KERNEL_INSTALL_DIR", bin_kernel_install_dir)\
110                      .replace("KERNEL_INSTALL_DIR", app_root_dir)
111
112     before, after = withProfile.split(
113                                 "# here your local standalone environment\n")
114
115     # create an environment file writer
116     writer = src.environment.FileEnvWriter(config,
117                                            logger,
118                                            pathlauncher,
119                                            src_root=None,
120                                            env_info=None)
121
122     # Display some information
123     if display:
124         # Write the launcher file
125         logger.write(_("Generating launcher for %s :\n") % 
126                      src.printcolors.printcLabel(config.VARS.application), 1)
127         logger.write("  %s\n" % src.printcolors.printcLabel(filepath), 1)
128     
129     # open the file and write into it
130     launch_file = open(filepath, "w")
131     launch_file.write(before)
132     # Write
133     writer.write_cfgForPy_file(launch_file, additional_env=additional_env)
134     launch_file.write(after)
135     launch_file.close()
136     
137     # change the rights in order to make the file executable for everybody
138     os.chmod(filepath,
139              stat.S_IRUSR |
140              stat.S_IRGRP |
141              stat.S_IROTH |
142              stat.S_IWUSR |
143              stat.S_IXUSR |
144              stat.S_IXGRP |
145              stat.S_IXOTH)
146     return filepath
147
148
149 def generate_catalog(machines, config, logger):
150     """Generates an xml catalog file from a list of machines.
151     
152     :param machines List: The list of machines to add in the catalog   
153     :param config Config: The global configuration
154     :param logger Logger: The logger instance to use for the display 
155                           and logging
156     :return: The catalog file path.
157     :rtype: str
158     """
159     # remove empty machines
160     machines = map(lambda l: l.strip(), machines)
161     machines = filter(lambda l: len(l) > 0, machines)
162     
163     # log something
164     src.printcolors.print_value(logger, _("Generate Resources Catalog"),
165                                 ", ".join(machines), 4)
166     
167     # The command to execute on each machine in order to get some information
168     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
169     user = getpass.getuser()
170
171     # Create the catalog path
172     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
173     catalog = file(catfile, "w")
174     
175     # Write into it
176     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
177     for k in machines:
178         logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
179         logger.flush()
180
181         # Verify that the machine is accessible
182         ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
183         p = subprocess.Popen(ssh_cmd, shell=True,
184                 stdin=subprocess.PIPE,
185                 stdout=subprocess.PIPE,
186                 stderr=subprocess.PIPE)
187         p.wait()
188
189         if p.returncode != 0: # The machine is not accessible
190             logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
191             logger.write("    " + 
192                          src.printcolors.printcWarning(p.stderr.read()), 2)
193         else:
194             # The machine is accessible, write the corresponding section on
195             # the xml file
196             logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
197             lines = p.stdout.readlines()
198             freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
199             nb_proc = len(lines) -1
200             memory = lines[-1].split(':')[-1].split()[0].strip()
201             memory = int(memory) / 1000
202
203             catalog.write("    <machine\n")
204             catalog.write("        protocol=\"ssh\"\n")
205             catalog.write("        nbOfNodes=\"1\"\n")
206             catalog.write("        mode=\"interactif\"\n")
207             catalog.write("        OS=\"LINUX\"\n")
208             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
209             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
210             catalog.write("        memInMB=\"%s\"\n" % memory)
211             catalog.write("        userName=\"%s\"\n" % user)
212             catalog.write("        name=\"%s\"\n" % k)
213             catalog.write("        hostname=\"%s\"\n" % k)
214             catalog.write("    >\n")
215             catalog.write("    </machine>\n")
216
217     catalog.write("</resources>\n")
218     catalog.close()
219     return catfile
220
221 def copy_catalog(config, catalog_path):
222     """Copy the xml catalog file into the right location
223     
224     :param config Config: The global configuration
225     :param catalog_path str: the catalog file path
226     :return: The environment dictionary corresponding to the file path.
227     :rtype: Dict
228     """
229     # Verify the existence of the file
230     if not os.path.exists(catalog_path):
231         raise IOError(_("Catalog not found: %s") % catalog_path)
232     # Get the application directory and copy catalog inside
233     out_dir = config.APPLICATION.workdir
234     new_catalog_path = os.path.join(out_dir, "CatalogResources.xml")
235     # Do the copy
236     shutil.copy(catalog_path, new_catalog_path)
237     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}
238     return additional_environ
239
240
241
242 ##################################################
243
244 ##
245 # Describes the command
246 def description():
247     return _("The launcher command generates a SALOME launcher.\n\nexample:"
248              "\nsat launcher SALOME-master")
249
250 ##
251 # Runs the command.
252 def run(args, runner, logger):
253
254     # check for product
255     (options, args) = parser.parse_args(args)
256
257     # Verify that the command was called with an application
258     src.check_config_has_application( runner.cfg )
259     
260     # Determine the launcher name (from option, profile section or by default "salome")
261     if options.name:
262         launcher_name = options.name
263     else:
264         launcher_name = src.get_launcher_name(runner.cfg)
265
266     # set the launcher path
267     launcher_path = runner.cfg.APPLICATION.workdir
268
269     # Copy a catalog if the option is called
270     additional_environ = {}
271     if options.catalog:
272         additional_environ = copy_catalog(runner.cfg, options.catalog)
273
274     # Generate a catalog of resources if the corresponding option was called
275     if options.gencat:
276         catalog_path  = generate_catalog(options.gencat.split(","),
277                                          runner.cfg,
278                                          logger)
279         additional_environ = copy_catalog(runner.cfg, catalog_path)
280
281     # activate mesa use in the generated launcher
282     if options.use_mesa:
283         src.activate_mesa_property(runner.cfg)
284
285     # Generate the launcher
286     launcherPath = generate_launch_file( runner.cfg,
287                                          logger,
288                                          launcher_name,
289                                          launcher_path,
290                                          additional_env = additional_environ )
291
292     return 0