Skip to content

Commit d38fe56

Browse files
author
Matthieu Hog
committed
created plugin and environment node systems
1 parent 03bd518 commit d38fe56

18 files changed

+824
-60
lines changed

.bandit

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[tool.bandit]
2+
skips = [B101, B102, B105, B106, B107, B113, B202, B401, B402, B403, B404, B405, B406, B407, B408, B409, B410, B413, B307, B311, B507, B602, B603, B605, B607, B610, B611, B703]
3+
4+
[tool.bandit.any_other_function_with_shell_equals_true]
5+
no_shell = [
6+
"os.execl",
7+
"os.execle",
8+
"os.execlp",
9+
"os.execlpe",
10+
"os.execv",
11+
"os.execve",
12+
"os.execvp",
13+
"os.execvpe",
14+
"os.spawnl",
15+
"os.spawnle",
16+
"os.spawnlp",
17+
"os.spawnlpe",
18+
"os.spawnv",
19+
"os.spawnve",
20+
"os.spawnvp",
21+
"os.spawnvpe",
22+
"os.startfile"
23+
]
24+
shell = [
25+
"os.system",
26+
"os.popen",
27+
"os.popen2",
28+
"os.popen3",
29+
"os.popen4",
30+
"popen2.popen2",
31+
"popen2.popen3",
32+
"popen2.popen4",
33+
"popen2.Popen3",
34+
"popen2.Popen4",
35+
"commands.getoutput",
36+
"commands.getstatusoutput"
37+
]
38+
subprocess = [
39+
"subprocess.Popen",
40+
"subprocess.call",
41+
"subprocess.check_call",
42+
"subprocess.check_output"
43+
]

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ You can create custom nodes in python and make them available in Meshroom using
6666
In a standard precompiled version of Meshroom, you can also directly add custom nodes in `lib/meshroom/nodes`.
6767
To be recognized by Meshroom, a custom folder with nodes should be a Python module (an `__init__.py` file is needed).
6868

69+
### Plugins
70+
71+
Meshroom supports installing containerised plugins via Docker (with the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)) or [Anaconda](https://docs.anaconda.com/free/miniconda/index.html).
72+
73+
To do so, make sure docker or anaconda is installed properly and available from the command line.
74+
Then click on `File > Advanced > Install Plugin From URL` or `File > Advanced > Install Plugin From Local Folder` to begin the installation.
75+
76+
To learn more about using or creating plugins, check the explanations [here](meshroom/plugins/README.md).
6977

7078
## License
7179

meshroom/core/__init__.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,19 @@
2727
# make a UUID based on the host ID and current time
2828
sessionUid = str(uuid.uuid1())
2929

30-
cacheFolderName = 'MeshroomCache'
31-
defaultCacheFolder = os.environ.get('MESHROOM_CACHE', os.path.join(tempfile.gettempdir(), cacheFolderName))
3230
nodesDesc = {}
3331
submitters = {}
3432
pipelineTemplates = {}
3533

34+
#meshroom paths
35+
meshroomFolder = os.path.dirname(os.path.dirname(__file__))
36+
cacheFolderName = 'MeshroomCache'
37+
defaultCacheFolder = os.environ.get('MESHROOM_CACHE', os.path.join(tempfile.gettempdir(), cacheFolderName))
38+
39+
#plugin paths
40+
pluginsNodesFolder = os.path.join(meshroomFolder, "plugins")
41+
pluginsPipelinesFolder = os.path.join(meshroomFolder, "pipelines")
42+
pluginCatalogFile = os.path.join(meshroomFolder, "plugins", "catalog.json")
3643

3744
def hashValue(value):
3845
""" Hash 'value' using sha1. """
@@ -329,28 +336,25 @@ def loadPipelineTemplates(folder):
329336

330337

331338
def initNodes():
332-
meshroomFolder = os.path.dirname(os.path.dirname(__file__))
333339
additionalNodesPath = os.environ.get("MESHROOM_NODES_PATH", "").split(os.pathsep)
334340
# filter empty strings
335341
additionalNodesPath = [i for i in additionalNodesPath if i]
336-
nodesFolders = [os.path.join(meshroomFolder, 'nodes')] + additionalNodesPath
342+
nodesFolders = [os.path.join(meshroomFolder, 'nodes')] + additionalNodesPath + [pluginsNodesFolder]
337343
for f in nodesFolders:
338344
loadAllNodes(folder=f)
339345

340346

341347
def initSubmitters():
342-
meshroomFolder = os.path.dirname(os.path.dirname(__file__))
343348
subs = loadSubmitters(os.environ.get("MESHROOM_SUBMITTERS_PATH", meshroomFolder), 'submitters')
344349
for sub in subs:
345350
registerSubmitter(sub())
346351

347352

348353
def initPipelines():
349-
meshroomFolder = os.path.dirname(os.path.dirname(__file__))
350354
# Load pipeline templates: check in the default folder and any folder the user might have
351355
# added to the environment variable
352356
additionalPipelinesPath = os.environ.get("MESHROOM_PIPELINE_TEMPLATES_PATH", "").split(os.pathsep)
353357
additionalPipelinesPath = [i for i in additionalPipelinesPath if i]
354-
pipelineTemplatesFolders = [os.path.join(meshroomFolder, 'pipelines')] + additionalPipelinesPath
358+
pipelineTemplatesFolders = [os.path.join(meshroomFolder, 'pipelines')] + additionalPipelinesPath + [pluginsPipelinesFolder]
355359
for f in pipelineTemplatesFolders:
356360
loadPipelineTemplates(f)

meshroom/core/node.py

+58-36
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class Status(Enum):
5151
KILLED = 5
5252
SUCCESS = 6
5353
INPUT = 7 # Special status for input nodes
54+
BUILD = 8
55+
FIRST_RUN = 9
5456

5557

5658
class ExecMode(Enum):
@@ -380,16 +382,16 @@ def saveStatistics(self):
380382
renameWritingToFinalPath(statisticsFilepathWriting, statisticsFilepath)
381383

382384
def isAlreadySubmitted(self):
383-
return self._status.status in (Status.SUBMITTED, Status.RUNNING)
385+
return self._status.status in (Status.SUBMITTED, Status.RUNNING, Status.BUILD, Status.FIRST_RUN)
384386

385387
def isAlreadySubmittedOrFinished(self):
386-
return self._status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS)
388+
return self._status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS, Status.BUILD, Status.FIRST_RUN)
387389

