Salome HOME
sat jobs: get the remote log on the local machine. Cancel jobs that have a failing...
[tools/sat.git] / commands / launcher.py
1 #!/usr/bin/env python\r
2 #-*- coding:utf-8 -*-\r
3 #  Copyright (C) 2010-2013  CEA/DEN\r
4 #\r
5 #  This library is free software; you can redistribute it and/or\r
6 #  modify it under the terms of the GNU Lesser General Public\r
7 #  License as published by the Free Software Foundation; either\r
8 #  version 2.1 of the License.\r
9 #\r
10 #  This library is distributed in the hope that it will be useful,\r
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
13 #  Lesser General Public License for more details.\r
14 #\r
15 #  You should have received a copy of the GNU Lesser General Public\r
16 #  License along with this library; if not, write to the Free Software\r
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA\r
18 \r
19 import os\r
20 import platform\r
21 import shutil\r
22 import getpass\r
23 import subprocess\r
24 import stat\r
25 \r
26 import src\r
27 \r
28 parser = src.options.Options()\r
29 \r
30 parser.add_option('n', 'name', 'string', 'name', _('The name of the launcher '\r
31                             '(default is APPLICATION.profile.launcher_name)'))\r
32 parser.add_option('c', 'catalog', 'string', 'catalog',\r
33     _('The resources catalog to use'))\r
34 parser.add_option('', 'gencat', 'string', 'gencat',\r
35     _("Create a resources catalog for the specified machines "\r
36       "(separated with ',') \n\tNOTICE: this command will ssh to retrieve"\r
37       " information to each machine in the list"))\r
38 \r
39 def generate_launch_file(config,\r
40                          logger,\r
41                          env_info=None,\r
42                          pathlauncher=None,\r
43                          display=True,\r
44                          additional_env={}):\r
45     '''Generates the launcher file.\r
46     \r
47     :param config Config: The global configuration\r
48     :param logger Logger: The logger instance to use for the display \r
49                           and logging\r
50     :param env_info str: The list of products to add in the files.\r
51     :param pathlauncher str: The path to the launcher to generate\r
52     :param src_root str: The path to the directory where the sources are\r
53     :param display boolean: If False, do not print anything in the terminal\r
54     :param additional_env dict: The dict giving additional \r
55                                 environment variables\r
56     :return: The launcher file path.\r
57     :rtype: str\r
58     '''\r
59     # Get the application directory and the profile directory\r
60     out_dir = config.APPLICATION.workdir\r
61     profile = config.APPLICATION.profile\r
62     profile_install_dir = get_profile_dir(config)\r
63     \r
64     # Compute the default launcher path if it is not provided in pathlauncher\r
65     # parameter\r
66     if pathlauncher is None:\r
67         filepath = os.path.join( os.path.join( profile_install_dir,\r
68                                                'bin',\r
69                                                'salome' ),\r
70                                  profile['launcher_name'] )\r
71     else:\r
72         filepath = os.path.join(pathlauncher, profile['launcher_name'])\r
73 \r
74     # Remove the file if it exists in order to replace it\r
75     if os.path.exists(filepath):\r
76         os.remove(filepath)\r
77 \r
78     # Add the APPLI variable\r
79     additional_env['APPLI'] = filepath\r
80 \r
81     # Get the launcher template\r
82     withProfile = src.fileEnviron.withProfile.replace( "PROFILE_INSTALL_DIR",\r
83                                                        profile_install_dir )\r
84     before, after = withProfile.split(\r
85                                 "# here your local standalone environment\n")\r
86 \r
87     # create an environment file writer\r
88     writer = src.environment.FileEnvWriter(config,\r
89                                            logger,\r
90                                            out_dir,\r
91                                            src_root=None,\r
92                                            env_info=env_info)\r
93 \r
94     # Display some information\r
95     if display:\r
96         # Write the launcher file\r
97         logger.write(_("Generating launcher for %s :\n") % \r
98                      src.printcolors.printcLabel(config.VARS.application), 1)\r
99         logger.write("  %s\n" %src.printcolors.printcLabel(filepath), 1)\r
100     \r
101     # open the file and write into it\r
102     launch_file = open(filepath, "w")\r
103     launch_file.write(before)\r
104     # Write\r
105     writer.write_cfgForPy_file(launch_file, additional_env=additional_env)\r
106     launch_file.write(after)\r
107     launch_file.close()\r
108     \r
109     # change the rights in order to make the file executable for everybody\r
110     os.chmod(filepath,\r
111              stat.S_IRUSR |\r
112              stat.S_IRGRP |\r
113              stat.S_IROTH |\r
114              stat.S_IWUSR |\r
115              stat.S_IXUSR |\r
116              stat.S_IXGRP |\r
117              stat.S_IXOTH)\r
118     return filepath\r
119 \r
120 def generate_launch_link(config,\r
121                          logger,\r
122                          launcherPath,\r
123                          pathlauncher=None,\r
124                          display=True,\r
125                          packageLauncher=False):\r
126     '''Generates the launcher link that sources Python \r
127        and call the actual launcher.\r
128     \r
129     :param config Config: The global configuration\r
130     :param logger Logger: The logger instance to use for the display \r
131                           and logging\r
132     :param launcherPath str: The path to the launcher to call\r
133     :param pathlauncher str: The path to the launcher (link) to generate\r
134     :param display boolean: If False, do not print anything in the terminal\r
135     :param packageLauncher boolean: if True, use a relative path (for package)\r
136     :return: The launcher link file path.\r
137     :rtype: str\r
138     '''\r
139     if pathlauncher is None:\r
140         # Make an executable file that sources python, then launch the launcher\r
141         # produced by generate_launch_file method\r
142         sourceLauncher = os.path.join(config.APPLICATION.workdir,\r
143                                       config.APPLICATION.profile.launcher_name)\r
144     else:\r
145         sourceLauncher = os.path.join(pathlauncher,\r
146                                       config.APPLICATION.profile.launcher_name)\r
147 \r
148     # Change the extension for the windows case\r
149     if platform.system() == "Windows" :\r
150             sourceLauncher += '.bat'\r
151 \r
152     # display some information\r
153     if display:\r
154         logger.write(_("\nGenerating the executable that sources"\r
155                        " python and runs the launcher :\n") , 1)\r
156         logger.write("  %s\n" %src.printcolors.printcLabel(sourceLauncher), 1)\r
157 \r
158     # open the file to write\r
159     f = open(sourceLauncher, "w")\r
160 \r
161     # Write the set up of the environment\r
162     if platform.system() == "Windows" :\r
163         shell = 'bat'\r
164     else:\r
165         shell = 'bash'\r
166         \r
167     # Write the Python environment files\r
168     env = src.environment.SalomeEnviron( config, \r
169                         src.fileEnviron.get_file_environ( f, shell, config ) )\r
170     env.set_a_product( "Python", logger)\r
171 \r
172     # Write the call to the original launcher\r
173     f.write( "\n\n")\r
174     if packageLauncher:\r
175         cmd = os.path.join('${out_dir_Path}', launcherPath)\r
176     else:\r
177         cmd = launcherPath\r
178 \r
179     if platform.system() == "Windows" :\r
180         cmd = 'python ' + cmd + ' %*'\r
181     else:\r
182         cmd = cmd + ' $*'\r
183     \r
184     f.write( cmd )\r
185     f.write( "\n\n")\r
186 \r
187     # Write the cleaning of the environment\r
188     env.finish(True)\r
189 \r
190     # Close new launcher\r
191     f.close()\r
192     os.chmod(sourceLauncher,\r
193              stat.S_IRUSR |\r
194              stat.S_IRGRP |\r
195              stat.S_IROTH |\r
196              stat.S_IWUSR |\r
197              stat.S_IWGRP |\r
198              stat.S_IWOTH |\r
199              stat.S_IXUSR |\r
200              stat.S_IXGRP |\r
201              stat.S_IXOTH)\r
202     return sourceLauncher\r
203 \r
204 def generate_catalog(machines, config, logger):\r
205     """Generates an xml catalog file from a list of machines.\r
206     \r
207     :param machines List: The list of machines to add in the catalog   \r
208     :param config Config: The global configuration\r
209     :param logger Logger: The logger instance to use for the display \r
210                           and logging\r
211     :return: The catalog file path.\r
212     :rtype: str\r
213     """\r
214     # remove empty machines\r
215     machines = map(lambda l: l.strip(), machines)\r
216     machines = filter(lambda l: len(l) > 0, machines)\r
217     \r
218     # log something\r
219     src.printcolors.print_value(logger, _("Generate Resources Catalog"),\r
220                                 ", ".join(machines), 4)\r
221     \r
222     # The command to execute on each machine in order to get some information\r
223     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'\r
224     user = getpass.getuser()\r
225 \r
226     # Create the catalog path\r
227     catfile = src.get_tmp_filename(config, "CatalogResources.xml")\r
228     catalog = file(catfile, "w")\r
229     \r
230     # Write into it\r
231     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")\r
232     for k in machines:\r
233         logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)\r
234         logger.flush()\r
235 \r
236         # Verify that the machine is accessible\r
237         ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)\r
238         p = subprocess.Popen(ssh_cmd, shell=True,\r
239                 stdin=subprocess.PIPE,\r
240                 stdout=subprocess.PIPE,\r
241                 stderr=subprocess.PIPE)\r
242         p.wait()\r
243 \r
244         if p.returncode != 0: # The machine is not accessible\r
245             logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)\r
246             logger.write("    " + \r
247                          src.printcolors.printcWarning(p.stderr.read()), 2)\r
248         else:\r
249             # The machine is accessible, write the corresponding section on\r
250             # the xml file\r
251             logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)\r
252             lines = p.stdout.readlines()\r
253             freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()\r
254             nb_proc = len(lines) -1\r
255             memory = lines[-1].split(':')[-1].split()[0].strip()\r
256             memory = int(memory) / 1000\r
257 \r
258             catalog.write("    <machine\n")\r
259             catalog.write("        protocol=\"ssh\"\n")\r
260             catalog.write("        nbOfNodes=\"1\"\n")\r
261             catalog.write("        mode=\"interactif\"\n")\r
262             catalog.write("        OS=\"LINUX\"\n")\r
263             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)\r
264             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)\r
265             catalog.write("        memInMB=\"%s\"\n" % memory)\r
266             catalog.write("        userName=\"%s\"\n" % user)\r
267             catalog.write("        name=\"%s\"\n" % k)\r
268             catalog.write("        hostname=\"%s\"\n" % k)\r
269             catalog.write("    >\n")\r
270             catalog.write("    </machine>\n")\r
271 \r
272     catalog.write("</resources>\n")\r
273     catalog.close()\r
274     return catfile\r
275 \r
276 def copy_catalog(config, catalog_path):\r
277     """Copy the xml catalog file into the right location\r
278     \r
279     :param config Config: The global configuration\r
280     :param catalog_path str: the catalog file path\r
281     :return: The environment dictionary corresponding to the file path.\r
282     :rtype: Dict\r
283     """\r
284     # Verify the existence of the file\r
285     if not os.path.exists(catalog_path):\r
286         raise IOError(_("Catalog not found: %s") % catalog_path)\r
287     # Compute the location where to copy the file\r
288     profile_dir = get_profile_dir(config)\r
289     new_catalog_path = os.path.join(profile_dir, "CatalogResources.xml")\r
290     # Do the copy\r
291     shutil.copy(catalog_path, new_catalog_path)\r
292     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}\r
293     return additional_environ\r
294 \r
295 def get_profile_dir(config):\r
296     """Get the profile directory from the config\r
297     \r
298     :param config Config: The global configuration\r
299     :return: The profile install directory\r
300     :rtype: Str\r
301     """\r
302     profile_name = config.APPLICATION.profile.product\r
303     profile_info = src.product.get_product_config(config, profile_name)\r
304     return profile_info.install_dir\r
305 \r
306 def finish_profile_install(config, launcherPath):\r
307     """Add some symlinks required for SALOME\r
308     \r
309     :param config Config: The global configuration\r
310     :param launcherPath str: the launcher file path\r
311     """\r
312     # Create a USERS directory\r
313     profile_dir = get_profile_dir(config)\r
314     user_dir = os.path.join(profile_dir, 'USERS')\r
315     if not os.path.exists(user_dir):\r
316         os.makedirs(user_dir)\r
317     # change rights of USERS directory\r
318     os.chmod(user_dir,\r
319              stat.S_IRUSR |\r
320              stat.S_IRGRP |\r
321              stat.S_IROTH |\r
322              stat.S_IWUSR |\r
323              stat.S_IWGRP |\r
324              stat.S_IWOTH |\r
325              stat.S_IXUSR |\r
326              stat.S_IXGRP |\r
327              stat.S_IXOTH)\r
328 \r
329     # create a link in root directory to the launcher\r
330     if platform.system() != "Windows" :
331         link_path = os.path.join(config.APPLICATION.workdir, 'salome')\r
332         if not os.path.exists(link_path):\r
333             try:\r
334                 os.symlink(launcherPath, link_path)\r
335             except OSError:\r
336                 os.remove(link_path)\r
337                 os.symlink(launcherPath, link_path)\r
338 \r
339         link_path = os.path.join(profile_dir, 'salome')\r
340         if not os.path.exists(link_path):\r
341             try:\r
342                 os.symlink(launcherPath, link_path)\r
343             except OSError:\r
344                 os.remove(link_path)\r
345                 os.symlink(launcherPath, link_path)
346 \r
347 ##################################################\r
348 \r
349 ##\r
350 # Describes the command\r
351 def description():\r
352     return _("The launcher command generates a salome launcher.")\r
353 \r
354 ##\r
355 # Runs the command.\r
356 def run(args, runner, logger):\r
357 \r
358     # check for product\r
359     (options, args) = parser.parse_args(args)\r
360 \r
361     # Verify that the command was called with an application\r
362     src.check_config_has_application( runner.cfg )\r
363     \r
364     # Verify that the APPLICATION section has a profile section\r
365     src.check_config_has_profile( runner.cfg )\r
366 \r
367     # Verify that the profile is installed\r
368     if not src.product.check_installation(\r
369                                 src.product.get_product_config(\r
370                                     runner.cfg,\r
371                                     runner.cfg.APPLICATION.profile.product)):\r
372         msg = _("The profile of the application is not correctly installed.")\r
373         logger.write(src.printcolors.printcError(msg), 1)\r
374         return 1\r
375 \r
376     # Change the name of the file to create \r
377     # if the corresponding option was called\r
378     if options.name:\r
379         runner.cfg.APPLICATION.profile['launcher_name'] = options.name\r
380 \r
381     # Copy a catalog if the option is called\r
382     additional_environ = {}\r
383     if options.catalog:\r
384         additional_environ = copy_catalog(runner.cfg, options.catalog)\r
385 \r
386     # Generate a catalog of resources if the corresponding option was called\r
387     if options.gencat:\r
388         catalog_path  = generate_catalog(options.gencat.split(","),\r
389                                          runner.cfg,\r
390                                          logger)\r
391         additional_environ = copy_catalog(runner.cfg, catalog_path)\r
392 \r
393     # Generate the launcher\r
394     launcherPath = generate_launch_file( runner.cfg,\r
395                                          logger,\r
396                                          additional_env = additional_environ )\r
397 \r
398     # Create the link (bash file that sources python and then call \r
399     # the actual launcher) to the launcher\r
400     generate_launch_link( runner.cfg, logger, launcherPath)\r
401 \r
402     # Add some links\r
403     finish_profile_install(runner.cfg, launcherPath)\r
404 \r
405     return 0\r