Salome HOME
updated copyright message
[modules/kernel.git] / src / Launcher / Launcher_XML_Persistence.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 #include <libxml/parser.h>
24
25 #include "Launcher_XML_Persistence.hxx"
26 #include "Launcher_Job_Command.hxx"
27 #include "Launcher_Job_CommandSALOME.hxx"
28 #include "Launcher_Job_YACSFile.hxx"
29 #include "Launcher_Job_PythonSALOME.hxx"
30
31 using namespace std;
32
33 namespace Launcher
34 {
35
36 list<Job *>
37 XML_Persistence::loadJobs(const char* jobs_file)
38 {
39   // Step 1: check jobs_file read access
40   FILE* xml_file = fopen(jobs_file, "r");
41   if (xml_file == NULL)
42   {
43     std::string error = "Error opening jobs_file in SALOME_Launcher::loadJobs: " + std::string(jobs_file);
44     LAUNCHER_INFOS(error);
45     throw LauncherException(error);
46   }
47
48   // Step 2: read xml file
49   xmlDocPtr doc = xmlReadFile(jobs_file, NULL, 0);
50   if (doc == NULL)
51   {
52     std::string error = "Error in xmlReadFile in SALOME_Launcher::loadJobs, could not parse file: " + std::string(jobs_file);
53     LAUNCHER_INFOS(error);
54     fclose(xml_file);
55     throw LauncherException(error);
56   }
57
58   // Step 3: Find jobs
59   list<Job *> jobs_list;
60   xmlNodePtr root_node = xmlDocGetRootElement(doc);
61   if (xmlStrToString(root_node->name) == "jobs")
62   {
63     xmlNodePtr xmlCurrentNode = root_node->xmlChildrenNode;
64     while(xmlCurrentNode != NULL)
65     {
66       if (xmlStrToString(xmlCurrentNode->name) == "job")
67       {
68         LAUNCHER_INFOS("A job is found");
69         Job * new_job = createJobFromXmlNode(xmlCurrentNode);
70         jobs_list.push_back(new_job);
71       }
72       xmlCurrentNode = xmlCurrentNode->next;
73     }
74   }
75   else
76   {
77     xmlFreeDoc(doc);
78     fclose(xml_file);
79     std::string error = "Error in xml file, could not find root_node named jobs: " + std::string(jobs_file);
80     LAUNCHER_INFOS(error);
81     throw LauncherException(error);
82   }
83
84   // Clean
85   xmlFreeDoc(doc);
86   fclose(xml_file);
87
88   return jobs_list;
89 }
90
91 void
92 XML_Persistence::saveJobs(const char* jobs_file, const list<const Job *> & jobs_list)
93 {
94   // Step 1: check jobs_file write access
95   FILE* xml_file = fopen(jobs_file, "w");
96   if (xml_file == NULL)
97   {
98     std::string error = "Error opening jobs_file in SALOME_Launcher::saveJobs: " + std::string(jobs_file);
99     LAUNCHER_INFOS(error);
100     throw LauncherException(error);
101   }
102
103   // Step 2: First lines
104   xmlKeepBlanksDefault(0);
105   xmlDocPtr doc = xmlNewDoc(xmlCharStrdup("1.0"));
106   xmlNodePtr root_node = xmlNewNode(NULL, xmlCharStrdup("jobs"));
107   xmlDocSetRootElement(doc, root_node);
108   xmlNodePtr doc_comment = xmlNewDocComment(doc, xmlCharStrdup("SALOME Launcher save jobs file"));
109   xmlAddPrevSibling(root_node, doc_comment);
110
111   // Step 3: For each job write it on the xml document
112   // We could put a mutex but are not foing to do that currently
113   list<const Job *>::const_iterator it_job;
114   for (it_job = jobs_list.begin(); it_job != jobs_list.end(); it_job++)
115   {
116     addJobToXmlDocument(root_node, **it_job);
117   }
118
119   // Final step: write file
120   int isOk = xmlSaveFormatFile(jobs_file, doc, 1);
121   if (!isOk)
122   {
123     std::string error = "Error during xml file saving in SALOME_Launcher::saveJobs: " + std::string(jobs_file);
124     LAUNCHER_INFOS(error);
125     xmlFreeDoc(doc);
126     fclose(xml_file);
127     throw LauncherException(error);
128   }
129
130   // Clean
131   xmlFreeDoc(doc);
132   fclose(xml_file);
133   LAUNCHER_MESSAGE("SALOME_Launcher::saveJobs : WRITING DONE!");
134 }
135
136 void
137 XML_Persistence::addJobToXmlDocument(xmlNodePtr root_node, const Job & job)
138 {
139   // Begin job
140   xmlNodePtr job_node = addNode(root_node, "job", "");
141   addAttr(job_node, "type", job.getJobType());
142   addAttr(job_node, "name", job.getJobName());
143
144   // Add user part
145   xmlNodePtr node = addNode(job_node, "user_part", "");
146
147   addNode(node, "job_file", job.getJobFile());
148   if (!job.getEnvFile().empty())
149     addNode(node, "env_file", job.getEnvFile());
150   if (!job.getWorkDirectory().empty())
151     addNode(node, "work_directory", job.getWorkDirectory());
152   if (!job.getLocalDirectory().empty())
153     addNode(node, "local_directory", job.getLocalDirectory());
154   if (!job.getResultDirectory().empty())
155     addNode(node, "result_directory", job.getResultDirectory());
156   if (!job.getPreCommand().empty())
157     addNode(node, "pre_command", job.getPreCommand());
158
159   // Parameters for COORM
160   if (!job.getLauncherFile().empty())
161     addNode(node, "launcher_file", job.getLauncherFile());
162   if (!job.getLauncherArgs().empty())
163     addNode(node, "launcher_args", job.getLauncherArgs());
164
165   // Files
166   if ( ! (job.get_in_files().empty() && job.get_out_files().empty()) )
167   {
168     xmlNodePtr files_node = addNode(node, "files", "");
169     list<string> in_files = job.get_in_files();
170     list<string> out_files = job.get_out_files();
171     for(list<string>::iterator it = in_files.begin(); it != in_files.end(); it++)
172       addNode(files_node, "in_file", *it);
173     for(list<string>::iterator it = out_files.begin(); it != out_files.end(); it++)
174       addNode(files_node, "out_file", *it);
175   }
176
177   // Resource part
178   resourceParams resource_params = job.getResourceRequiredParams();
179   xmlNodePtr res_node = addNode(node, "resource_params", "");
180   addNode(res_node, "name", resource_params.name);
181   if (!resource_params.hostname.empty())
182     addNode(res_node, "hostname", resource_params.hostname);
183   if (!resource_params.OS.empty())
184     addNode(res_node, "OS", resource_params.OS);
185   if (resource_params.nb_proc > 0)
186     addNumericalNode(res_node, "nb_proc", resource_params.nb_proc);
187   if (resource_params.nb_node > 0)
188     addNumericalNode(res_node, "nb_node", resource_params.nb_node);
189   if (resource_params.nb_proc_per_node > 0)
190     addNumericalNode(res_node, "nb_proc_per_node", resource_params.nb_proc_per_node);
191   if (resource_params.cpu_clock > 0)
192     addNumericalNode(res_node, "cpu_clock", resource_params.cpu_clock);
193   if (resource_params.mem_mb > 0)
194     addNumericalNode(res_node, "mem_mb", resource_params.mem_mb);
195
196   if (!job.getMaximumDuration().empty())
197     addNode(node, "maximum_duration", job.getMaximumDuration());
198   if (!job.getQueue().empty())
199     addNode(node, "queue", job.getQueue());
200   if (!job.getPartition().empty())
201     addNode(node, "partition", job.getPartition());
202   if (job.getExclusive())
203     addNode(node, "exclusive", job.getExclusiveStr());
204   if (job.getMemPerCpu() > 0)
205     addNumericalNode(res_node, "mem_per_cpu", job.getMemPerCpu());
206   if (!job.getWCKey().empty())
207     addNode(node, "wckey", job.getWCKey());
208   if (!job.getExtraParams().empty())
209     addNode(node, "extra_params", job.getExtraParams());
210
211   // Specific parameters part
212   map<string, string> specific_parameters = job.getSpecificParameters();
213   if (!specific_parameters.empty())
214   {
215     xmlNodePtr specific_parameters_node = addNode(node, "specific_parameters", "");
216     for(map<string, string>::iterator it = specific_parameters.begin();
217         it != specific_parameters.end();
218         it++)
219     {
220       xmlNodePtr specific_parameter_node = addNode(specific_parameters_node,
221                                                    "specific_parameter", "");
222       addNode(specific_parameter_node, "name", it->first);
223       addNode(specific_parameter_node, "value", it->second);
224     }
225   }
226
227   // Run part
228   xmlNodePtr run_node = addNode(job_node, "run_part", "");
229   addNode(run_node, "job_state", job.getState());
230   addNode(run_node, "job_reference", job.getReference());
231 }
232
233 Job *
234 XML_Persistence::createJobFromXmlNode(xmlNodePtr job_node)
235 {
236   Launcher::Job * new_job;
237
238   // Begin Job
239   string job_name = getAttrValue(job_node, "name");
240   if (job_name.empty())
241     throw LauncherException("Invalid job: name is not defined");
242   string job_type = getAttrValue(job_node, "type");
243   if (job_type.empty())
244     throw LauncherException(string("Invalid job \"") + job_name + "\": type is not defined");
245   if (job_type == Launcher::Job_Command::TYPE_NAME)
246     new_job = new Launcher::Job_Command();
247   else if (job_type == Launcher::Job_CommandSALOME::TYPE_NAME)
248     new_job = new Launcher::Job_CommandSALOME();
249   else if (job_type == Launcher::Job_YACSFile::TYPE_NAME)
250     new_job = new Launcher::Job_YACSFile();
251   else if (job_type == Launcher::Job_PythonSALOME::TYPE_NAME)
252     new_job = new Launcher::Job_PythonSALOME();
253   else
254   {
255     string error = string("Invalid job \"") + job_name + "\": invalid type \"" + job_type + "\"";
256     throw LauncherException(error);
257   }
258   new_job->setJobName(job_name);
259
260   try
261   {
262     xmlNodePtr current_node = xmlFirstElementChild(job_node);
263     bool user_ok = false;
264     bool run_ok = false;
265     while (current_node != NULL)
266     {
267       string node_name = xmlStrToString(current_node->name);
268       if (node_name == "user_part")
269       {
270         parseUserNode(new_job, current_node);
271         user_ok = true;
272       }
273       else if (node_name == "run_part")
274       {
275         parseRunNode(new_job, current_node);
276         run_ok = true;
277       }
278       else
279         throw LauncherException(string("invalid node \"") + node_name + "\"");
280       current_node = xmlNextElementSibling(current_node);
281     }
282     if (!user_ok) throw LauncherException("missing user part");
283     if (!run_ok) throw LauncherException("missing run part");
284   }
285   catch (const LauncherException & exc)
286   {
287     delete new_job;
288     string error = string("Invalid job \"") + job_name + "\": " + exc.msg;
289     throw LauncherException(error);
290   }
291
292   return new_job;
293 }
294
295 void
296 XML_Persistence::parseUserNode(Job * new_job, xmlNodePtr user_node)
297 {
298   xmlNodePtr current_node = xmlFirstElementChild(user_node);
299   bool job_file_ok = false;
300   while (current_node != NULL)
301   {
302     string node_name = xmlStrToString(current_node->name);
303     if (node_name == "job_file")
304     {
305       new_job->setJobFile(getNodeContent(current_node));
306       job_file_ok = true;
307     }
308     else if (node_name == "env_file")
309       new_job->setEnvFile(getNodeContent(current_node));
310     else if (node_name == "pre_command")
311       new_job->setPreCommand(getNodeContent(current_node));
312     else if (node_name == "work_directory")
313       new_job->setWorkDirectory(getNodeContent(current_node));
314     else if (node_name == "local_directory")
315       new_job->setLocalDirectory(getNodeContent(current_node));
316     else if (node_name == "result_directory")
317       new_job->setResultDirectory(getNodeContent(current_node));
318     else if (node_name == "launcher_file") // For COORM
319       new_job->setLauncherFile(getNodeContent(current_node));
320     else if (node_name == "launcher_args") // For COORM
321       new_job->setLauncherArgs(getNodeContent(current_node));
322     else if (node_name == "files")
323     {
324       // Get in and out files
325       xmlNodePtr file_node = xmlFirstElementChild(current_node);
326       while (file_node != NULL)
327       {
328         string file_node_name = xmlStrToString(file_node->name);
329         if (file_node_name == "in_file")
330           new_job->add_in_file(getNodeContent(file_node));
331         else if (file_node_name == "out_file")
332           new_job->add_out_file(getNodeContent(file_node));
333         else
334           throw LauncherException(string("invalid node \"") + file_node_name + "\"");
335         file_node = xmlNextElementSibling(file_node);
336       }
337     }
338     else if (node_name == "resource_params")
339       parseResourceNode(new_job, current_node);
340     else if (node_name == "maximum_duration")
341       new_job->setMaximumDuration(getNodeContent(current_node));
342     else if (node_name == "queue")
343       new_job->setQueue(getNodeContent(current_node));
344     else if (node_name == "partition")
345       new_job->setPartition(getNodeContent(current_node));
346     else if (node_name == "exclusive")
347       new_job->setExclusiveStr(getNodeContent(current_node));
348     else if (node_name == "mem_per_cpu")
349       new_job->setMemPerCpu(getNumericalNodeContent<unsigned long>(current_node));
350     else if (node_name == "wckey")
351       new_job->setWCKey(getNodeContent(current_node));
352     else if (node_name == "extra_params")
353       new_job->setExtraParams(getNodeContent(current_node));
354     else if (node_name == "specific_parameters")
355     {
356       // Get specific parameters
357       xmlNodePtr parameter_node = xmlFirstElementChild(current_node);
358       while (parameter_node != NULL)
359       {
360         string parameter_node_name = xmlStrToString(parameter_node->name);
361         if (parameter_node_name == "specific_parameter")
362         {
363           xmlNodePtr inparam_node = xmlFirstElementChild(parameter_node);
364           string name;
365           string value;
366           while (inparam_node != NULL)
367           {
368             string inparam_node_name = xmlStrToString(inparam_node->name);
369             if (inparam_node_name == "name")
370               name = getNodeContent(inparam_node);
371             else if (inparam_node_name == "value")
372               value = getNodeContent(inparam_node);
373             else
374               throw LauncherException(string("invalid node \"") + inparam_node_name + "\"");
375             inparam_node = xmlNextElementSibling(inparam_node);
376           }
377           if (name.empty()) throw LauncherException("missing parameter name");
378           if (value.empty()) throw LauncherException("missing parameter value");
379           new_job->addSpecificParameter(name, value);
380         }
381         else
382           throw LauncherException(string("invalid node \"") + parameter_node_name + "\"");
383         parameter_node = xmlNextElementSibling(parameter_node);
384       }
385     }
386     else
387       throw LauncherException(string("invalid node \"") + node_name + "\"");
388     current_node = xmlNextElementSibling(current_node);
389   }
390   if (!job_file_ok) throw LauncherException("missing job file");
391 }
392
393 void
394 XML_Persistence::parseResourceNode(Job * new_job, xmlNodePtr res_node)
395 {
396   resourceParams p;
397   xmlNodePtr current_node = xmlFirstElementChild(res_node);
398   while (current_node != NULL)
399   {
400     string node_name = xmlStrToString(current_node->name);
401     if (node_name == "name")
402       p.name = getNodeContent(current_node);
403     else if (node_name == "hostname")
404       p.hostname = getNodeContent(current_node);
405     else if (node_name == "OS")
406       p.OS = getNodeContent(current_node);
407     else if (node_name == "nb_proc")
408       p.nb_proc = getNumericalNodeContent<long>(current_node);
409     else if (node_name == "nb_node")
410       p.nb_node = getNumericalNodeContent<long>(current_node);
411     else if (node_name == "nb_proc_per_node")
412       p.nb_proc_per_node = getNumericalNodeContent<long>(current_node);
413     else if (node_name == "cpu_clock")
414       p.cpu_clock = getNumericalNodeContent<long>(current_node);
415     else if (node_name == "mem_mb")
416       p.mem_mb = getNumericalNodeContent<long>(current_node);
417     else if (node_name == "mem_per_cpu")
418       new_job->setMemPerCpu(getNumericalNodeContent<long>(current_node));
419     else
420       throw LauncherException(string("invalid node \"") + node_name + "\"");
421     current_node = xmlNextElementSibling(current_node);
422   }
423   new_job->setResourceRequiredParams(p);
424 }
425
426 void
427 XML_Persistence::parseRunNode(Job * new_job, xmlNodePtr run_node)
428 {
429   xmlNodePtr current_node = xmlFirstElementChild(run_node);
430   while (current_node != NULL)
431   {
432     string node_name = xmlStrToString(current_node->name);
433     if (node_name == "job_state")
434       new_job->setState(getNodeContent(current_node));
435     else if (node_name == "resource_choosed_name")
436     {
437       // This parameter was present in older versions of Salome. Now we just silently ignore it.
438     }
439     else if (node_name == "job_reference")
440       new_job->setReference(getNodeContent(current_node));
441     else
442       throw LauncherException(string("invalid node \"") + node_name + "\"");
443     current_node = xmlNextElementSibling(current_node);
444   }
445 }
446
447 string
448 XML_Persistence::getAttrValue(xmlNodePtr node, const string & attrName)
449 {
450   string attrValue;
451   xmlChar * xmlAttrName = xmlCharStrdup(attrName.c_str());
452   xmlChar * xmlAttrValue = xmlGetProp(node, xmlAttrName);
453   if (xmlAttrValue != NULL) attrValue = (const char *)xmlAttrValue;
454   xmlFree(xmlAttrName);
455   xmlFree(xmlAttrValue);
456   return attrValue;
457 }
458
459 inline string
460 XML_Persistence::xmlStrToString(const xmlChar * xmlStr)
461 {
462   return string((const char *)xmlStr);
463 }
464
465 string
466 XML_Persistence::getNodeContent(xmlNodePtr node)
467 {
468   string nodeContent;
469   xmlChar * xmlStrContent = xmlNodeGetContent(node);
470   if (xmlStrContent != NULL) nodeContent = (const char *)xmlStrContent;
471   xmlFree(xmlStrContent);
472   return nodeContent;
473 }
474
475 template<typename T>
476 T
477 XML_Persistence::getNumericalNodeContent(xmlNodePtr node)
478 {
479   T result;
480   istringstream nodeContentStream(getNodeContent(node));
481   if (!(nodeContentStream >> result))
482     throw LauncherException(xmlStrToString(node->name) + " parameter is not correct");
483   return result;
484 }
485
486 xmlNodePtr
487 XML_Persistence::addNode(xmlNodePtr father, const string & name, const string & content)
488 {
489   xmlChar * xmlStrName = xmlCharStrdup(name.c_str());
490   xmlChar * xmlStrContent = NULL;
491   if (!content.empty())
492     xmlStrContent = xmlCharStrdup(content.c_str());
493   xmlNodePtr node = xmlNewChild(father, NULL, xmlStrName, xmlStrContent);
494   xmlFree(xmlStrName);
495   xmlFree(xmlStrContent);
496   return node;
497 }
498
499 template<typename T>
500 xmlNodePtr
501 XML_Persistence::addNumericalNode(xmlNodePtr father, const string & name, T content)
502 {
503   ostringstream nodeContentStream;
504   nodeContentStream << content;
505   return addNode(father, name, nodeContentStream.str());
506 }
507
508 void
509 XML_Persistence::addAttr(xmlNodePtr node, const string & name, const string & value)
510 {
511   xmlChar * xmlStrName = xmlCharStrdup(name.c_str());
512   xmlChar * xmlStrValue = xmlCharStrdup(value.c_str());
513   xmlNewProp(node, xmlStrName, xmlStrValue);
514   xmlFree(xmlStrName);
515   xmlFree(xmlStrValue);
516 }
517
518 Job*
519 XML_Persistence::createJobFromString(const std::string& jobDump)
520 {
521   xmlDocPtr doc;
522   doc = xmlReadMemory(jobDump.c_str(), (int)jobDump.length(), "noname.xml", NULL, 0); //TODO: conversion from size_t to int, possible loss of data
523   if (doc == NULL)
524   {
525     std::string error = "Error in xmlReadMemory in XML_Persistence::createJobFromString, could not parse string: " + jobDump;
526     LAUNCHER_INFOS(error);
527     throw LauncherException(error);
528   }
529
530   // Step 3: Find jobs
531   Job * result = NULL;
532   xmlNodePtr root_node = xmlDocGetRootElement(doc);
533   if (xmlStrToString(root_node->name) == "jobs")
534   {
535     xmlNodePtr xmlCurrentNode = root_node->xmlChildrenNode;
536     while(xmlCurrentNode != NULL && result == NULL)
537     {
538       if (xmlStrToString(xmlCurrentNode->name) == "job")
539       {
540         LAUNCHER_INFOS("A job is found");
541         result = createJobFromXmlNode(xmlCurrentNode);
542       }
543       xmlCurrentNode = xmlCurrentNode->next;
544     }
545   }
546   else
547   {
548     xmlFreeDoc(doc);
549     std::string error = "Error while parsing job dump: " + jobDump;
550     LAUNCHER_INFOS(error);
551     throw LauncherException(error);
552   }
553
554   // Clean
555   xmlFreeDoc(doc);
556   return result;
557 }
558
559 std::string
560 XML_Persistence::dumpJob(const Job& job)
561 {
562   // Initialization
563   xmlKeepBlanksDefault(0);
564   xmlDocPtr doc = xmlNewDoc(xmlCharStrdup("1.0"));
565   xmlNodePtr root_node = xmlNewNode(NULL, xmlCharStrdup("jobs"));
566   xmlDocSetRootElement(doc, root_node);
567   xmlNodePtr doc_comment = xmlNewDocComment(doc, xmlCharStrdup("SALOME Launcher job"));
568   xmlAddPrevSibling(root_node, doc_comment);
569
570   addJobToXmlDocument(root_node, job);
571
572   // Final step: write to result
573   xmlChar *xmlbuff;
574   int buffersize;
575   xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
576   std::string result;
577   if(buffersize > 0)
578     result = (const char*) xmlbuff;
579
580   // Clean
581   xmlFree(xmlbuff);
582   xmlFreeDoc(doc);
583   return result;
584 }
585
586 }