388390
def isFinishedOrRunning(self):
389-
return self._status.status in (Status.SUCCESS, Status.RUNNING)
391+
return self._status.status in (Status.SUCCESS, Status.RUNNING, Status.BUILD, Status.FIRST_RUN)
390392

391393
def isRunning(self):
392-
return self._status.status == Status.RUNNING
394+
return self._status.status in (Status.RUNNING, Status.BUILD, Status.FIRST_RUN)
393395

394396
def isStopped(self):
395397
return self._status.status == Status.STOPPED
@@ -401,36 +403,56 @@ def process(self, forceCompute=False):
401403
if not forceCompute and self._status.status == Status.SUCCESS:
402404
logging.info("Node chunk already computed: {}".format(self.name))
403405
return
404-
global runningProcesses
405-
runningProcesses[self.name] = self
406-
self._status.initStartCompute()
407-
exceptionStatus = None
408-
startTime = time.time()
409-
self.upgradeStatusTo(Status.RUNNING)
410-
self.statThread = stats.StatisticsThread(self)
411-
self.statThread.start()
412-
try:
413-
self.node.nodeDesc.processChunk(self)
414-
except Exception:
415-
if self._status.status != Status.STOPPED:
416-
exceptionStatus = Status.ERROR
417-
raise
418-
except (KeyboardInterrupt, SystemError, GeneratorExit):
419-
exceptionStatus = Status.STOPPED
420-
raise
421-
finally:
422-
self._status.initEndCompute()
423-
self._status.elapsedTime = time.time() - startTime
424-
if exceptionStatus is not None:
425-
self.upgradeStatusTo(exceptionStatus)
426-
logging.info(" - elapsed time: {}".format(self._status.elapsedTimeStr))
427-
# Ask and wait for the stats thread to stop
428-
self.statThread.stopRequest()
429-
self.statThread.join()
430-
self.statistics = stats.Statistics()
431-
del runningProcesses[self.name]
432-
433-
self.upgradeStatusTo(Status.SUCCESS)
406+
407+
#if plugin node and if first call call meshroom_compute inside the env on 'host' so that the processchunk
408+
# of the node will be ran into the env
409+
if hasattr(self.node.nodeDesc, 'envFile') and self._status.status!=Status.FIRST_RUN:
410+
try:
411+
if not self.node.nodeDesc.isBuild():
412+
self.upgradeStatusTo(Status.BUILD)
413+
self.node.nodeDesc.build()
414+
self.upgradeStatusTo(Status.FIRST_RUN)
415+
command = self.node.nodeDesc.getCommandLine(self)
416+
#NOTE: docker returns 0 even if mount fail (it fails on the deamon side)
417+
logging.info("Running plugin node with "+command)
418+
status = os.system(command)
419+
if status != 0:
420+
raise RuntimeError("Error in node execution")
421+
self.updateStatusFromCache()
422+
except Exception as ex:
423+
self.logger.exception(ex)
424+
self.upgradeStatusTo(Status.ERROR)
425+
else:
426+
global runningProcesses
427+
runningProcesses[self.name] = self
428+
self._status.initStartCompute()
429+
exceptionStatus = None
430+
startTime = time.time()
431+
self.upgradeStatusTo(Status.RUNNING)
432+
self.statThread = stats.StatisticsThread(self)
433+
self.statThread.start()
434+
try:
435+
self.node.nodeDesc.processChunk(self)
436+
except Exception:
437+
if self._status.status != Status.STOPPED:
438+
exceptionStatus = Status.ERROR
439+
raise
440+
except (KeyboardInterrupt, SystemError, GeneratorExit):
441+
exceptionStatus = Status.STOPPED
442+
raise
443+
finally:
444+
self._status.initEndCompute()
445+
self._status.elapsedTime = time.time() - startTime
446+
if exceptionStatus is not None:
447+
self.upgradeStatusTo(exceptionStatus)
448+
logging.info(" - elapsed time: {}".format(self._status.elapsedTimeStr))
449+
# Ask and wait for the stats thread to stop
450+
self.statThread.stopRequest()
451+
self.statThread.join()
452+
self.statistics = stats.Statistics()
453+
del runningProcesses[self.name]
454+
455+
self.upgradeStatusTo(Status.SUCCESS)
434456

435457
def stopProcess(self):
436458
if not self.isExtern():
@@ -1127,8 +1149,8 @@ def getGlobalStatus(self):
11271149
return Status.INPUT
11281150
chunksStatus = [chunk.status.status for chunk in self._chunks]
11291151

1130-
anyOf = (Status.ERROR, Status.STOPPED, Status.KILLED,
1131-
Status.RUNNING, Status.SUBMITTED)
1152+
anyOf = (Status.ERROR, Status.STOPPED, Status.KILLED, Status.RUNNING, Status.BUILD, Status.FIRST_RUN,
1153+
Status.SUBMITTED,)
11321154
allOf = (Status.SUCCESS,)
11331155

11341156
for status in anyOf:

0 commit comments

Comments
 (0)