-
-
-
- + + + + +
-
+
+
-
-
- -
-
- - Home - - -
- - Help - - -
- - Tools - -
+
+
+
+
+
+
-
@@ -489,10 +519,12 @@ class="btn green"> Link
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
- + Home + + +
- + Help + + +
- + Tools + +
Select a tool +
+ + + ++
+
+
+
+
+
diff --git a/front_end/openVRE/public/htmlib/js.inc.php b/front_end/openVRE/public/htmlib/js.inc.php
index 06034cde..b2c72174 100644
--- a/front_end/openVRE/public/htmlib/js.inc.php
+++ b/front_end/openVRE/public/htmlib/js.inc.php
@@ -146,7 +146,7 @@
-
+
@@ -163,15 +163,11 @@
-
+
-
-
-
-
+
+
+
+
+
-
-
- All
+
+
+
+
+
Select a tool -
- - - --
-
-
-
-
- ##pending##
+ ##pending##
##execution##
##mtime##
@@ -34,7 +34,7 @@
diff --git a/front_end/openVRE/public/htmlib/footer.inc.php b/front_end/openVRE/public/htmlib/footer.inc.php
index 24a53675..0f23d1ad 100644
--- a/front_end/openVRE/public/htmlib/footer.inc.php
+++ b/front_end/openVRE/public/htmlib/footer.inc.php
@@ -85,7 +85,7 @@
Our Cookie Policy
-
-
-
- All
-
-
-
-
-
-
-
-
+
-
- foreach($toolList as $t) {
+
+ if (strpos($kw, 'visualizer') === false) $type = 'tools';
+ else $type = 'visualizers';
-
-
+
+
+
-
+ ?>
\ No newline at end of file
diff --git a/front_end/openVRE/public/htmlib/TreeTblworkspace/TR_fileInteractive.htm b/front_end/openVRE/public/htmlib/TreeTblworkspace/TR_fileInteractive.htm
index 1d847ce4..bd50eb83 100644
--- a/front_end/openVRE/public/htmlib/TreeTblworkspace/TR_fileInteractive.htm
+++ b/front_end/openVRE/public/htmlib/TreeTblworkspace/TR_fileInteractive.htm
@@ -22,7 +22,7 @@
- Understand
+ Understand
+
+
+
+
+
-->
-
+
+ containerName \
--net $networkName \
- $cmd_envs \
+ $customToolParameters \
--hostname $this->containerName \
-p $hostPort:{$tool['infrastructure']['container_port']} {$tool['infrastructure']['container_image']} {$tool['infrastructure']['executable']});
EOF;
@@ -1105,7 +1105,7 @@ protected function setBashCommandDockerSgeInteractive($tool, $cmd_envs)
}
- protected function setBashCommandDockerCompose($tool, $cmd_envs)
+ protected function setBashCommandDockerCompose($tool, $customToolParameters)
{
$this->job_type = "interactive";
$dockerComposeFile = $GLOBALS['toolsPath'] . $tool['infrastructure']['docker_path'];
@@ -1136,7 +1136,28 @@ protected function setBashCommandDockerCompose($tool, $cmd_envs)
echo '# End time:' \$(date) >> $this->log_file_virtual;
EOF;
- return $cmd . "\n" . $monitorContainer . $cmd_envs;
+ return $cmd . "\n" . $monitorContainer . $customToolParameters;
+ }
+
+
+ protected function isToolRunning()
+ {
+ $ch = curl_init();
+ $defaultInternalPort = 8787;
+ curl_setopt_array($ch, [
+ CURLOPT_URL => $this->containerName . ":" . $defaultInternalPort,
+ CURLOPT_NOBODY => true,
+ CURLOPT_TIMEOUT => 5,
+ CURLOPT_CONNECTTIMEOUT => 5,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FOLLOWLOCATION => false,
+ ]);
+
+ curl_exec($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ return $httpCode !== 0;
}
@@ -1148,23 +1169,46 @@ protected function setBashCommandDockerSge($tool)
}
$this->containerName = $tool['infrastructure']['container_image'] . "_" . $_SESSION['User']['activeProject'];
- $cmd_envs = "";
+ $customToolParameters = "";
$envReplacements = ['$this->containerName' => $this->containerName];
foreach ($tool['infrastructure']['container_env'] as $env_key => $env_value) {
$env_value = str_replace(array_keys($envReplacements), array_values($envReplacements), $env_value);
- $cmd_envs .= "-e $env_key=$env_value ";
+ $customToolParameters .= "-e $env_key=$env_value ";
}
foreach ($tool['infrastructure']['volumes'] as $hostDir => $containerDir) {
$userHomeDir = $this->root_dir_volumes . "/" . $this->project;
- $cmd_envs .= "-v $userHomeDir" . "$hostDir:$containerDir ";
+ $customToolParameters .= "-v $userHomeDir" . "$hostDir:$containerDir ";
+
+ $user = getUserById($_SESSION['User']['_id']);
+ $dataDir = $user['id'] . "/" . $user['activeProject'];
+ $upDirId = createGSDirBNS($dataDir . $hostDir, 1);
+ getProjectLogger()->info("Creating directory:" . $dataDir . $hostDir . "($upDirId)");
+ addMetadataToFile($upDirId, array(
+ "expiration" => -1,
+ "description" => "Uploaded personal data"
+ ));
+
+ $dataDirP = $GLOBALS['dataDir'] . "/$dataDir";
+ if (!is_dir("$dataDirP" . $hostDir)) {
+ mkdir("$dataDirP" . $hostDir, 0775);
+ }
+ }
+
+ if (!empty($tool['infrastructure']['user'])) {
+ $customToolParameters .= "--user " . escapeshellarg($tool['infrastructure']['user'] . " ");
}
if ($tool['infrastructure']['interactive']) {
if ($tool['infrastructure']['docker_type'] == "compose") {
- $cmd = $this->setBashCommandDockerCompose($tool, $cmd_envs);
+ $cmd = $this->setBashCommandDockerCompose($tool, $customToolParameters);
} else {
- $cmd = $this->setBashCommandDockerSgeInteractive($tool, $cmd_envs);
+ if ($this->isToolRunning()) {
+ $toolUrl = $GLOBALS['URL'] . "interactive-tool/" . $this->containerName . "/";
+ $_SESSION['errorData']['Error'][] = "There is already a running instance of this tool at: " . $toolUrl . "";
+ }
+
+ $cmd = $this->setBashCommandDockerSgeInteractive($tool, $customToolParameters);
}
} else {
$cmd_vre = $tool['infrastructure']['executable'] .
@@ -1175,7 +1219,7 @@ protected function setBashCommandDockerSge($tool)
$cmd = "docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock -d" .
- " " . $cmd_envs .
+ " " . $customToolParameters .
"--memory=" . $tool['infrastructure']['memory'] . "g" .
" -v " . $this->pub_dir_volumes . ":" . $GLOBALS['shared'] . "public_tmp/ " .
" -v " . $this->root_dir_volumes . ":" . $GLOBALS['shared'] . "userdata_tmp/{$_SESSION['User']['id']}" .
@@ -1244,9 +1288,9 @@ protected function setBashCmd_docker_EGA($tool)
" --out_metadata " . $this->stageout_file_virtual .
" --log_file " . $this->log_file_virtual;
- $cmd_envs = "";
+ $customToolParameters = "";
foreach ($tool['infrastructure']['container_env'][0] as $env_key => $env_value) {
- $cmd_envs .= "-e $env_key=$env_value ";
+ $customToolParameters .= "-e $env_key=$env_value ";
}
$vaultKey = $_SESSION['userVaultInfo']['vaultKey'];
@@ -1261,7 +1305,7 @@ protected function setBashCmd_docker_EGA($tool)
}
$cmd = "docker run --device /dev/fuse --security-opt apparmor:unconfined --cap-add SYS_ADMIN -v /var/run/docker.sock:/var/run/docker.sock " .
- " " . $cmd_envs .
+ " " . $customToolParameters .
" -v " . $this->pub_dir_host . ":" . $GLOBALS['shared'] . "public_tmp/ " .
" -v " . $this->root_dir_host . "/" . $_SESSION['User']['id'] . ":" . $GLOBALS['shared'] . "userdata_tmp/" . $_SESSION['User']['id'] .
" --tmpfs " . "/clean_files:rw,uid=1000,gid=1000" .
@@ -1443,6 +1487,7 @@ protected function enqueue($tool)
$pid = execJob($this->working_dir, $this->submission_file, $queue, $cpus, $memory, $this->stdout_file, $this->stderr_file, $jobManager, $this->toolId, $jobOptions);
$this->logger->info("Tool job submitted to SGE queue '$queue' (PID=$pid)");
+ LoggerFactory::getPersistentLogger()->info("Job {pid} for tool {toolId} submitted to SGE queue {queue}", array( "toolId" => $this->toolId, "queue" => $queue, "pid" => $pid));
$this->pid = $pid;
return $pid;
diff --git a/front_end/openVRE/public/phplib/db.inc.php b/front_end/openVRE/public/phplib/db.inc.php
index 38ce06bd..46824e0b 100644
--- a/front_end/openVRE/public/phplib/db.inc.php
+++ b/front_end/openVRE/public/phplib/db.inc.php
@@ -20,7 +20,7 @@
// create handlers
$dbname = getenv('MONGO_MAIN_DB');
-
+$GLOBALS['mongodbClient'] = $VREConn;
$GLOBALS['db'] = $VREConn->$dbname;
$GLOBALS['usersCol'] = $GLOBALS['db']->users;
$GLOBALS['filesCol'] = $GLOBALS['db']->files;
@@ -28,10 +28,10 @@
$GLOBALS['logMailCol'] = $GLOBALS['db']->checkMail;
$GLOBALS['toolsCol'] = $GLOBALS['db']->tools;
$GLOBALS['visualizersCol'] = $GLOBALS['db']->visualizers;
-$GLOBALS['fileFormatsCol'] = $GLOBALS['db']->file_formats;
+$GLOBALS['fileFormatsCol'] = $GLOBALS['db']->file_formats;
$GLOBALS['dataTypesCol'] = $GLOBALS['db']->data_types;
$GLOBALS['helpsCol'] = $GLOBALS['db']->helps;
$GLOBALS['sampleDataCol'] = $GLOBALS['db']->sampleData;
-$GLOBALS['logExecutionsCol'] = $GLOBALS['db']->log_executions;
+$GLOBALS['actionLogs'] = $GLOBALS['db']->action_logs;
//adding new cred for SITES collection
$GLOBALS['sitesCol'] = $GLOBALS['db']->sites;
diff --git a/front_end/openVRE/public/phplib/interactive_tools.inc.php b/front_end/openVRE/public/phplib/interactive_tools.inc.php
new file mode 100644
index 00000000..2d41f7ee
--- /dev/null
+++ b/front_end/openVRE/public/phplib/interactive_tools.inc.php
@@ -0,0 +1,110 @@
+ false,
+ "reload" => false,
+ "title" => "Job not found",
+ "message" => "The requested interactive session could not be found."
+ ];
+ }
+
+ $job = $jobs[$pid];
+
+ /*----------------------------------------------------
+ * Job state
+ *---------------------------------------------------*/
+
+ if ($job['state'] == "PENDING") {
+ return [
+ "ready" => false,
+ "reload" => true,
+ "title" => "Waiting for scheduler",
+ "message" => "The interactive session is waiting for compute resources."
+ ];
+ }
+
+ if ($job['state'] != "RUNNING") {
+ return [
+ "ready" => false,
+ "reload" => false,
+ "title" => "Session finished",
+ "message" => "The interactive session is no longer running."
+ ];
+ }
+
+ /*----------------------------------------------------
+ * Wait until stdout exists
+ *---------------------------------------------------*/
+
+ if (!is_file($job['stdout_file'])) {
+ return [
+ "ready" => false,
+ "reload" => true,
+ "title" => "Starting container",
+ "message" => "Waiting for launcher output..."
+ ];
+ }
+
+ $stdout = file_get_contents($job['stdout_file']);
+
+ /*----------------------------------------------------
+ * Save metadata in the session (KEEP THIS)
+ *---------------------------------------------------*/
+
+ /*
+ if (preg_match('/ExposedPort: (\d+)/', $stdout, $matches)) {
+ $_SESSION['User']['lastjobs'][$pid]['interactive_tool']['port'] = $matches[1];
+ }
+
+ if (preg_match('/ContainerID: (\w+)/', $stdout, $matches)) {
+ $_SESSION['User']['lastjobs'][$pid]['interactive_tool']['container_id'] = $matches[1];
+ }
+
+ if (preg_match('/ContainerName: (\S+)/', $stdout, $matches)) {
+ $_SESSION['User']['lastjobs'][$pid]['interactive_tool']['containerName'] = $matches[1];
+ }
+ */
+
+ /*----------------------------------------------------
+ * Has the service started?
+ *---------------------------------------------------*/
+
+ if (strpos($stdout, "Service UP") === false) {
+
+ return [
+ "ready" => false,
+ "reload" => true,
+ "title" => "Preparing interactive session",
+ "message" => "The container is running. Waiting for the application to become available..."
+ ];
+ }
+
+ /*----------------------------------------------------
+ * Mark service ready
+ *---------------------------------------------------*/
+
+ $url = $GLOBALS['SERVER']
+ . $interactiveToolprefix
+ . $job['containerName']
+ . "/";
+
+ return [
+ "ready" => true,
+ "reload" => false,
+ "title" => "Interactive session ready",
+ "message" => "Your session is ready.",
+ "url" => $url,
+ "job" => $job
+ ];
+}
diff --git a/front_end/openVRE/public/phplib/mail.inc.php b/front_end/openVRE/public/phplib/mail.inc.php
index 33b87696..186c91d0 100644
--- a/front_end/openVRE/public/phplib/mail.inc.php
+++ b/front_end/openVRE/public/phplib/mail.inc.php
@@ -21,8 +21,8 @@ function sendEmail($recipient, $subject, $body, $reply = null, $bcc = null)
$reply = $GLOBALS['ADMINMAIL'];
}
- $mail->AddReplyTo($reply, $GLOBALS['FROMNAME']);
- $mail->SetFrom($reply, $GLOBALS['FROMNAME']);
+ $mail->SetFrom(getenv('MAIL_USER'), $GLOBALS['FROMNAME']);
+ $mail->AddReplyTo($reply);
$mail->Subject = $subject;
$mail->Body = $body;
$mail->AddAddress($recipient);
diff --git a/front_end/openVRE/public/phplib/processJob.inc.php b/front_end/openVRE/public/phplib/processJob.inc.php
index 5447a205..b93485c1 100644
--- a/front_end/openVRE/public/phplib/processJob.inc.php
+++ b/front_end/openVRE/public/phplib/processJob.inc.php
@@ -25,16 +25,19 @@ function execJob($workDir, $shFile, $queue, $cpus = 1, $mem = 0, $logFile = "job
if (is_null($_SESSION['User']['id'])) {
getJobProcessLogger()->error("User ID not found in session.");
+ LoggerFactory::getPersistentLogger()->error("User ID {userId} not found in session.", array('userId' => $_SESSION['User']['id']));
throw new NotFoundException("User ID not found in session.");
}
if (!file_exists($shFile)) {
getJobProcessLogger()->error("Shell script file does not exist: $shFile");
+ LoggerFactory::getPersistentLogger()->error("Shell script file {shFile} does not exist", array('shFile' => $shFile));
throw new NotFoundException("Shell script file does not exist: $shFile");
}
if (!is_dir($workDir)) {
getJobProcessLogger()->error("Working directory does not exist: $workDir");
+ LoggerFactory::getPersistentLogger()->error("Working directory {workDir} does not exist", array('workDir' => $workDir));
throw new NotFoundException("Working directory does not exist: $workDir");
}
@@ -251,6 +254,7 @@ function delJob($pid, $launcherType = null, $login = null)
}
$_SESSION['errorData']['Info'][] = "Job successfully cancelled";
+ LoggerFactory::getPersistentLogger()->info("Job {pid} successfully cancelled", array('pid' => $pid));
// wait to make qdel/terminateActivity effective
sleep(15);
diff --git a/front_end/openVRE/public/phplib/projects.inc.php b/front_end/openVRE/public/phplib/projects.inc.php
index be0c3c70..60957c35 100644
--- a/front_end/openVRE/public/phplib/projects.inc.php
+++ b/front_end/openVRE/public/phplib/projects.inc.php
@@ -3,9 +3,7 @@
use League\OAuth2\Client\Token\AccessToken;
use OpenVRE\LoggerFactory;
use OpenVRE\NotFoundException;
-use OpenVRE\Oauth2Provider;
use OpenVRE\UserType;
-use OpenVRE\VaultClient;
function getProjectLogger()
@@ -19,6 +17,7 @@ function getProjectLogger()
return $logger;
}
+
function prepUserWorkSpace($homeDir, $projectDir, $sampleData = "", $projectData = array(), $verbose = false, $asRoot = 0)
{
// set current directory
@@ -1280,6 +1279,7 @@ function updatePendingFiles($sessionId)
// and consequently reload workspace (checkPendingJobs.php)
} else {
getProjectLogger()->info("Automatic job update detects job $pid is not running anymore");
+ LoggerFactory::getPersistentLogger()->info("Automatic job update detects job {pid} is not running anymore", array('pid' => $pid));
$SGE_updated[$pid] = $job;
$SGE_updated[$pid]['state'] = "NOT_RUNNING";
}
@@ -1356,6 +1356,7 @@ function processRunningJobInfo($job, $jobProcess, $pid, $title, $descrip, &$file
function processFinishedJobInfo($job, $pid, $title, &$filesPending)
{
getProjectLogger()->info("Workspace reload detects job $pid is not running anymore");
+ LoggerFactory::getPersistentLogger()->info("Workspace reload detects job {pid} is not running anymore", array('pid' => $pid));
unset($_SESSION['errorData']);
$job_in_err = 0;
@@ -1366,6 +1367,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending)
getProjectLogger()->error("Tool '" . $job['toolId'] . "' received from JobTool not registered");
getProjectLogger()->error("Cannot obtain results from '$title' in folder '" . basename($job['working_dir']) . "'. Job metadata is not valid.");
getProjectLogger()->error("Failed to register $pid job outfiles. Job metadata has toolId '" . $job['toolId'] . "'");
+ LoggerFactory::getPersistentLogger()->error("Failed to register {pid} job outfiles. Tool {toolId} not registered.", array('pid' => $pid, 'toolId' => $job['toolId']));
$job_in_err = 1;
return;
}
@@ -1377,6 +1379,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending)
getProjectLogger()->debug("Finished building output from toolINFO + stageout_file + stageout_data: " . json_encode($outs_files));
if (empty($outs_files)) {
getProjectLogger()->warning("Failed to register $pid job outfiles. Output file list empty.");
+ LoggerFactory::getPersistentLogger()->warning("Failed to register {pid} job outfiles. Output file list empty.", array('pid' => $pid));
$job_in_err = 1;
}
@@ -1414,7 +1417,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending)
print "
Recovering path from remote_paths: $remote_path
"; $_SESSION['errorData']['Error'][] = "Recovering path from remote_paths: $remote_path"; } - // this is right (?) + $out_data['path'] = $remote_path; } else { if ($is_required) { @@ -1423,7 +1426,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) $msg .= ". No 'path' and no usable 'remote_paths' found."; $msg .= ". Job metadata: " . print_r($out_data, true); $_SESSION['errorData']['Error'][] = $msg; - log_addOutregister($pid, $msg); + LoggerFactory::getPersistentLogger()->error("Job output file {outName} not created.", array('outName' => $out_name)); $job_in_err = 1; } continue; @@ -1481,8 +1484,9 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) // job successfully finished and already in mongo. Update medatada if ($fileId) { - getProjectLogger()->debug("JOB $pid finished successfully."); + getProjectLogger()->info("JOB $pid finished successfully."); getProjectLogger()->debug("Updating only outfile $out_name '$rfn' metadata from job $pid"); + LoggerFactory::getPersistentLogger()->info("Job {pid} finished successfully.", array('pid' => $pid)); list($out_vre, $metadata) = getVREfile_fromFile($out_data); addMetadataToFile($fileId, $metadata); } elseif (is_file($rfn) || is_dir($rfn) || isset($out_data['meta_data']['remote_paths'][0]['remote_path'])) { // job successfully finished but not yet on mongo. Save output @@ -1547,6 +1551,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) if ($job_in_err) { getProjectLogger()->error("Failed to register all job outfiles"); getProjectLogger()->error("JOB $pid FINISHED but with errors"); + LoggerFactory::getPersistentLogger()->error("Job {pid} finished with errors. Failed to register all job outfiles.", array('pid' => $pid)); $logFileP = $job['log_file']; $logFile = fromAbsPath_toPath($job['log_file']); @@ -1570,7 +1575,8 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) } } } else { - getProjectLogger()->debug("JOB $pid finished successfully."); + getProjectLogger()->info("JOB $pid finished successfully."); + LoggerFactory::getPersistentLogger()->info("Job {pid} finished successfully.", array('pid' => $pid)); } } @@ -1756,7 +1762,7 @@ function build_outputs_list($tool, $stageout_job, $stageout_file) array_push($stageout_meta[$out['name']], $out); } - } elseif ($tool['external'] !== false) { + } elseif ($tool['external'] !== false && !$tool['infrastructure']['interactive']) { $_SESSION['errorData']['Warning'][] = date("h:i:s") . ": Tool stageout file '" . $stageout_file . "' is not found"; getProjectLogger()->warning("Tool stageout file '" . $stageout_file . "' is not found"); } @@ -1777,20 +1783,6 @@ function build_outputs_list($tool, $stageout_job, $stageout_file) array_push($stageout_data[$out['name']], $out); } } - if ($debug) { - - print "\n__________FROM FILE________________\n"; - print json_encode($stageout_meta, JSON_PRETTY_PRINT); - - print "\n__________FROM JOB________________\n"; - print json_encode($stageout_data, JSON_PRETTY_PRINT); - - // Merge FILE + JOB (job overrides file) - $stageout_meta = array_merge($stageout_meta, $stageout_data); - - print "\n__________MERGED (FILE + JOB)________________\n"; - print json_encode($stageout_meta, JSON_PRETTY_PRINT); - } // merging file data from tool and stageout_file $outs_meta = array(); @@ -1913,14 +1905,20 @@ function downloadFile($rfn) $fhandle = fopen($rfn, 'r'); fseek($fhandle, $offset); // seek to the requested offset, this is 0 if it's not a partial content request - $data = fread($fhandle, $length); fclose($fhandle); header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size); } - header("Content-Disposition: attachment;filename=" . $filename); - header('Content-Type: ' . $content_type); + + if (strtolower($fileExtension) === 'html' || strtolower($fileExtension) === 'htm') { + header("Content-Disposition: inline; filename=\"$filename\""); + header("Content-Type: text/html; charset=UTF-8"); + } else { + header("Content-Disposition: attachment; filename=\"$filename\""); + header("Content-Type: " . $content_type); + } + header("Accept-Ranges: bytes"); header("Pragma: public"); header("Expires: -1"); @@ -2283,3 +2281,130 @@ function moveFiles($fns, $target_fn) */ } } + + +function syncProjectFiles($projects) +{ + foreach ($projects as $projectId => $projectAttributes) { + $projectFullPath = $GLOBALS['dataDir'] . "/" . $projectAttributes['path']; + + if (!is_dir($projectFullPath)) { + continue; // skip projects whose root path doesn't exist + } + + syncDirectoryRecursive($projectFullPath, $projectAttributes['path'], $projectId, null); + } +} + + +function syncDirectoryRecursive($fullFolderPath, $relativeFolderPath, $projectId, $parentDirId) +{ + $entries = scandir($fullFolderPath); + + foreach ($entries as $entry) { + // skip hidden files/folders (this also covers "." and "..") + if ($entry[0] === '.') { + continue; + } + + $relativePath = $relativeFolderPath . "/" . $entry; + $fullPath = $fullFolderPath . "/" . $entry; + + if (is_dir($fullPath)) { + // skip folders named "run*" (e.g. "run001") + if (fnmatch('run*', $entry)) { + continue; + } + + // ensure this subdirectory itself is registered, then recurse into it + $subDirId = ensureDirRegistered($relativePath, $projectId, $parentDirId); + syncDirectoryRecursive($fullPath, $relativePath, $projectId, $subDirId); + } else { + ensureFileRegistered($relativePath, $fullPath, $projectId, $parentDirId); + } + } +} + +function ensureDirRegistered($relativePath, $projectId, $parentDirId) +{ + $existingId = getGSFileId_fromPath($relativePath); + if ($existingId) { + return $existingId; + } + + $dirId = createLabel(); + + $mongoDirDocument = array( + '_id' => $dirId, + 'mtime' => new MongoDB\BSON\UTCDateTime(strtotime("now") * 1000), + 'owner' => $_SESSION['User']['id'], + 'path' => $relativePath, + 'project' => $projectId, + 'parentDir' => $parentDirId, + 'type' => "dir", + 'files' => [] + ); + + $GLOBALS['filesCol']->updateOne( + ['_id' => $dirId], + ['$set' => $mongoDirDocument], + ['upsert' => true] + ); + + if ($parentDirId) { + $GLOBALS['filesCol']->updateOne( + ['_id' => $parentDirId], + ['$addToSet' => ['files' => $dirId]] + ); + } + + return $dirId; +} + +function ensureFileRegistered($relativePath, $fullPath, $projectId, $parentDirId) +{ + if (getGSFileId_fromPath($relativePath)) { + return; // already registered + } + + $fileId = createLabel(); + + $mongoFileDocument = array( + '_id' => $fileId, + 'mtime' => new MongoDB\BSON\UTCDateTime(strtotime("now") * 1000), + 'owner' => $_SESSION['User']['id'], + 'size' => filesize($fullPath), + 'path' => $relativePath, + 'project' => $projectId, + 'parentDir' => $parentDirId, + 'type' => "file" + ); + + $mongoFileMetadataDocument = array( + '_id' => $fileId, + 'compressed' => false, + 'data_type' => null, + 'format' => null, + 'validated' => true, + 'visible' => true + ); + + $GLOBALS['filesCol']->updateOne( + ['_id' => $fileId], + ['$set' => $mongoFileDocument], + ['upsert' => true] + ); + + $GLOBALS['filesMetaCol']->updateOne( + ['_id' => $fileId], + ['$set' => $mongoFileMetadataDocument], + ['upsert' => true] + ); + + if ($parentDirId) { + $GLOBALS['filesCol']->updateOne( + ['_id' => $parentDirId], + ['$addToSet' => ['files' => $fileId]] + ); + } +} diff --git a/front_end/openVRE/public/user/usrProfile.php b/front_end/openVRE/public/user/usrProfile.php index e5eebac4..63411302 100644 --- a/front_end/openVRE/public/user/usrProfile.php +++ b/front_end/openVRE/public/user/usrProfile.php @@ -3,6 +3,10 @@ require __DIR__ . "/../../config/bootstrap.php"; redirectOutside(); +$includeHpc = false; +$includeEgaFiles = false; +$includeOpenStack = false; + ?> @@ -396,78 +400,104 @@ class="icon-question tooltips" data-container="body" class="caption-subject font-blue-madison bold uppercase">Linked Accounts - + +
+
+
-
-
+
+
+
\ No newline at end of file
diff --git a/front_end/openVRE/public/phplib/classes/LoggerFactory.php b/front_end/openVRE/public/phplib/classes/LoggerFactory.php
index 3ed215d1..7a62b25e 100644
--- a/front_end/openVRE/public/phplib/classes/LoggerFactory.php
+++ b/front_end/openVRE/public/phplib/classes/LoggerFactory.php
@@ -2,16 +2,21 @@
namespace OpenVRE;
-use Monolog\Logger;
+require_once __DIR__ . "/../db.inc.php";
+
+use Monolog\Handler\MongoDBHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;
use Monolog\Level;
+use Monolog\Logger;
+use Monolog\Processor\UidProcessor;
+use Monolog\Processor\PsrLogMessageProcessor;
class LoggerFactory
{
private static array $loggers = [];
- public static function getLogger(string $channel = 'app'): Logger
+ public static function getLogger(string $channel = 'app-stdout'): Logger
{
if (isset(self::$loggers[$channel])) {
return self::$loggers[$channel];
@@ -34,4 +39,26 @@ public static function getLogger(string $channel = 'app'): Logger
return $logger;
}
+
+
+ public static function getPersistentLogger(): Logger
+ {
+ if (isset(self::$loggers['app-mongodb'])) {
+ return self::$loggers['app-mongodb'];
+ }
+
+ $logger = new Logger('app-mongodb');
+ $logsCollection = 'action_logs';
+ $mongodb = new MongoDBHandler($GLOBALS['mongodbClient'], getenv('MONGO_MAIN_DB'), $logsCollection, level::Info);
+ $logger->pushHandler($mongodb);
+ $logger->pushProcessor(new UidProcessor());
+ $logger->pushProcessor(new PsrLogMessageProcessor());
+ $logger->pushProcessor(function ($record) {
+ $record['extra']['userId'] = $_SESSION['User']['id'];
+ return $record;
+ });
+ self::$loggers['app-mongodb'] = $logger;
+
+ return $logger;
+ }
}
diff --git a/front_end/openVRE/public/phplib/classes/ProcessSGE.php b/front_end/openVRE/public/phplib/classes/ProcessSGE.php
index 47888b13..2d326db3 100644
--- a/front_end/openVRE/public/phplib/classes/ProcessSGE.php
+++ b/front_end/openVRE/public/phplib/classes/ProcessSGE.php
@@ -41,7 +41,7 @@ class ProcessSGE
private Logger $logger;
- public function __construct($cl = false, $workDir = "", $queue = "srv.q", $jobname = "", $cpu = 1, $mem = 0, $logFile = "job_output.log", $errFile = "job_error.log")
+ public function __construct($cl = false, $workDir = "", $queue = "local.q", $jobname = "", $cpu = 1, $mem = 0, $logFile = "job_output.log", $errFile = "job_error.log")
{
$this->logger = LoggerFactory::getLogger("Process SGE interface");
$current_user = posix_getpwuid(posix_geteuid());
@@ -94,7 +94,7 @@ private function runCom()
public function setFullCommand()
{
$workDir = $this->workDir;
- $command = QSUB . " -N '" . $this->jobname . "' -wd $workDir -q " . $this->queue . " -o " . $this->logFile . " -e " . $this->errFile;
+ $command = QSUB . " -notify -N '" . $this->jobname . "' -wd $workDir -q " . $this->queue . " -o " . $this->logFile . " -e " . $this->errFile;
if ($this->cpu > 1) {
$command .= " -l cpu=" . $this->cpu;
}
@@ -131,6 +131,7 @@ public function getRunningJobInfo($pid)
if (is_null($jobState[0])) {
$job['state'] = "FINISHING";
+ LoggerFactory::getPersistentLogger()->info("Job {pid} finished", array("pid" => $pid));
} else {
list($pid, $state) = explode("\t", $jobState[0]);
$job['state'] = $this->jobState[$state];
@@ -177,7 +178,8 @@ public function stop($pid = null)
$command = QDEL . ' ' . $pid;
exec($command, $r);
$res = join(" ", $r);
- log_addInfo($jobid, "SGE/qdel: " . $res);
+ $this->logger->info("Job stopped: " . $res);
+ LoggerFactory::getPersistentLogger()->info("Job {pid} stopped", array("pid" => $pid));
if (preg_match('/has deleted/i', $res) || preg_match('/registered the job \d+ for deletion/', $res)) {
return array(true, $res);
} else {
diff --git a/front_end/openVRE/public/phplib/classes/ProcessSlurm.php b/front_end/openVRE/public/phplib/classes/ProcessSlurm.php
index f362efdc..aee6aed1 100644
--- a/front_end/openVRE/public/phplib/classes/ProcessSlurm.php
+++ b/front_end/openVRE/public/phplib/classes/ProcessSlurm.php
@@ -200,6 +200,7 @@ public function getRunningJobInfo($pid)
if (!$line) {
$this->logger->debug("ProcessSlurm: getRunningJobInfo: job not running anymore. State: FINISHING");
+ LoggerFactory::getPersistentLogger()->info("Job {pid} finished.", array("pid" => $pid));
$job['state'] = "FINISHING";
// log message like SGE version:
$this->logger->debug("ProcessSlurm: getRunningJobInfo: log message added");
diff --git a/front_end/openVRE/public/phplib/classes/SwiftClient.py b/front_end/openVRE/public/phplib/classes/SwiftClient.py
deleted file mode 100644
index 433b2766..00000000
--- a/front_end/openVRE/public/phplib/classes/SwiftClient.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import swiftclient
-
-
-
-class SwiftClientExample:
- def __init__(self, auth_url, username, password, project_name, user_domain_name, project_domain_name):
- self.auth_url = auth_url
- self.username = username
- self.password = password
- self.project_name = project_name
- self.user_domain_name = user_domain_name
- self.project_domain_name = project_domain_name
- self.conn = None
-
- def authenticate(self):
- self.conn = swiftclient.Connection(
- authurl=self.auth_url,
- user=self.username,
- key=self.password
- )
-
- def list_containers(self):
- if self.conn is None:
- return "Not authenticated."
-
- containers = self.conn.get_account()[1]
- return [container["name"] for container in containers]
-
- def download_object(self, container_name, object_name):
- if self.conn is None:
- return "Not authenticated."
-
- try:
- response, content = self.conn.get_object(container_name, object_name)
- with open(object_name, "wb") as f:
- f.write(content)
- return f"Object '{object_name}' downloaded and saved successfully."
- except swiftclient.exceptions.ClientException as e:
- return f"Failed to download object '{object_name}': {e}"
-
- def close_connection(self):
- if self.conn is not None:
- self.conn.close()
-
-
-
-if __name__ == "__main__":
-
- auth_url="https://ncloud.bsc.es:5000/v3"
- username="bsc23829"
- password="Maio23!"
- project_name="bsc22Disc4All"
- user_domain_name="bsc-compute"
- project_domain_name="bsc22Disc4All"
- swift_client = SwiftClientExample(auth_url, username, password, project_name, user_domain_name, project_domain_name)
- swift_client.authenticate()
-
- swift_client.list_containers()
-
- swift_client.close_connection()
diff --git a/front_end/openVRE/public/phplib/classes/Tooljob.php b/front_end/openVRE/public/phplib/classes/Tooljob.php
index 5f92480a..43e1d33c 100644
--- a/front_end/openVRE/public/phplib/classes/Tooljob.php
+++ b/front_end/openVRE/public/phplib/classes/Tooljob.php
@@ -1048,7 +1048,7 @@ protected function getFreePort()
}
- protected function setBashCommandDockerSgeInteractive($tool, $cmd_envs)
+ protected function setBashCommandDockerSgeInteractive($tool, $customToolParameters)
{
$this->job_type = "interactive";
$container_port = $tool['infrastructure']['container_port'];
@@ -1058,7 +1058,7 @@ protected function setBashCommandDockerSgeInteractive($tool, $cmd_envs)
throw new UnexpectedValueException("No free ports available to run the interactive tool.");
}
- $networkName = $GLOBALS['networkName'];
+ $networkName = $GLOBALS['NETWORK_NAME'];
$cmd = <<
+
+ -
+
- + Home + + +
- + Interactive Tool + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "
+ target="_blank">
+
+
+ Open Interactive Session
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ This page refreshes automatically every 3 seconds.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Launcher logs
+ + "> + Job Standard Output + + + + + + +Recovering path from remote_paths: $remote_path
"; $_SESSION['errorData']['Error'][] = "Recovering path from remote_paths: $remote_path"; } - // this is right (?) + $out_data['path'] = $remote_path; } else { if ($is_required) { @@ -1423,7 +1426,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) $msg .= ". No 'path' and no usable 'remote_paths' found."; $msg .= ". Job metadata: " . print_r($out_data, true); $_SESSION['errorData']['Error'][] = $msg; - log_addOutregister($pid, $msg); + LoggerFactory::getPersistentLogger()->error("Job output file {outName} not created.", array('outName' => $out_name)); $job_in_err = 1; } continue; @@ -1481,8 +1484,9 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) // job successfully finished and already in mongo. Update medatada if ($fileId) { - getProjectLogger()->debug("JOB $pid finished successfully."); + getProjectLogger()->info("JOB $pid finished successfully."); getProjectLogger()->debug("Updating only outfile $out_name '$rfn' metadata from job $pid"); + LoggerFactory::getPersistentLogger()->info("Job {pid} finished successfully.", array('pid' => $pid)); list($out_vre, $metadata) = getVREfile_fromFile($out_data); addMetadataToFile($fileId, $metadata); } elseif (is_file($rfn) || is_dir($rfn) || isset($out_data['meta_data']['remote_paths'][0]['remote_path'])) { // job successfully finished but not yet on mongo. Save output @@ -1547,6 +1551,7 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) if ($job_in_err) { getProjectLogger()->error("Failed to register all job outfiles"); getProjectLogger()->error("JOB $pid FINISHED but with errors"); + LoggerFactory::getPersistentLogger()->error("Job {pid} finished with errors. Failed to register all job outfiles.", array('pid' => $pid)); $logFileP = $job['log_file']; $logFile = fromAbsPath_toPath($job['log_file']); @@ -1570,7 +1575,8 @@ function processFinishedJobInfo($job, $pid, $title, &$filesPending) } } } else { - getProjectLogger()->debug("JOB $pid finished successfully."); + getProjectLogger()->info("JOB $pid finished successfully."); + LoggerFactory::getPersistentLogger()->info("Job {pid} finished successfully.", array('pid' => $pid)); } } @@ -1756,7 +1762,7 @@ function build_outputs_list($tool, $stageout_job, $stageout_file) array_push($stageout_meta[$out['name']], $out); } - } elseif ($tool['external'] !== false) { + } elseif ($tool['external'] !== false && !$tool['infrastructure']['interactive']) { $_SESSION['errorData']['Warning'][] = date("h:i:s") . ": Tool stageout file '" . $stageout_file . "' is not found"; getProjectLogger()->warning("Tool stageout file '" . $stageout_file . "' is not found"); } @@ -1777,20 +1783,6 @@ function build_outputs_list($tool, $stageout_job, $stageout_file) array_push($stageout_data[$out['name']], $out); } } - if ($debug) { - - print "\n__________FROM FILE________________\n"; - print json_encode($stageout_meta, JSON_PRETTY_PRINT); - - print "\n__________FROM JOB________________\n"; - print json_encode($stageout_data, JSON_PRETTY_PRINT); - - // Merge FILE + JOB (job overrides file) - $stageout_meta = array_merge($stageout_meta, $stageout_data); - - print "\n__________MERGED (FILE + JOB)________________\n"; - print json_encode($stageout_meta, JSON_PRETTY_PRINT); - } // merging file data from tool and stageout_file $outs_meta = array(); @@ -1913,14 +1905,20 @@ function downloadFile($rfn) $fhandle = fopen($rfn, 'r'); fseek($fhandle, $offset); // seek to the requested offset, this is 0 if it's not a partial content request - $data = fread($fhandle, $length); fclose($fhandle); header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes ' . $offset . '-' . ($offset + $length) . '/' . $size); } - header("Content-Disposition: attachment;filename=" . $filename); - header('Content-Type: ' . $content_type); + + if (strtolower($fileExtension) === 'html' || strtolower($fileExtension) === 'htm') { + header("Content-Disposition: inline; filename=\"$filename\""); + header("Content-Type: text/html; charset=UTF-8"); + } else { + header("Content-Disposition: attachment; filename=\"$filename\""); + header("Content-Type: " . $content_type); + } + header("Accept-Ranges: bytes"); header("Pragma: public"); header("Expires: -1"); @@ -2283,3 +2281,130 @@ function moveFiles($fns, $target_fn) */ } } + + +function syncProjectFiles($projects) +{ + foreach ($projects as $projectId => $projectAttributes) { + $projectFullPath = $GLOBALS['dataDir'] . "/" . $projectAttributes['path']; + + if (!is_dir($projectFullPath)) { + continue; // skip projects whose root path doesn't exist + } + + syncDirectoryRecursive($projectFullPath, $projectAttributes['path'], $projectId, null); + } +} + + +function syncDirectoryRecursive($fullFolderPath, $relativeFolderPath, $projectId, $parentDirId) +{ + $entries = scandir($fullFolderPath); + + foreach ($entries as $entry) { + // skip hidden files/folders (this also covers "." and "..") + if ($entry[0] === '.') { + continue; + } + + $relativePath = $relativeFolderPath . "/" . $entry; + $fullPath = $fullFolderPath . "/" . $entry; + + if (is_dir($fullPath)) { + // skip folders named "run*" (e.g. "run001") + if (fnmatch('run*', $entry)) { + continue; + } + + // ensure this subdirectory itself is registered, then recurse into it + $subDirId = ensureDirRegistered($relativePath, $projectId, $parentDirId); + syncDirectoryRecursive($fullPath, $relativePath, $projectId, $subDirId); + } else { + ensureFileRegistered($relativePath, $fullPath, $projectId, $parentDirId); + } + } +} + +function ensureDirRegistered($relativePath, $projectId, $parentDirId) +{ + $existingId = getGSFileId_fromPath($relativePath); + if ($existingId) { + return $existingId; + } + + $dirId = createLabel(); + + $mongoDirDocument = array( + '_id' => $dirId, + 'mtime' => new MongoDB\BSON\UTCDateTime(strtotime("now") * 1000), + 'owner' => $_SESSION['User']['id'], + 'path' => $relativePath, + 'project' => $projectId, + 'parentDir' => $parentDirId, + 'type' => "dir", + 'files' => [] + ); + + $GLOBALS['filesCol']->updateOne( + ['_id' => $dirId], + ['$set' => $mongoDirDocument], + ['upsert' => true] + ); + + if ($parentDirId) { + $GLOBALS['filesCol']->updateOne( + ['_id' => $parentDirId], + ['$addToSet' => ['files' => $dirId]] + ); + } + + return $dirId; +} + +function ensureFileRegistered($relativePath, $fullPath, $projectId, $parentDirId) +{ + if (getGSFileId_fromPath($relativePath)) { + return; // already registered + } + + $fileId = createLabel(); + + $mongoFileDocument = array( + '_id' => $fileId, + 'mtime' => new MongoDB\BSON\UTCDateTime(strtotime("now") * 1000), + 'owner' => $_SESSION['User']['id'], + 'size' => filesize($fullPath), + 'path' => $relativePath, + 'project' => $projectId, + 'parentDir' => $parentDirId, + 'type' => "file" + ); + + $mongoFileMetadataDocument = array( + '_id' => $fileId, + 'compressed' => false, + 'data_type' => null, + 'format' => null, + 'validated' => true, + 'visible' => true + ); + + $GLOBALS['filesCol']->updateOne( + ['_id' => $fileId], + ['$set' => $mongoFileDocument], + ['upsert' => true] + ); + + $GLOBALS['filesMetaCol']->updateOne( + ['_id' => $fileId], + ['$set' => $mongoFileMetadataDocument], + ['upsert' => true] + ); + + if ($parentDirId) { + $GLOBALS['filesCol']->updateOne( + ['_id' => $parentDirId], + ['$addToSet' => ['files' => $fileId]] + ); + } +} diff --git a/front_end/openVRE/public/user/usrProfile.php b/front_end/openVRE/public/user/usrProfile.php index e5eebac4..63411302 100644 --- a/front_end/openVRE/public/user/usrProfile.php +++ b/front_end/openVRE/public/user/usrProfile.php @@ -3,6 +3,10 @@ require __DIR__ . "/../../config/bootstrap.php"; redirectOutside(); +$includeHpc = false; +$includeEgaFiles = false; +$includeOpenStack = false; + ?> @@ -396,78 +400,104 @@ class="icon-question tooltips" data-container="body" class="caption-subject font-blue-madison bold uppercase">Linked Accounts - + +
+
HPC resources access (via SSH)
+ +
+
+
- + + Do you have an account to an HPC facility? + + Link it and you'll be able to launch jobs there. Data will + be transferred via SSH, from/to your HPC home directory. +
+
+
+
+
+
+
+
+
- +
-
-
- -
+ - -
+
+
+ +
-
- + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ + +
-
@@ -489,10 +519,12 @@ class="btn green"> Link
- + + + + +
@@ -564,7 +596,8 @@ class="btn green"> Link

-
+