FOSSology  4.4.0
Open Source License Compliance by Open Source Software
JobController.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2018 Siemens AG
4  Author: Gaurav Mishra <mishra.gaurav@siemens.com>
5  SPDX-FileCopyrightText: © 2022 Samuel Dushimimana <dushsam100@gmail.com>
6 
7  SPDX-License-Identifier: GPL-2.0-only
8 */
14 namespace Fossology\UI\Api\Controllers;
15 
29 use Psr\Http\Message\ServerRequestInterface;
30 use Slim\Psr7\Request;
31 
37 {
41  const UPLOAD_PARAM = "upload";
45  const JOB_COMPLETED = 0x1 << 1;
49  const JOB_STARTED = 0x1 << 2;
53  const JOB_QUEUED = 0x1 << 3;
57  const JOB_FAILED = 0x1 << 4;
58 
68  public function getAllJobs($request, $response, $args)
69  {
70  $apiVersion = ApiVersion::getVersion($request);
71  $this->throwNotAdminException();
72 
73  $queryParams = $request->getQueryParams();
74  $query = $apiVersion == ApiVersion::V2 ? $queryParams : array_map(function($header) {
75  return implode(",", $header);
76  }, $request->getHeaders());
77 
78  $limit = isset($query['limit']) ? intval($query['limit']) : 0;
79  $page = isset($query['page']) ? intval($query['page']) : 1;
80  $sort = $queryParams['sort'] ?? "ASC";
81  $status = $queryParams['status'] ?? null;
82 
83  if ($limit < 0 || $page < 1) {
84  throw new HttpBadRequestException("Limit and page cannot be smaller than 1 and has to be numeric!");
85  }
86 
87  return $this->getAllResults(null, $status, $request, $response, $sort, $limit, $page, $apiVersion);
88  }
89 
99  public function getJobs($request, $response, $args)
100  {
101  $apiVersion = ApiVersion::getVersion($request);
102  $userId = $this->restHelper->getUserId();
103 
104  $queryParams = $request->getQueryParams();
105  $query = $apiVersion == ApiVersion::V2 ? $queryParams : array_map(function($header) {
106  return implode(",", $header);
107  }, $request->getHeaders());
108 
109  $limit = isset($query['limit']) ? intval($query['limit']) : 0;
110  $page = isset($query['page']) ? intval($query['page']) : 1;
111  $sort = $queryParams['sort'] ?? "ASC";
112  $status = $queryParams['status'] ?? null;
113 
114  if ($limit < 0 || $page < 1) {
115  throw new HttpBadRequestException("Limit and page cannot be smaller than 1 and has to be numeric!");
116  }
117 
118  $id = isset($args['id']) ? intval($args['id']) : null;
119  if ($id !== null && !$this->dbHelper->doesIdExist("job", "job_pk", $id)) {
120  throw new HttpNotFoundException("Job id " . $id . " doesn't exist");
121  }
122 
123  if ($id !== null) {
124  /* If the ID is passed, don't check for upload */
125  return $this->getAllResults($id, $status, $request, $response, $sort, $limit, $page, $apiVersion);
126  }
127 
128  if (array_key_exists(self::UPLOAD_PARAM, $queryParams)) {
129  /* If the upload is passed, filter accordingly */
130  return $this->getFilteredResults(intval($queryParams[self::UPLOAD_PARAM]),
131  $status, $request, $response, $sort, $limit, $page, $apiVersion);
132  }
133 
134  /* Otherwise return all jobs for the current user */
135  return $this->getAllUserResults($userId, $status, $request, $response, $sort, $limit, $page, $apiVersion);
136  }
137 
147  public function createJob($request, $response, $args)
148  {
149  $apiVersion = ApiVersion::getVersion($request);
150  $folder = null;
151  $upload = null;
152  if ($apiVersion == ApiVersion::V2) {
153  $query = $request->getQueryParams();
154  $folder = $query["folderId"] ?? null;
155  $upload = $query["uploadId"] ?? null;
156  } else {
157  $folder = $request->hasHeader('folderId') ? $request->getHeaderLine('folderId') : null;
158  $upload = $request->hasHeader('uploadId') ? $request->getHeaderLine('uploadId') : null;
159  }
160  if (is_numeric($folder) && is_numeric($upload) && $folder > 0 && $upload > 0) {
161  $scanOptionsJSON = $this->getParsedBody($request);
162  if (empty($scanOptionsJSON)) {
163  throw new HttpBadRequestException("No agents selected!");
164  }
165  $uploadHelper = new UploadHelper();
166  $info = $uploadHelper->handleScheduleAnalysis($upload, $folder,
167  $scanOptionsJSON, false, $apiVersion);
168  return $response->withJson($info->getArray(), $info->getCode());
169  }
170  throw new HttpBadRequestException(
171  "Folder id and upload id should be integers!");
172  }
173 
184  public function deleteJob($request, $response, $args)
185  {
186  $userId = $this->restHelper->getUserId();
187  $userName = $this->restHelper->getUserDao()->getUserName($userId);
188 
189  /* Check if the job exists */
190  $jobId = intval($args['id']);
191  if (! $this->dbHelper->doesIdExist("job", "job_pk", $jobId)) {
192  throw new HttpNotFoundException("Job id " . $jobId . " doesn't exist");
193  }
194 
195  /* Check if user has permission to delete this job*/
196  $canDeleteJob = $this->restHelper->getJobDao()->hasActionPermissionsOnJob($jobId, $userId, $this->restHelper->getGroupId());
197  if (! $canDeleteJob) {
198  throw new HttpForbiddenException(
199  "You don't have permission to delete this job.");
200  }
201 
202  $queueId = $args['queue'];
203 
204  /* Get Jobs that depend on the job to be deleted */
205  $JobQueue = $this->restHelper->getShowJobDao()->getJobInfo([$jobId])[$jobId]["jobqueue"];
206 
207  if (!array_key_exists($queueId, $JobQueue)) {
208  throw new HttpNotFoundException(
209  "Job queue " . $queueId . " doesn't exist in Job " . $jobId);
210  }
211 
212  $dependentJobs = [];
213  $dependentJobs[] = $queueId;
214 
215  foreach ($JobQueue as $job) {
216  if (in_array($queueId, $job["depends"])) {
217  $dependentJobs[] = $job["jq_pk"];
218  }
219  }
220 
221  /* Delete All jobs in dependentJobs */
222  foreach ($dependentJobs as $job) {
223  $Msg = "\"" . _("Killed by") . " " . $userName . "\"";
224  $command = "kill $job $Msg";
225  $rv = fo_communicate_with_scheduler($command, $response_from_scheduler, $error_info);
226  if (!$rv) {
228  "Failed to kill job $jobId");
229  }
230  }
231  $returnVal = new Info(200, "Job deleted successfully", InfoType::INFO);
232  return $response->withJson($returnVal->getArray(), $returnVal->getCode());
233  }
234 
248  private function getAllUserResults($userId, $status, $request, $response, $sort, $limit, $page, $apiVersion)
249  {
250  list($jobs, $count) = $this->dbHelper->getUserJobs($userId, $status, $sort, $limit, $page);
251  $finalJobs = [];
252  foreach ($jobs as $job) {
253  $this->updateEta($job);
254  if ($apiVersion == ApiVersion::V2) {
255  $this->addJobQueue($job, $request);
256  }
257  $finalJobs[] = $job->getArray($apiVersion);
258  }
259  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
260  }
261 
275  private function getAllResults($id, $status, $request, $response, $sort, $limit, $page, $apiVersion)
276  {
277  list($jobs, $count) = $this->dbHelper->getJobs($id, $status, $sort, $limit, $page, null);
278  $finalJobs = [];
279  foreach ($jobs as $job) {
280  $this->updateEta($job);
281  if ($apiVersion == ApiVersion::V2) {
282  $this->addJobQueue($job, $request);
283  }
284  $finalJobs[] = $job->getArray($apiVersion);
285  }
286  if ($id !== null) {
287  $finalJobs = $finalJobs[0];
288  }
289  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
290  }
291 
306  private function getFilteredResults($uploadId, $status, $request, $response, $sort, $limit, $page, $apiVersion)
307  {
308  if (! $this->dbHelper->doesIdExist("upload", "upload_pk", $uploadId)) {
309  throw new HttpNotFoundException("Upload id " . $uploadId .
310  " doesn't exist");
311  }
312  list($jobs, $count) = $this->dbHelper->getJobs(null, $status, $sort, $limit, $page, $uploadId);
313  $finalJobs = [];
314  foreach ($jobs as $job) {
315  $this->updateEta($job);
316  if ($apiVersion == ApiVersion::V2) {
317  $this->addJobQueue($job, $request);
318  }
319  $finalJobs[] = $job->getArray($apiVersion);
320  }
321  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
322  }
323 
329  private function updateEta(&$job)
330  {
331  $job->setEta($this->getUploadEtaInSeconds($job->getId(),
332  $job->getUploadId()));
333  }
334 
342  private function getUploadEtaInSeconds($jobId, $uploadId)
343  {
344  $showJobDao = $this->restHelper->getShowJobDao();
345  $eta = $showJobDao->getEstimatedTime($jobId, '', 0, $uploadId);
346  $eta = explode(":", $eta);
347  if (count($eta) > 1) {
348  $eta = (intval($eta[0]) * 3600) + (intval($eta[1]) * 60) + intval($eta[2]);
349  } else {
350  $eta = 0;
351  }
352  return $eta;
353  }
354 
364  public function getJobsHistory($request, $response, $args)
365  {
366  $query = $request->getQueryParams();
367  if (!array_key_exists(self::UPLOAD_PARAM, $query)) {
368  throw new HttpBadRequestException("'upload' is a required query param");
369  }
370  $upload_fk = intval($query[self::UPLOAD_PARAM]);
371  // checking if the upload exists and if yes, whether it is accessible
372  $this->uploadAccessible($upload_fk);
373 
378  $dbManager = $this->dbHelper->getDbManager();
379 
380  // getting all the jobs from the DB for the upload id
381  $query = "SELECT job_pk FROM job WHERE job_upload_fk=$1;";
382  $statement = __METHOD__.".getJobs";
383  $result = $dbManager->getRows($query, [$upload_fk], $statement);
384 
385  // creating a list of all the job_pks
386  $allJobPks = array_column($result, 'job_pk');
387 
388  // getting the show jobs data for each job
389  $showJobData = $this->getJobQueue($allJobPks);
390 
391  // creating the response structure
392  $allJobsHistory = array();
393  foreach ($showJobData as $jobValObj) {
394  $finalJobqueue = array();
395  foreach ($jobValObj['job']['jobQueue'] as $jqVal) {
396  $depends = [];
397  if ($jqVal['depends'][0] != null) {
398  $depends = $jqVal['depends'];
399  }
400  $download = null;
401  if (!empty($jqVal['download'])) {
402  $download = [
403  "text" => $jqVal["download"],
404  "link" => ReportController::buildDownloadPath($request,
405  $jqVal['jq_job_fk'])
406  ];
407  }
408  $jobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
409  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
410  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
411  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
412  $jqVal['isReady'], $download);
413  $finalJobqueue[] = $jobQueue->getArray();
414  }
415  $job = new ShowJob($jobValObj['job']['jobId'],
416  $jobValObj['job']['jobName'], $finalJobqueue,
417  $jobValObj['upload']['uploadId']);
418  $allJobsHistory[] = $job->getArray();
419  }
420  return $response->withJson($allJobsHistory, 200);
421  }
422 
429  private function getJobQueue($allJobPks)
430  {
431  $showJobsDao = $this->restHelper->getShowJobDao();
432  $jobsInfo = $showJobsDao->getJobInfo($allJobPks);
433  usort($jobsInfo, [$this, "compareJobsInfo"]);
434 
439  $ajaxShowJobs = $this->restHelper->getPlugin('ajaxShowJobs');
440  $showJobData = $ajaxShowJobs->getShowJobsForEachJob($jobsInfo, true);
441 
442  return $showJobData;
443  }
444 
451  private function addJobQueue(&$job, $request = null)
452  {
453  $jobQueue = $this->getJobQueue([$job->getId()]);
454  $finalJobqueue = array();
455  foreach ($jobQueue[0]['job']['jobQueue'] as $jqVal) {
456  $depends = [];
457  if ($jqVal['depends'][0] != null) {
458  $depends = $jqVal['depends'];
459  }
460  $download = null;
461  if (!empty($jqVal['download'])) {
462  $download = [
463  "text" => $jqVal["download"],
464  "link" => ReportController::buildDownloadPath($request,
465  $jqVal['jq_job_fk'])
466  ];
467  }
468  $singleJobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
469  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
470  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
471  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
472  $jqVal['isReady'], $download);
473  $finalJobqueue[] = $singleJobQueue->getArray();
474  }
475  $job->setJobQueue($finalJobqueue);
476  }
477 
484  private function compareJobsInfo($JobsInfo1, $JobsInfo2)
485  {
486  return $JobsInfo2["job"]["job_pk"] - $JobsInfo1["job"]["job_pk"];
487  }
488 
497  public function getJobStatistics($request, $response, $args)
498  {
499  $this->throwNotAdminException();
501  $statisticsPlugin = $this->restHelper->getPlugin('dashboard-statistics');
502  $res = $statisticsPlugin->CountAllJobs(true);
503  return $response->withJson($res, 200);
504  }
505 
515  public function getAllServerJobsStatus($request, $response, $args)
516  {
517  $this->throwNotAdminException();
519  $allJobStatusPlugin = $this->restHelper->getPlugin('ajax_all_job_status');
520  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
521  $res = $allJobStatusPlugin->handle($symfonyRequest);
522  return $response->withJson(json_decode($res->getContent(), true), 200);
523  }
524 
533  public function getSchedulerJobOptionsByOperation($request, $response, $args)
534  {
535  $this->throwNotAdminException();
536  $operation = $args['operationName'];
538  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
539 
540  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
541  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
542  throw new HttpBadRequestException("Operation '$operation' not allowed." .
543  " Allowed operations are: $allowedOperations");
544  }
545 
547  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
548  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
549  $symfonyRequest->request->set('operation', $operation);
550  $symfonyRequest->request->set('fromRest', true);
551  $res = $schedulerPlugin->handle($symfonyRequest);
552  return $response->withJson($res, 200);
553  }
554 
564  public function handleRunSchedulerOption($request, $response, $args)
565  {
566  $this->throwNotAdminException();
567  $body = $this->getParsedBody($request);
568  $query = $request->getQueryParams();
569 
570  $operation = $body['operation'];
572  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
573 
574  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
575  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
576  throw new HttpBadRequestException("Operation '$operation' not allowed." .
577  " Allowed operations are: $allowedOperations");
578  }
579 
581  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
582  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
583  $symfonyRequest->request->set('operation', $operation);
584  $symfonyRequest->request->set('fromRest', true);
585  $data = $schedulerPlugin->handle($symfonyRequest);
586 
587  if ($operation == 'status' || $operation == 'verbose') {
588  if (!isset($query['job']) || !in_array($query['job'], $data['jobList'])) {
589  $allowedJobs = implode(', ', $data['jobList']);
590  throw new HttpBadRequestException("Job '{$query['job']}' not " .
591  "allowed. Allowed jobs are: $allowedJobs");
592  }
593  if (($operation == 'verbose') && (!isset($query['level']) || !in_array($query['level'], $data['verboseList']))) {
594  $allowedLevels = implode(', ', $data['verboseList']);
595  throw new HttpBadRequestException("Level '{$query['level']}' not " .
596  "allowed. Allowed levels are: $allowedLevels");
597  }
598  } elseif ($operation == 'priority' && (!isset($query['priority']) || !in_array($query['priority'], $data['priorityList']))) {
599  $allowedPriorities = implode(', ', $data['priorityList']);
600  throw new HttpBadRequestException("Priority '{$query['priority']}' not " .
601  "allowed. Allowed priorities are: $allowedPriorities");
602  }
603 
604  if ($operation == 'status') {
605  $query['priority'] = null;
606  $query['level'] = null;
607  } else if ($operation == 'priority') {
608  $query['job'] = null;
609  $query['level'] = null;
610  } else if ($operation == 'verbose') {
611  $query['priority'] = null;
612  } else {
613  $query['job'] = null;
614  $query['priority'] = null;
615  $query['level'] = null;
616  }
617 
618  $response_from_scheduler = $adminSchedulerPlugin->OperationSubmit(
619  $operation, array_search($query['job'], $data['jobList']),
620  $query['priority'], $query['level']);
621  $operation_text = $adminSchedulerPlugin->GetOperationText($operation);
622  $status_msg = "";
623  $report = "";
624 
625  if (!empty($adminSchedulerPlugin->error_info)) {
626  $text = _("failed");
627  $status_msg .= "$operation_text $text.";
628  throw new HttpInternalServerErrorException($status_msg . $report);
629  }
630  $text = _("successfully");
631  $status_msg .= "$operation_text $text.";
632  if (! empty($response_from_scheduler)) {
633  $report .= $response_from_scheduler;
634  }
635 
636  $info = new Info(200, $status_msg. $report, InfoType::INFO);
637  return $response->withJson($info->getArray(), $info->getCode());
638  }
639 }
createJob($request, $response, $args)
getJobs($request, $response, $args)
getFilteredResults($uploadId, $status, $request, $response, $sort, $limit, $page, $apiVersion)
getAllResults($id, $status, $request, $response, $sort, $limit, $page, $apiVersion)
deleteJob($request, $response, $args)
compareJobsInfo($JobsInfo1, $JobsInfo2)
Sort compare function to order $JobsInfo by job_pk.
getAllUserResults($userId, $status, $request, $response, $sort, $limit, $page, $apiVersion)
getAllJobs($request, $response, $args)
Base controller for REST calls.
getParsedBody(ServerRequestInterface $request)
Parse request body as JSON and return associative PHP array.
Override Slim response for withJson function.
Handle new file uploads from Slim framework and move to FOSSology.
static getVersion(ServerRequestInterface $request)
Definition: ApiVersion.php:29
Different type of infos provided by REST.
Definition: InfoType.php:16
Info model to contain general error and return values.
Definition: Info.php:19
Model class to hold JobQueue info.
Definition: JobQueue.php:18
Model class to hold ShowJob info.
Definition: ShowJob.php:18
fo_communicate_with_scheduler($input, &$output, &$error_msg)
Communicate with scheduler, send commands to the scheduler, then get the output.