Salome HOME
b2917df824b463e1076dabf453fecba94df5f34c
[modules/smesh.git] / src / Tools / padder / meshjob / impl / MeshJobManager_i.cxx
1 //  Copyright (C) 2011  EDF R&D
2 //
3 //  This library is free software; you can redistribute it and/or
4 //  modify it under the terms of the GNU Lesser General Public
5 //  License as published by the Free Software Foundation; either
6 //  version 2.1 of the License.
7 //
8 //  This library is distributed in the hope that it will be useful,
9 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 //  Lesser General Public License for more details.
12 //
13 //  You should have received a copy of the GNU Lesser General Public
14 //  License along with this library; if not, write to the Free Software
15 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 //  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19 // Authors : Guillaume Boulant (EDF) - 01/03/2011
20
21 #include "MeshJobManager_i.hxx"
22
23 #include <SALOMEconfig.h>
24 #include CORBA_SERVER_HEADER(SALOME_Exception)
25
26 #include "Basics_Utils.hxx"         // For standard logging
27 #include "SALOME_KernelServices.hxx"   // For CORBA logging
28 #undef LOG
29 #define LOG STDLOG
30
31 #include "testhelper.hxx"
32
33 //
34 // ====================================================================
35 // General purpose helper functions (to put elsewhere at least)
36 // ====================================================================
37 //
38 #include <sys/time.h>
39 /*!
40  * This function must be used to associate a datetime tag to a job
41  */
42 static long timetag() {
43   timeval tv;
44   gettimeofday(&tv,0);
45   long tag = tv.tv_usec + tv.tv_sec*1000000;
46   return tag;
47 }
48
49 /*!
50  * This function returns true if the string text starts with the string
51  * token.
52  */
53 static bool myStartsWith(const std::string& text,const std::string& token){     
54   if(text.length() < token.length())
55     return false;
56   return (text.compare(0, token.length(), token) == 0);
57 }
58
59 //
60 // ====================================================================
61 // Constructor/Destructor
62 // ====================================================================
63 //
64 MeshJobManager_i::MeshJobManager_i(CORBA::ORB_ptr orb,
65                                    PortableServer::POA_ptr poa,
66                                    PortableServer::ObjectId * contId,
67                                    const char *instanceName,
68                                    const char *interfaceName)
69   : Engines_Component_i(orb, poa, contId, instanceName, interfaceName)
70 {
71   LOG("Activating MESHJOB::MeshJobManager object");
72   _thisObj = this ;
73   _id = _poa->activate_object(_thisObj);
74
75   _salomeLauncher   = KERNEL::getSalomeLauncher();
76   if(CORBA::is_nil(_salomeLauncher)){
77     LOG("The SALOME launcher can't be reached ==> STOP");
78     throw KERNEL::createSalomeException("SALOME launcher can't be reached");
79   }
80
81   _resourcesManager = KERNEL::getResourcesManager();
82   if(CORBA::is_nil(_resourcesManager)){
83     LOG("The SALOME resource manager can't be reached ==> STOP");
84     throw KERNEL::createSalomeException("The SALOME resource manager can't be reached");
85   }
86 }
87
88 MeshJobManager_i::~MeshJobManager_i() {
89   LOG("MeshJobManager_i::~MeshJobManager_i()");
90 }
91
92 //
93 // ====================================================================
94 // Helper functions to deals with the local and remote file systems
95 // ====================================================================
96 //
97 #include <fstream>     // to get the file streams
98 #include <sys/stat.h>  // to get mkdir
99 #include <sys/types.h> // to get mkdir options
100 #include <unistd.h>    // to get basename
101 #include <stdlib.h>    // to get system and getenv
102
103 static std::string OUTPUTFILE("output.med");
104 static std::string DATAFILE("data.txt");
105 static std::string SCRIPTFILE("padder.sh");
106 static std::string SEPARATOR(" ");
107
108 static std::string USER(getenv("USER"));
109 static std::string LOCAL_INPUTDIR("/tmp/spadder.local.inputdir."+USER);
110 static std::string LOCAL_RESULTDIR("/tmp/spadder.local.resultdir."+USER);
111 static std::string REMOTE_WORKDIR("/tmp/spadder.remote.workdir."+USER);
112
113 /*!
114  * This function creates the padder text input file containing the
115  * input data (list of filenames and groupnames) and returns the path
116  * of the created file. This function is the one that knows the format
117  * of the padder input file. If the input file format changes, then
118  * this function (and only this one) should be updated.
119  */
120 const char * MeshJobManager_i::_writeDataFile(std::vector<MESHJOB::MeshJobParameter> listConcreteMesh,
121                                               std::vector<MESHJOB::MeshJobParameter> listSteelBarMesh) {
122
123   mkdir(LOCAL_INPUTDIR.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
124
125   // Make it static so that it's allocated once (constant name)
126   static std::string * dataFilename = new std::string(LOCAL_INPUTDIR+"/"+DATAFILE);
127   std::ofstream dataFile(dataFilename->c_str());
128   
129   // We first specify the concrete mesh data (filename and groupname)
130   std::string line;
131   line = std::string(basename(listConcreteMesh[0].file_name)) + " " + std::string(listConcreteMesh[0].group_name);
132   dataFile << line.c_str() << std::endl;
133   // Note that we use here the basename because the files are supposed
134   // to be copied in the REMOTE_WORKDIR for execution.
135   
136   // The, we can specify the steelbar mesh data, starting by the
137   // number of meshes
138   int nbSteelBarMesh=listSteelBarMesh.size();
139   line = std::string("nbSteelbarMesh") + SEPARATOR + ToString(nbSteelBarMesh);
140   dataFile << line.c_str() << std::endl;
141   for (int i=0; i<nbSteelBarMesh; i++) {
142     line = std::string(basename(listSteelBarMesh[i].file_name)) + " " + std::string(listSteelBarMesh[i].group_name);
143     dataFile << line.c_str() << std::endl;
144   }
145   
146   // Finally, we conclude with the name of the output file
147   line = OUTPUTFILE;
148   dataFile << line.c_str() << std::endl;
149   dataFile.close();
150   return dataFilename->c_str();  
151 }
152
153 /*!
154  * This function creates a shell script that runs padder whith the
155  * specified data file, and returns the path of the created script
156  * file. The config id is used to retrieve the path to the binary file
157  * and other required files.
158  */
159 const char* MeshJobManager_i::_writeScriptFile(const char * dataFileName, const char * configId) {
160   mkdir(LOCAL_INPUTDIR.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
161
162   // Make it static so that it's allocated once (constant name)
163   static std::string * scriptFilename = new std::string(LOCAL_INPUTDIR+"/"+SCRIPTFILE);
164
165   char * binpath = _configMap[configId].binpath;
166   char * envpath = _configMap[configId].envpath;
167
168   std::ofstream script(scriptFilename->c_str());
169   script << "#!/bin/sh"                                   << std::endl;
170   script << "here=$(dirname $0)"                          << std::endl;
171   script << ". " << envpath                               << std::endl;
172   script << binpath << " $here/" << basename(dataFileName) << std::endl;
173   // Note that we use the basename of the datafile because all data
174   // files are supposed to have been copied in the REMOTE_WORKDIR.
175   script.close();
176   return scriptFilename->c_str();
177 }
178
179 //
180 // ====================================================================
181 // Functions to initialize and supervise the mesh computation job
182 // ====================================================================
183 //
184 bool MeshJobManager_i::configure(const char *configId,
185                                  const MESHJOB::ConfigParameter & configParameter)
186 {
187   beginService("MeshJobManager_i::configure");
188   
189   _configMap[configId] = configParameter;
190
191   LOG("Adding configuration for " << configId);
192   LOG("- binpath = " << _configMap[configId].binpath);
193   LOG("- envpath = " << _configMap[configId].envpath);
194
195   endService("MeshJobManager_i::configure");
196   return true;
197 }
198
199 long MeshJobManager_i::JOBID_UNDEFINED = -1;
200
201 /*! Initialize a smesh computation job and return the job identifier */
202 CORBA::Long MeshJobManager_i::initialize(const MESHJOB::MeshJobParameterList & meshJobParameterList,
203                                          const char * configId)
204 {
205   beginService("MeshJobManager_i::initialize");
206
207   //
208   // We first analyse the CORBA sequence to store data in C++ vectors
209   //
210   std::vector<MESHJOB::MeshJobParameter> listConcreteMesh;
211   std::vector<MESHJOB::MeshJobParameter> listSteelBarMesh;
212   for(CORBA::ULong i=0; i<meshJobParameterList.length(); i++) {
213     MESHJOB::MeshJobParameter currentMesh = meshJobParameterList[i];
214     switch ( currentMesh.file_type ) {
215     case MESHJOB::MED_CONCRETE:
216       listConcreteMesh.push_back(currentMesh);
217       break;
218     case MESHJOB::MED_STEELBAR:
219       listSteelBarMesh.push_back(currentMesh);
220       break;
221     default:
222       LOG("The type of the file is not recognized");
223       return JOBID_UNDEFINED;
224     }
225   }
226   
227   if ( listConcreteMesh.size() != 1 ) {
228     // Not consistent with the specification
229     LOG("You specify more than one concrete mesh");
230     return JOBID_UNDEFINED;
231   }
232   
233   LOG("Nb. concrete mesh = " << listConcreteMesh.size());
234   LOG("Nb. steelbar mesh = " << listSteelBarMesh.size());
235
236   // We initiate here a datetime to tag the files and folder
237   // associated to this job.
238   long jobDatetimeTag = timetag();
239   // And a MESHJOB::MeshJobPaths structure to hold the directories
240   // where to find data
241   MESHJOB::MeshJobPaths * jobPaths = new MESHJOB::MeshJobPaths();
242   jobPaths->local_inputdir  = LOCAL_INPUTDIR.c_str();
243   jobPaths->local_resultdir = (LOCAL_RESULTDIR + "." + ToString(jobDatetimeTag)).c_str();
244   jobPaths->remote_workdir  = (REMOTE_WORKDIR + "." + ToString(jobDatetimeTag)).c_str();  
245
246   //
247   // Then, we have to create the padder input data file. This input
248   // data is a text file containing the list of file names and group
249   // names.
250   //
251   const char * dataFilename = this->_writeDataFile(listConcreteMesh, listSteelBarMesh);
252   LOG("dataFilename = " << dataFilename);
253   const char * scriptFilename = this->_writeScriptFile(dataFilename, configId);
254   LOG("scriptFilename = " << scriptFilename);
255
256   //
257   // Then, the following instructions consists in preparing the job
258   // parameters to request the SALOME launcher for creating a new
259   // job.
260   //
261   Engines::JobParameters_var jobParameters = new Engines::JobParameters;
262   jobParameters->job_type = CORBA::string_dup("command");
263   // CAUTION: the job_file must be a single filename specifying a
264   // self-consistent script to be executed without any argument on the
265   // remote host.
266   jobParameters->job_file = CORBA::string_dup(scriptFilename);
267
268   //
269   // Specification of the working spaces:
270   //
271   // - local_directory: can be used to specify where to find the input
272   //   files on the local resource. It's optionnal if you specify the
273   //   absolute path name of input files.
274   //
275   // - result_directory: must be used to specify where to download the
276   //   output files on the local resources
277   //
278   // - work_directory: must be used to specify the remote directory
279   //   where to put all the stuff to run the job. Note that the job
280   //   will be executed from within this directory, i.e. a change
281   //   directory toward this working directory is done by the batch
282   //   system before running the specified job script.
283   //
284   jobParameters->local_directory  = CORBA::string_dup("");
285   jobParameters->result_directory = CORBA::string_dup(jobPaths->local_resultdir);
286   jobParameters->work_directory   = CORBA::string_dup(jobPaths->remote_workdir);
287
288   // We specify the input files that are required to execute the
289   // job_file. If basenames are specified, then the files are supposed
290   // to be located in local_directory.
291   int nbFiles = listSteelBarMesh.size()+2;
292   // The number of input file is: 
293   //   (nb. of steelbar meshfile)
294   // + (1 concrete meshfile)
295   // + (1 padder input file)
296   // = nb steelbar meshfile + 2
297   jobParameters->in_files.length(nbFiles);
298   jobParameters->in_files[0] = CORBA::string_dup(listConcreteMesh[0].file_name);
299   for (int i=0; i<listSteelBarMesh.size(); i++) {
300     jobParameters->in_files[1+i] = CORBA::string_dup(listSteelBarMesh[i].file_name);
301   }
302   jobParameters->in_files[1+listSteelBarMesh.size()] = CORBA::string_dup(dataFilename);
303   // Note that all these input files will be copied in the
304   // REMOTE_WORKDIR on the remote host
305   
306   // Then, we have to specify the existance of an output
307   // filenames. The path is supposed to be a path on the remote
308   // resource, i.e. where the job is executed.
309   jobParameters->out_files.length(1);
310   std::string outputfile_name = std::string(jobPaths->remote_workdir)+"/"+OUTPUTFILE;
311   jobParameters->out_files[0] = CORBA::string_dup(outputfile_name.c_str());
312
313   // CAUTION: the maximum duration has to be set with a format like "hh:mm"
314   jobParameters->maximum_duration = CORBA::string_dup("01:00");
315   jobParameters->queue = CORBA::string_dup("");
316
317   // Setting resource and additionnal properties (if needed)
318   // The resource parameters can be initiated from scratch, for
319   // example by specifying the values in hard coding:
320   // >>>
321   //jobParameters->resource_required.name = CORBA::string_dup("localhost");
322   //jobParameters->resource_required.hostname = CORBA::string_dup("localhost");
323   //jobParameters->resource_required.mem_mb = 1024 * 10;
324   //jobParameters->resource_required.nb_proc = 1;
325   // <<<
326   // But it's better to initiate these parameters from a resource
327   // definition known by the resource manager. This ensures that the
328   // resource will be available:
329   //const char * resourceName = "localhost";
330   //const char * resourceName = "boulant@claui2p1";
331   //const char * resourceName = "nepal@nepal";
332   const char * resourceName = _configMap[configId].resname;
333   Engines::ResourceDefinition * resourceDefinition = _resourcesManager->GetResourceDefinition(resourceName);
334   // CAUTION: This resource should have been defined in the
335   // CatalogResource.xml associated to the SALOME application.
336   //
337   // Then, the values can be used to initiate the resource parameters
338   // of the job:
339   jobParameters->resource_required.name     = CORBA::string_dup(resourceDefinition->name.in());
340   // CAUTION: the additionnal two following parameters MUST be
341   // specified explicitly, because they are not provided by the
342   // resource definition:
343   jobParameters->resource_required.mem_mb   = resourceDefinition->mem_mb;
344   jobParameters->resource_required.nb_proc  = resourceDefinition->nb_proc_per_node;
345   // CAUTION: the parameter mem_mb specifies the maximum memory value
346   // that could be allocated for executing the job. This takes into
347   // account not only the data that could be loaded by the batch
348   // process but also the linked dynamic library.
349   //
350   // A possible problem, for exemple in the case where you use the ssh
351   // emulation of a batch system, is to get an error message as below
352   // when libBatch try to run the ssh command:
353   //
354   // ## /usr/bin/ssh: error while loading shared libraries: libcrypto.so.0.9.8: failed
355   // ## to map segment from shared object: Cannot allocate memory
356   //
357   // In this exemple, the mem_mb was set to 1MB, value that is not
358   // sufficient to load the dynamic libraries linked to the ssh
359   // executable (libcrypto.so in the error message).
360   //
361   // So, even in the case of a simple test shell script, you should
362   // set this value at least to a standard threshold as 500MB
363
364   int jobId = JOBID_UNDEFINED;
365   try {
366     jobId = _salomeLauncher->createJob(jobParameters);
367     // We register the datetime tag of this job
368     _jobDateTimeMap[jobId]=jobDatetimeTag;
369     _jobPathsMap[jobId] = jobPaths;
370   }
371   catch (const SALOME::SALOME_Exception & ex) {
372     LOG("SALOME Exception in createJob !" <<ex.details.text.in());
373     //LOG(ex.details.text.in());
374     return JOBID_UNDEFINED;
375   }
376   catch (const CORBA::SystemException& ex) {
377     LOG("Receive SALOME System Exception: "<<ex);
378     LOG("Check SALOME servers...");
379     return JOBID_UNDEFINED;
380   }
381   
382   endService("MeshJobManager_i::initialize");
383   return jobId;
384 }
385
386 /*! Submit the job execution and return true if submission is OK */
387 bool MeshJobManager_i::start(CORBA::Long jobId) {
388   beginService("MeshJobManager_i::start");
389
390   try {
391     _salomeLauncher->launchJob(jobId);
392   }
393   catch (const SALOME::SALOME_Exception & ex) {
394     LOG("SALOME Exception in createJob !" <<ex.details.text.in());
395     //LOG(ex.details.text.in());
396     return false;
397   }
398   catch (const CORBA::SystemException& ex) {
399     LOG("Receive SALOME System Exception: "<<ex);
400     LOG("Check SALOME servers...");
401     return false;
402   }
403
404   endService("MeshJobManager_i::initialize");
405   return true;
406 }
407
408 /*! Request the launch manager for the state of the specified job */
409 char* MeshJobManager_i::getState(CORBA::Long jobId) {
410   beginService("MeshJobManager_i::getState");
411
412   std::string state;
413   try
414   {
415     state = _salomeLauncher->getJobState(jobId);
416   }
417   catch (const SALOME::SALOME_Exception & ex)
418   {
419     LOG("SALOME Exception in getJobState !");
420     state = ex.details.text;
421   }
422   catch (const CORBA::SystemException& ex)
423   {
424     LOG("Receive SALOME System Exception: " << ex);
425     state="SALOME System Exception - see logs";
426   }
427   LOG("jobId="<<ToString(jobId)<<" state="<<state);
428   endService("MeshJobManager_i::getState");
429   return CORBA::string_dup(state.c_str());
430 }
431
432 MESHJOB::MeshJobPaths * MeshJobManager_i::getPaths(CORBA::Long jobId) {
433
434   MESHJOB::MeshJobPaths * jobPaths = _jobPathsMap[jobId];
435   if ( jobPaths == NULL ) {
436     LOG("You request the working paths for an undefined job (jobId="<<ToString(jobId)<<")");
437     return NULL; // Maybe raise an exception?
438   }
439   return jobPaths;
440 }
441
442
443 MESHJOB::MeshJobResults * MeshJobManager_i::finalize(CORBA::Long jobId) {
444   beginService("MeshJobManager_i::getResults");
445   MESHJOB::MeshJobResults * result = new MESHJOB::MeshJobResults();
446
447   MESHJOB::MeshJobPaths * jobPaths = this->getPaths(jobId);
448   std::string local_resultdir(jobPaths->local_resultdir);
449   result->results_dirname = local_resultdir.c_str();  
450   try
451   {
452     _salomeLauncher->getJobResults(jobId, local_resultdir.c_str());
453  
454     // __BUG__: to prevent from a bug of the MED driver (SALOME
455     // 5.1.5), we change the basename of the output file to force the
456     // complete reloading of data by the med driver.
457     long jobDatetimeTag = _jobDateTimeMap[jobId];
458     std::string outputFileName = "output"+ToString(jobDatetimeTag)+".med";
459     rename((local_resultdir+"/"+OUTPUTFILE).c_str(), (local_resultdir+"/"+outputFileName).c_str());
460
461     result->outputmesh_filename = outputFileName.c_str();
462     result->status = "OK";
463  }
464   catch (const SALOME::SALOME_Exception & ex)
465   {
466     LOG("SALOME Exception in getResults !");
467     result->status = "SALOME Exception in getResults !";
468   }
469   catch (const CORBA::SystemException& ex)
470   {
471     LOG("Receive CORBA System Exception: " << ex);
472     result->status = "Receive CORBA System Exception: see log";
473   }
474   endService("MeshJobManager_i::getResults");
475   return result;
476 }
477
478
479 /*! Clean all data associated to this job and remove the job from the launch manager */
480 bool MeshJobManager_i::clean(CORBA::Long jobId) {
481   beginService("MeshJobManager_i::clean");
482   
483   // __GBO__ WORK IN PROGRESS: we just clean the temporary local
484   // directories. The remote working directories are tag with the
485   // execution datetime and the we prevent the task from conflict
486   // with files of another task.
487   MESHJOB::MeshJobPaths * jobPaths = this->getPaths(jobId);
488   if ( jobPaths == NULL ) return false;
489
490   // WARN: !!!!!
491   // For safety reason (and prevent from bug that could erase the
492   // filesystem), we cancel the operation in the case where the
493   // directories to delete are not in the /tmp folder.
494   std::string shell_command("rm -rf ");
495   std::string inputdir(jobPaths->local_inputdir);
496   std::string resultdir(jobPaths->local_resultdir);
497   if ( !myStartsWith(inputdir,"/tmp/") )  {
498     LOG("WRN: The directory "<<inputdir<<" is not in /tmp. NO DELETE is done");
499   } else {
500     shell_command+=inputdir+" ";
501   }
502   if ( !myStartsWith(resultdir,"/tmp/"))  {
503     LOG("WRN: The directory "<<resultdir<<" is not in /tmp. NO DELETE is done");
504   } else {
505     shell_command+=resultdir;
506   }
507
508   LOG("DBG: clean shell command = "<<shell_command);
509
510   bool cleanOk = false;
511   int error = system(shell_command.c_str());
512   if (error == 0) cleanOk = true;
513
514   endService("MeshJobManager_i::clean");
515   return cleanOk;
516 }
517
518
519 std::vector<std::string> * MeshJobManager_i::_getResourceNames() {
520
521   //
522   // These part is just to control the available resources
523   //
524   Engines::ResourceParameters params;
525   KERNEL::getLifeCycleCORBA()->preSet(params);
526
527   Engines::ResourceList * resourceList = _resourcesManager->GetFittingResources(params);
528   Engines::ResourceDefinition * resourceDefinition = NULL;
529   LOG("### resource list:");
530   std::vector<std::string>* resourceNames = new std::vector<std::string>();
531   if (resourceList) {
532     for (int i = 0; i < resourceList->length(); i++) {
533       const char* aResourceName = (*resourceList)[i];
534       resourceNames->push_back(std::string(aResourceName));
535       LOG("resource["<<i<<"] = "<<aResourceName);
536       resourceDefinition = _resourcesManager->GetResourceDefinition(aResourceName);
537       LOG("protocol["<<i<<"] = "<<resourceDefinition->protocol);
538     }
539   }
540
541   // Note: a ResourceDefinition is used to create a batch configuration
542   // in the Launcher. This operation is done at Launcher startup from
543   // the configuration file CatalogResources.xml provided by the
544   // SALOME application.
545   // In the code instructions, you just have to choose a resource
546   // configuration by its name and then define the ResourceParameters
547   // that specify additionnal properties for a specific job submission
548   // (use the attribute resource_required of the JobParameters).
549
550   return resourceNames;
551 }
552
553
554 //
555 // ==========================================================================
556 // Factory services
557 // ==========================================================================
558 //
559 extern "C"
560 {
561   PortableServer::ObjectId * MeshJobManagerEngine_factory( CORBA::ORB_ptr orb,
562                                                            PortableServer::POA_ptr poa,
563                                                            PortableServer::ObjectId * contId,
564                                                            const char *instanceName,
565                                                            const char *interfaceName)
566   {
567     MESSAGE("PortableServer::ObjectId * MeshJobManagerEngine_factory()");
568     MeshJobManager_i * myEngine = new MeshJobManager_i(orb, poa, contId, instanceName, interfaceName);
569     return myEngine->getId() ;
570   }
571 }