]> SALOME platform Git repositories - modules/kernel.git/blob - src/ResourcesManager/ResourcesManager.cxx
Salome HOME
Extension to the case where APPLI points at a salome launcher file
[modules/kernel.git] / src / ResourcesManager / ResourcesManager.cxx
1 // Copyright (C) 2007-2019  CEA/DEN, EDF R&D, 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 "ResourcesManager.hxx" 
24 #include "SALOME_ResourcesCatalog_Handler.hxx"
25 #include <Basics_Utils.hxx>
26 #include <fstream>
27 #include <iostream>
28 #include <sstream>
29 #include <string.h>
30 #include <map>
31 #include <list>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #ifdef WIN32
35 #else
36 #include <unistd.h>
37 #endif
38 #include <libxml/parser.h>
39
40 #include <algorithm>
41
42 #define MAX_SIZE_FOR_HOSTNAME 256;
43
44 using namespace std;
45
46 const string ResourcesManager_cpp::DEFAULT_RESOURCE_NAME = "localhost";
47
48 static LoadRateManagerFirst first;
49 static LoadRateManagerCycl cycl;
50 static LoadRateManagerAltCycl altcycl;
51
52 resourceParams::resourceParams()
53 : can_launch_batch_jobs(false),
54   can_run_containers(false),
55   nb_proc(-1),
56   nb_node(0),
57   nb_proc_per_node(-1),
58   cpu_clock(-1),
59   mem_mb(-1)
60 {
61 }
62
63 //=============================================================================
64 /*!
65  * just for test
66  */ 
67 //=============================================================================
68
69 ResourcesManager_cpp::
70 ResourcesManager_cpp(const char *xmlFilePath)
71 {
72   _path_resources.push_back(xmlFilePath);
73 #if defined(_DEBUG_) || defined(_DEBUG)
74   std::cerr << "ResourcesManager_cpp constructor" << std::endl;
75 #endif
76   _resourceManagerMap["first"]=&first;
77   _resourceManagerMap["cycl"]=&cycl;
78   _resourceManagerMap["altcycl"]=&altcycl;
79   _resourceManagerMap["best"]=&altcycl;
80   _resourceManagerMap[""]=&altcycl;
81
82   AddDefaultResourceInCatalog();
83 }
84
85 //=============================================================================
86 /*!
87  *  Standard constructor, parse resource file.
88  *  - if ${APPLI} exists in environment,
89  *    look for ${HOME}/${APPLI}/CatalogResources.xml
90  *  - else look for default:
91  *    ${KERNEL_ROOT_DIR}/share/salome/resources/kernel/CatalogResources.xml
92  *  - parse XML resource file.
93  */ 
94 //=============================================================================
95
96 ResourcesManager_cpp::ResourcesManager_cpp() throw(ResourcesException)
97 {
98   RES_MESSAGE("ResourcesManager_cpp constructor");
99
100   _resourceManagerMap["first"]=&first;
101   _resourceManagerMap["cycl"]=&cycl;
102   _resourceManagerMap["altcycl"]=&altcycl;
103   _resourceManagerMap["best"]=&altcycl;
104   _resourceManagerMap[""]=&altcycl;
105
106   AddDefaultResourceInCatalog();
107
108   bool default_catalog_resource = true;
109   if (getenv("USER_CATALOG_RESOURCES_FILE") != 0)
110   {
111     default_catalog_resource = false;
112     std::string user_file("");
113     user_file = getenv("USER_CATALOG_RESOURCES_FILE");
114     std::ifstream ifile(user_file.c_str(), std::ifstream::in );
115     if (ifile) {
116       // The file exists, and is open for input
117       _path_resources.push_back(user_file);
118     }
119     else {
120       default_catalog_resource = false;
121       RES_INFOS("Warning: USER_CATALOG_RESOURCES_FILE is set and file cannot be found.")
122       RES_INFOS("Warning: That's why we try to create a new one.")
123       std::ofstream user_catalog_file;
124       user_catalog_file.open(user_file.c_str());
125       if (user_catalog_file.fail())
126       {
127         RES_INFOS("Error: cannot write in the user catalog resources files");
128         RES_INFOS("Error: using default CatalogResources.xml file");
129         default_catalog_resource = true;
130       }
131       else
132       {
133         user_catalog_file << "<!-- File created by SALOME -->" << std::endl;
134         user_catalog_file << "<!DOCTYPE ResourcesCatalog>" << std::endl;
135         user_catalog_file << "<resources>" << std::endl;
136         user_catalog_file << "   <machine name=\"localhost\" hostname=\"localhost\" />" << std::endl;
137         user_catalog_file << "</resources>" << std::endl;
138         user_catalog_file.close();
139       }
140     }
141   }
142   if (default_catalog_resource)
143   {
144     std::string default_file("");
145     if (getenv("APPLI") != 0)
146     {
147       default_file += getenv("HOME");
148       default_file += "/";
149       default_file += getenv("APPLI");
150       default_file += "/CatalogResources.xml";
151       std::ifstream ifile(default_file.c_str(), std::ifstream::in );
152       if (ifile) {
153         // The file exists, and is open for input
154         _path_resources.push_back(default_file);
155         default_catalog_resource=false;
156       }
157     }
158   }
159   if (default_catalog_resource)
160   {
161     std::string default_file("");
162     if(!getenv("KERNEL_ROOT_DIR"))
163       throw ResourcesException("you must define KERNEL_ROOT_DIR environment variable!! -> cannot load a CatalogResources.xml");
164     default_file = getenv("KERNEL_ROOT_DIR");
165     default_file += "/share/salome/resources/kernel/CatalogResources.xml";
166     _path_resources.push_back(default_file);
167   }
168
169   ParseXmlFiles();
170   RES_MESSAGE("ResourcesManager_cpp constructor end");
171 }
172
173 //=============================================================================
174 /*!
175  *  Standard Destructor
176  */ 
177 //=============================================================================
178
179 ResourcesManager_cpp::~ResourcesManager_cpp()
180 {
181   RES_MESSAGE("ResourcesManager_cpp destructor");
182 }
183
184 //=============================================================================
185 //! get the list of resource names fitting constraints given by params
186 /*!
187  * Steps:
188  * 1: Restrict list with resourceList if defined
189  * 2: If name is defined -> check resource list
190  * 3: If not 2:, if hostname is defined -> check resource list
191  * 4: If not 3:, sort resource with nb_proc, etc...
192  * 5: In all cases remove resource that does not correspond with OS
193  * 6: And remove resource with componentList - if list is empty ignored it...
194  */ 
195 //=============================================================================
196
197 std::vector<std::string> 
198 ResourcesManager_cpp::GetFittingResources(const resourceParams& params) throw(ResourcesException)
199 {
200   RES_MESSAGE("[GetFittingResources] on computer " << Kernel_Utils::GetHostname().c_str());
201   RES_MESSAGE("[GetFittingResources] with resource name: " << params.name);
202   RES_MESSAGE("[GetFittingResources] with hostname: "<< params.hostname);
203
204   // Result
205   std::vector<std::string> vec;
206
207   // Parse Again CalatogResource File
208   ParseXmlFiles();
209
210   // Steps:
211   // 1: If name is defined -> check resource list
212   // 2: Restrict list with resourceList if defined
213   // 3: If not 2:, if hostname is defined -> check resource list
214   // 4: If not 3:, sort resource with nb_proc, etc...
215   // 5: In all cases remove resource that does not correspond with OS
216   // 6: And remove resource with componentList - if list is empty ignored it...
217
218   // Step 1
219   if (params.name != "")
220   {
221     RES_MESSAGE("[GetFittingResources] name parameter found !");
222     if (_resourcesList.find(params.name) != _resourcesList.end())
223     {
224       vec.push_back(params.name);
225       return vec;
226     }
227     else
228       RES_MESSAGE("[GetFittingResources] resource name was not found on resource list ! name requested was " << params.name);
229       std::string error("[GetFittingResources] resource name was not found on resource list ! name requested was " + params.name);
230       throw ResourcesException(error);
231   }
232
233   MapOfParserResourcesType local_resourcesList = _resourcesList;
234   // Step 2
235   if (params.resourceList.size() > 0)
236   {
237     RES_MESSAGE("[GetFittingResources] Restricted resource list found !");
238     local_resourcesList.clear();
239     std::vector<std::string>::size_type sz = params.resourceList.size();
240
241     for (unsigned int i=0; i < sz; i++)
242     {
243       if (_resourcesList.find(params.resourceList[i]) != _resourcesList.end())
244         local_resourcesList[params.resourceList[i]] = _resourcesList[params.resourceList[i]];
245     }
246   }
247
248   // Step 3
249   if (params.hostname != "")
250   {
251     RES_MESSAGE("[GetFittingResources] Entering in hostname case !");
252
253     std::string hostname = params.hostname;
254     if (hostname ==  "localhost")
255       hostname = Kernel_Utils::GetHostname().c_str();
256
257     std::map<std::string, ParserResourcesType>::const_iterator iter = _resourcesList.begin();
258     for (; iter != _resourcesList.end(); iter++)
259     {
260       if ((*iter).second.HostName == hostname)
261         vec.push_back((*iter).first);
262     }
263   }
264   // Step 4
265   else
266   {
267     // --- Search for available resources sorted by priority
268     MapOfParserResourcesType_it i = local_resourcesList.begin();
269     for (; i != local_resourcesList.end(); ++i)
270       vec.push_back(i->first);
271
272     // --- set wanted parameters
273     ResourceDataToSort::_nbOfProcWanted = params.nb_proc;
274     ResourceDataToSort::_nbOfNodesWanted = params.nb_node;
275     ResourceDataToSort::_nbOfProcPerNodeWanted = params.nb_proc_per_node;
276     ResourceDataToSort::_CPUFreqMHzWanted = params.cpu_clock;
277     ResourceDataToSort::_memInMBWanted = params.mem_mb;
278     // --- end of set
279         
280     // Sort
281     std::list<ResourceDataToSort> li;
282     std::vector<std::string>::iterator iter = vec.begin();
283     for (; iter != vec.end(); iter++)
284       li.push_back(local_resourcesList[(*iter)].DataForSort);
285     li.sort();
286
287     vec.clear();
288     for (std::list<ResourceDataToSort>::iterator iter2 = li.begin(); iter2 != li.end(); iter2++)
289       vec.push_back((*iter2)._Name);
290   }
291
292   // Step 5
293   SelectOnlyResourcesWithOS(vec, params.OS.c_str());
294
295   // Step 6
296   std::vector<std::string> vec_save(vec);
297   KeepOnlyResourcesWithComponent(vec, params.componentList);
298   if (vec.size() == 0)
299     vec = vec_save;
300
301   // Step 7 : Filter on possible usage
302   vector<string> prev_list(vec);
303   vec.clear();
304   for (vector<string>::iterator iter = prev_list.begin() ; iter != prev_list.end() ; iter++)
305   {
306     MapOfParserResourcesType::const_iterator it = _resourcesList.find(*iter);
307     if (it != _resourcesList.end() &&
308         (!params.can_launch_batch_jobs || it->second.can_launch_batch_jobs) &&
309         (!params.can_run_containers || it->second.can_run_containers))
310       vec.push_back(*iter);
311   }
312
313   // End
314   // Send an exception if return list is empty...
315   if (vec.size() == 0)
316   {
317     std::string error("[GetFittingResources] ResourcesManager doesn't find any resource that fits to your parameters");
318     throw ResourcesException(error);
319   }
320
321   return vec;
322 }
323
324 //=============================================================================
325 /*!
326  *  add an entry in the resources catalog xml file.
327  */ 
328 //=============================================================================
329
330 void
331 ResourcesManager_cpp::AddResourceInCatalog(const ParserResourcesType & new_resource)
332 {
333   if (new_resource.Name == DEFAULT_RESOURCE_NAME){
334     ParserResourcesType default_resource = _resourcesList[DEFAULT_RESOURCE_NAME];
335     // some of the properties of the default resource shouldn't be modified
336     std::string check;
337     if( default_resource.HostName != new_resource.HostName)
338       check += "The Hostname property of the default resource can not be modified.\n";
339     if( default_resource.AppliPath != new_resource.AppliPath)
340       check += "The Applipath property of the default resource can not be modified.\n";
341     if( !new_resource.can_run_containers)
342       check += "The default resource should be able to run containers.\n";
343     if( !new_resource.can_launch_batch_jobs)
344       check += "The default resource should be able to launch batch jobs.\n";
345     if( default_resource.Protocol != new_resource.Protocol)
346       check += "The Protocol property of the default resource can not be modified.\n";
347     if(!check.empty())
348       throw ResourcesException(check);
349   }
350   // TODO - Add minimal check
351   _resourcesList[new_resource.Name] = new_resource;
352 }
353
354 //=============================================================================
355 /*!
356  *  Deletes a resource from the catalog
357  */ 
358 //=============================================================================
359
360 void ResourcesManager_cpp::DeleteResourceInCatalog(const char * name)
361 {
362   if (DEFAULT_RESOURCE_NAME == name){
363     std::string error("Cannot delete default local resource \"" + DEFAULT_RESOURCE_NAME + "\"");
364     throw ResourcesException(error);
365   }
366   MapOfParserResourcesType_it it = _resourcesList.find(name);
367   if (it != _resourcesList.end())
368     _resourcesList.erase(name);
369   else
370     RES_INFOS("You try to delete a resource that does not exist... : " << name);
371 }
372
373 //=============================================================================
374 /*!
375  *  write the current data in memory in file.
376  */ 
377 //=============================================================================
378
379 void ResourcesManager_cpp::WriteInXmlFile(std::string xml_file)
380 {
381   RES_MESSAGE("WriteInXmlFile : start");
382
383   MapOfParserResourcesType resourceListToSave(_resourcesList);
384   if (resourceListToSave.empty())
385   {
386     RES_MESSAGE("WriteInXmlFile: nothing to do, no resource to save!");
387     return;
388   }
389
390   if (xml_file == "")
391   {
392     _path_resources_it = _path_resources.begin();
393     xml_file = *_path_resources_it;
394   }
395
396   const char* aFilePath = xml_file.c_str();
397   FILE* aFile = fopen(aFilePath, "w");
398
399   if (aFile == NULL)
400   {
401     std::cerr << "Error opening file in WriteInXmlFile : " << xml_file << std::endl;
402     return;
403   }
404   
405   xmlDocPtr aDoc = xmlNewDoc(BAD_CAST "1.0");
406   xmlNewDocComment(aDoc, BAD_CAST "ResourcesCatalog");
407
408   SALOME_ResourcesCatalog_Handler* handler =
409     new SALOME_ResourcesCatalog_Handler(resourceListToSave);
410   handler->PrepareDocToXmlFile(aDoc);
411   delete handler;
412
413   int isOk = xmlSaveFormatFile(aFilePath, aDoc, 1);
414   if (!isOk) 
415      std::cerr << "Error while XML file saving : " << xml_file << std::endl;
416   
417   // Free the document
418   xmlFreeDoc(aDoc);
419   fclose(aFile);
420   RES_MESSAGE("WriteInXmlFile : WRITING DONE!");
421 }
422
423 //=============================================================================
424 /*!
425  *  parse the data type catalog
426  */ 
427 //=============================================================================
428
429 const MapOfParserResourcesType& ResourcesManager_cpp::ParseXmlFiles()
430 {
431   // Parse file only if its modification time is greater than lasttime (last registered modification time)
432   bool to_parse = false;
433   for(_path_resources_it = _path_resources.begin(); _path_resources_it != _path_resources.end(); ++_path_resources_it)
434   {
435     struct stat statinfo;
436     int result = stat((*_path_resources_it).c_str(), &statinfo);
437     if (result < 0)
438     {
439       RES_MESSAGE("Resource file " << *_path_resources_it << " does not exist");
440       return _resourcesList;
441     }
442
443     if(_lasttime == 0 || statinfo.st_mtime > _lasttime)
444     {
445       to_parse = true;
446       _lasttime = statinfo.st_mtime;
447     }
448   }
449
450   if (to_parse)
451   {
452     _resourcesList.clear();
453     AddDefaultResourceInCatalog();
454     // On parse tous les fichiers
455     for(_path_resources_it = _path_resources.begin(); _path_resources_it != _path_resources.end(); ++_path_resources_it)
456     {
457       MapOfParserResourcesType _resourcesList_tmp;
458       MapOfParserResourcesType _resourcesBatchList_tmp;
459       SALOME_ResourcesCatalog_Handler *handler( new SALOME_ResourcesCatalog_Handler(_resourcesList_tmp) );
460       const char *aFilePath( (*_path_resources_it).c_str() );
461       FILE* aFile = fopen(aFilePath, "r");
462
463       if (aFile != NULL)
464       {
465         xmlDocPtr aDoc = xmlReadFile(aFilePath, NULL, 0);
466         if (aDoc != NULL)
467         {
468           handler->ProcessXmlDocument(aDoc);
469
470           // adding new resources to the file
471           for (MapOfParserResourcesType_it i = _resourcesList_tmp.begin(); i != _resourcesList_tmp.end(); ++i)
472           {
473             MapOfParserResourcesType_it j = _resourcesList.find(i->first);
474             if (i->second.HostName == DEFAULT_RESOURCE_NAME || i->second.HostName == Kernel_Utils::GetHostname())
475             {
476               MapOfParserResourcesType_it it0(_resourcesList.find(DEFAULT_RESOURCE_NAME));
477               if(it0!=_resourcesList.end())
478                 {
479                   ParserResourcesType& localhostElt((*it0).second);
480                   localhostElt.DataForSort._nbOfNodes=(*i).second.DataForSort._nbOfNodes;
481                   localhostElt.DataForSort._nbOfProcPerNode=(*i).second.DataForSort._nbOfProcPerNode;
482                   localhostElt.DataForSort._CPUFreqMHz=(*i).second.DataForSort._CPUFreqMHz;
483                   localhostElt.DataForSort._memInMB=(*i).second.DataForSort._memInMB;
484                 }
485               RES_MESSAGE("Resource " << i->first << " is not added because it is the same "
486                           "machine as default local resource \"" << DEFAULT_RESOURCE_NAME << "\"");
487             }
488             else if (j != _resourcesList.end())
489             {
490               cerr << "ParseXmlFiles Warning, two resources with the same name were found, "
491                       "taking the first declaration : " << i->first << endl;
492             }
493             else
494             {
495               _resourcesList[i->first] = i->second;
496             }
497           }
498         }
499         else
500           std::cerr << "ResourcesManager_cpp: could not parse file " << aFilePath << std::endl;
501         // Free the document
502         xmlFreeDoc(aDoc);
503         fclose(aFile);
504       }
505       else
506         std::cerr << "ResourcesManager_cpp: file " << aFilePath << " is not readable." << std::endl;
507
508       delete handler;
509     }
510   }
511   return _resourcesList;
512 }
513
514 //=============================================================================
515 /*!
516  *   consult the content of the list
517  */ 
518 //=============================================================================
519
520 const MapOfParserResourcesType& ResourcesManager_cpp::GetList() const
521 {
522   return _resourcesList;
523 }
524
525 //! threadsafe
526 std::string ResourcesManager_cpp::Find(const std::string& policy, const std::vector<std::string>& listOfResources) const
527 {
528   std::map<std::string , LoadRateManager*>::const_iterator it(_resourceManagerMap.find(policy));
529   if(it==_resourceManagerMap.end())
530         {
531           it=_resourceManagerMap.find("");
532           return ((*it).second)->Find(listOfResources, _resourcesList);
533         }
534   return ((*it).second)->Find(listOfResources, _resourcesList);
535 }
536
537 //=============================================================================
538 /*!
539  *  Gives a sublist of resources with matching OS.
540  *  If parameter OS is empty, gives the complete list of resources
541  */ 
542 //=============================================================================
543 void 
544 ResourcesManager_cpp::SelectOnlyResourcesWithOS(std::vector<std::string>& resources, std::string OS)
545 {
546   if (OS != "")
547   {
548     // a computer list is given : take only resources with OS on those computers
549     std::vector<std::string> vec_tmp = resources;
550     resources.clear();
551     std::vector<std::string>::iterator iter = vec_tmp.begin();
552     for (; iter != vec_tmp.end(); iter++)
553     {
554       MapOfParserResourcesType::const_iterator it = _resourcesList.find(*iter);
555       if(it != _resourcesList.end())
556         if ( (*it).second.OS == OS)
557           resources.push_back(*iter);
558     }
559   }
560 }
561
562
563 //=============================================================================
564 /*!
565  *  Gives a sublist of machines on which the component is known.
566  */ 
567 //=============================================================================
568 void 
569 ResourcesManager_cpp::KeepOnlyResourcesWithComponent(std::vector<std::string>& resources, 
570                                                      const std::vector<std::string>& componentList)
571 {
572   std::vector<std::string> kept_resources;
573
574   std::vector<std::string>::iterator iter = resources.begin();
575   for (; iter != resources.end(); iter++)
576   {
577     const std::vector<std::string>& mapOfComponentsOfCurrentHost = _resourcesList[*iter].ComponentsList;
578
579     bool erasedHost = false;
580     if( mapOfComponentsOfCurrentHost.size() > 0 )
581     {
582       for(unsigned int i=0; i<componentList.size(); i++)
583       {
584         std::vector<std::string>::const_iterator itt = find(mapOfComponentsOfCurrentHost.begin(),
585                                                             mapOfComponentsOfCurrentHost.end(),
586                                                             componentList[i]);
587         if (itt == mapOfComponentsOfCurrentHost.end())
588         {
589           erasedHost = true;
590           break;
591         }
592       }
593     }
594     if(!erasedHost)
595       kept_resources.push_back(*iter);
596   }
597   resources=kept_resources;
598 }
599
600 //! thread safe
601 ParserResourcesType ResourcesManager_cpp::GetResourcesDescr(const std::string & name) const
602 {
603   MapOfParserResourcesType::const_iterator it(_resourcesList.find(name));
604   if (it != _resourcesList.end())
605     return (*it).second;
606   else
607   {
608     std::string error("[GetResourcesDescr] Resource does not exist: ");
609     error += name;
610     throw ResourcesException(error);
611   }
612 }
613
614 void ResourcesManager_cpp::AddDefaultResourceInCatalog()
615 {
616   ParserResourcesType resource;
617   resource.Name = DEFAULT_RESOURCE_NAME;
618   // We can't use "localhost" for parameter hostname because the containers are registered in the
619   // naming service with the real hostname, not "localhost"
620   resource.HostName = Kernel_Utils::GetHostname();
621   resource.DataForSort._Name = DEFAULT_RESOURCE_NAME;
622   resource.Protocol = sh;
623   resource.Batch = none;
624 #ifndef WIN32
625   if (getenv("HOME") != NULL && getenv("APPLI") != NULL)
626   {
627     resource.AppliPath = string(getenv("HOME")) + "/" + getenv("APPLI");
628   }
629   string tmpdir = "/tmp";
630   if (getenv("TMPDIR") != NULL)
631     tmpdir = getenv("TMPDIR");
632   resource.working_directory = tmpdir + "/salome_localres_workdir";
633   if (getenv("USER") != NULL)
634     resource.working_directory += string("_") + getenv("USER");
635 #else
636   if (getenv("USERPROFILE") != NULL && getenv("APPLI") != NULL)
637   {
638     resource.AppliPath = string(getenv("USERPROFILE")) + "\\" + getenv("APPLI");
639   }
640   string tmpdir = "C:\\tmp";
641   if (getenv("TEMP") != NULL)
642     tmpdir = getenv("TEMP");
643   resource.working_directory = tmpdir + "\\salome_localres_workdir";
644   if (getenv("USERNAME") != NULL)
645     resource.working_directory += string("_") + getenv("USERNAME");
646 #endif
647   resource.can_launch_batch_jobs = true;
648   resource.can_run_containers = true;
649   _resourcesList[resource.Name] = resource;
650 }