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 
30 use Psr\Http\Message\ServerRequestInterface;
31 use Slim\Psr7\Request;
32 
38 {
42  const UPLOAD_PARAM = "upload";
46  const JOB_COMPLETED = 0x1 << 1;
50  const JOB_STARTED = 0x1 << 2;
54  const JOB_QUEUED = 0x1 << 3;
58  const JOB_FAILED = 0x1 << 4;
59 
69  public function getAllJobs($request, $response, $args)
70  {
71  $apiVersion = ApiVersion::getVersion($request);
72  $this->throwNotAdminException();
73  $id = null;
74  $limit = 0;
75  $page = 1;
76  if ($apiVersion == ApiVersion::V2) {
77  $query = $request->getQueryParams();
78  $limit = $query['limit'] ?? 0;
79  $page = $query['page'] ?? 1;
80  } else {
81  $limit = $request->hasHeader('limit') ? $request->getHeaderLine('limit') : 0;
82  $page = $request->hasHeader('page') ? $request->getHeaderLine('page') : 1;
83  }
84  if (empty($page)) {
85  $page = 1;
86  }
87  if ((! is_numeric($limit) || $limit < 0) ||
88  (! is_numeric($page) || $page < 1)) {
89  throw new HttpBadRequestException(
90  "Limit and page cannot be smaller than 1 and has to be numeric!");
91  }
92 
93  return $this->getAllResults($id, $request, $response, $limit, $page, $apiVersion);
94  }
95 
105  public function getJobs($request, $response, $args)
106  {
107  $apiVersion = ApiVersion::getVersion($request);
108  $query = $request->getQueryParams();
109  $userId = $this->restHelper->getUserId();
110  $limit = 0;
111  $page = 1;
112  if ($apiVersion == ApiVersion::V2) {
113  $limit = $query['limit'] ?? 0;
114  $page = $query['page'] ?? 1;
115  } else {
116  $limit = $request->hasHeader('limit') ? $request->getHeaderLine('limit') : 0;
117  $page = $request->hasHeader('page') ? $request->getHeaderLine('page') : 1;
118  }
119  if (empty($page)) {
120  $page = 1;
121  }
122  if ((! is_numeric($limit) || $limit < 0) ||
123  (! is_numeric($page) || $page < 1)) {
124  throw new HttpBadRequestException(
125  "Limit and page cannot be smaller than 1 and has to be numeric!");
126  }
127 
128  $id = null;
129  if (isset($args['id'])) {
130  $id = intval($args['id']);
131  if (! $this->dbHelper->doesIdExist("job", "job_pk", $id)) {
132  throw new HttpNotFoundException("Job id " . $id . " doesn't exist");
133  }
134  }
135 
136  if ($id !== null) {
137  /* If the ID is passed, don't check for upload */
138  return $this->getAllResults($id, $request, $response, $limit, $page, $apiVersion);
139  }
140 
141  if (array_key_exists(self::UPLOAD_PARAM, $query)) {
142  /* If the upload is passed, filter accordingly */
143  return $this->getFilteredResults(intval($query[self::UPLOAD_PARAM]),
144  $request, $response, $limit, $page, $apiVersion);
145  } else {
146  $id = null;
147  return $this->getAllUserResults($id, $userId, $request, $response, $limit, $page, $apiVersion);
148  }
149  }
150 
160  public function createJob($request, $response, $args)
161  {
162  $apiVersion = ApiVersion::getVersion($request);
163  $folder = null;
164  $upload = null;
165  if ($apiVersion == ApiVersion::V2) {
166  $query = $request->getQueryParams();
167  $folder = $query["folderId"] ?? null;
168  $upload = $query["uploadId"] ?? null;
169  } else {
170  $folder = $request->hasHeader('folderId') ? $request->getHeaderLine('folderId') : null;
171  $upload = $request->hasHeader('uploadId') ? $request->getHeaderLine('uploadId') : null;
172  }
173  if (is_numeric($folder) && is_numeric($upload) && $folder > 0 && $upload > 0) {
174  $scanOptionsJSON = $this->getParsedBody($request);
175  if (empty($scanOptionsJSON)) {
176  throw new HttpBadRequestException("No agents selected!");
177  }
178  $uploadHelper = new UploadHelper();
179  $info = $uploadHelper->handleScheduleAnalysis($upload, $folder,
180  $scanOptionsJSON, false, $apiVersion);
181  return $response->withJson($info->getArray(), $info->getCode());
182  }
183  throw new HttpBadRequestException(
184  "Folder id and upload id should be integers!");
185  }
186 
197  public function deleteJob($request, $response, $args)
198  {
199  $userId = $this->restHelper->getUserId();
200  $userName = $this->restHelper->getUserDao()->getUserName($userId);
201 
202  /* Check if the job exists */
203  $jobId = intval($args['id']);
204  if (! $this->dbHelper->doesIdExist("job", "job_pk", $jobId)) {
205  throw new HttpNotFoundException("Job id " . $jobId . " doesn't exist");
206  }
207 
208  /* Check if user has permission to delete this job*/
209  $canDeleteJob = $this->restHelper->getJobDao()->hasActionPermissionsOnJob($jobId, $userId, $this->restHelper->getGroupId());
210  if (! $canDeleteJob) {
211  throw new HttpForbiddenException(
212  "You don't have permission to delete this job.");
213  }
214 
215  $queueId = $args['queue'];
216 
217  /* Get Jobs that depend on the job to be deleted */
218  $JobQueue = $this->restHelper->getShowJobDao()->getJobInfo([$jobId])[$jobId]["jobqueue"];
219 
220  if (!array_key_exists($queueId, $JobQueue)) {
221  throw new HttpNotFoundException(
222  "Job queue " . $queueId . " doesn't exist in Job " . $jobId);
223  }
224 
225  $dependentJobs = [];
226  $dependentJobs[] = $queueId;
227 
228  foreach ($JobQueue as $job) {
229  if (in_array($queueId, $job["depends"])) {
230  $dependentJobs[] = $job["jq_pk"];
231  }
232  }
233 
234  /* Delete All jobs in dependentJobs */
235  foreach ($dependentJobs as $job) {
236  $Msg = "\"" . _("Killed by") . " " . $userName . "\"";
237  $command = "kill $job $Msg";
238  $rv = fo_communicate_with_scheduler($command, $response_from_scheduler, $error_info);
239  if (!$rv) {
241  "Failed to kill job $jobId");
242  }
243  }
244  $returnVal = new Info(200, "Job deleted successfully", InfoType::INFO);
245  return $response->withJson($returnVal->getArray(), $returnVal->getCode());
246  }
247 
260  private function getAllUserResults($id, $uid, $request, $response, $limit, $page, $apiVersion)
261  {
262  list($jobs, $count) = $this->dbHelper->getUserJobs($id, $uid, $limit, $page);
263  $finalJobs = [];
264  foreach ($jobs as $job) {
265  $this->updateEtaAndStatus($job);
266  if ($apiVersion == ApiVersion::V2) {
267  $this->addJobQueue($job, $request);
268  }
269  $finalJobs[] = $job->getArray($apiVersion);
270  }
271  if ($id !== null) {
272  $finalJobs = $finalJobs[0];
273  } else {
274  usort($finalJobs, [$this, "sortJobsByDate"]);
275  }
276  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
277  }
278 
290  private function getAllResults($id, $request, $response, $limit, $page, $apiVersion)
291  {
292  list($jobs, $count) = $this->dbHelper->getJobs($id, $limit, $page);
293  $finalJobs = [];
294  foreach ($jobs as $job) {
295  $this->updateEtaAndStatus($job);
296  if ($apiVersion == ApiVersion::V2) {
297  $this->addJobQueue($job, $request);
298  }
299  $finalJobs[] = $job->getArray($apiVersion);
300  }
301  if ($id !== null) {
302  $finalJobs = $finalJobs[0];
303  } else {
304  usort($finalJobs, [$this, "sortJobsByDate"]);
305  }
306  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
307  }
308 
321  private function getFilteredResults($uploadId, $request, $response, $limit, $page, $apiVersion)
322  {
323  if (! $this->dbHelper->doesIdExist("upload", "upload_pk", $uploadId)) {
324  throw new HttpNotFoundException("Upload id " . $uploadId .
325  " doesn't exist");
326  }
327  list($jobs, $count) = $this->dbHelper->getJobs(null, $limit, $page, $uploadId);
328  $finalJobs = [];
329  foreach ($jobs as $job) {
330  $this->updateEtaAndStatus($job);
331  if ($apiVersion == ApiVersion::V2) {
332  $this->addJobQueue($job, $request);
333  }
334  $finalJobs[] = $job->getArray($apiVersion);
335  }
336  usort($finalJobs, [$this, "sortJobsByDate"]);
337  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
338  }
339 
345  private function updateEtaAndStatus(&$job)
346  {
347  $jobDao = $this->restHelper->getJobDao();
348 
349  $jobqueue = [];
350  $jobqueue = $jobDao->getChlidJobStatus($job->getId());
351 
352  $job->setEta($this->getUploadEtaInSeconds($job->getId(),
353  $job->getUploadId()));
354 
355  $job->setStatus($this->getJobStatus(array_keys($jobqueue)));
356  }
357 
365  private function getUploadEtaInSeconds($jobId, $uploadId)
366  {
367  $showJobDao = $this->restHelper->getShowJobDao();
368  $eta = $showJobDao->getEstimatedTime($jobId, '', 0, $uploadId);
369  $eta = explode(":", $eta);
370  if (count($eta) > 1) {
371  $eta = (intval($eta[0]) * 3600) + (intval($eta[1]) * 60) + intval($eta[2]);
372  } else {
373  $eta = 0;
374  }
375  return $eta;
376  }
377 
384  private function getJobStatus($jobqueue)
385  {
386  $showJobDao = $this->restHelper->getShowJobDao();
387  $jobStatus = 0;
388  /* Check each job in queue */
389  foreach ($jobqueue as $jobId) {
390  $jobInfo = $showJobDao->getDataForASingleJob($jobId);
391  $endtext = $jobInfo['jq_endtext'];
392  switch ($endtext) {
393  case 'Completed':
394  $jobStatus |= self::JOB_COMPLETED;
395  break;
396  case 'Started':
397  case 'Restarted':
398  case 'Paused':
399  $jobStatus |= self::JOB_STARTED;
400  break;
401  default:
402  if (empty($jobInfo['jq_endtime'])) {
403  $jobStatus |= self::JOB_QUEUED;
404  } else {
405  $jobStatus |= self::JOB_FAILED;
406  }
407  }
408  }
409 
410  $jobStatusString = "";
411  if ($jobStatus & self::JOB_FAILED) {
412  /* If at least one job is failed, set status as failed */
413  $jobStatusString = "Failed";
414  } else if ($jobStatus & self::JOB_STARTED) {
415  /* If at least one job is started, set status as processing */
416  $jobStatusString = "Processing";
417  } else if ($jobStatus & self::JOB_QUEUED) {
418  $jobStatusString = "Queued";
419  } else {
420  /* If everything completed successfully, set status as completed */
421  $jobStatusString = "Completed";
422  }
423  return $jobStatusString;
424  }
425 
435  public function getJobsHistory($request, $response, $args)
436  {
437  $query = $request->getQueryParams();
438  if (!array_key_exists(self::UPLOAD_PARAM, $query)) {
439  throw new HttpBadRequestException("'upload' is a required query param");
440  }
441  $upload_fk = intval($query[self::UPLOAD_PARAM]);
442  // checking if the upload exists and if yes, whether it is accessible
443  $this->uploadAccessible($upload_fk);
444 
449  $dbManager = $this->dbHelper->getDbManager();
450 
451  // getting all the jobs from the DB for the upload id
452  $query = "SELECT job_pk FROM job WHERE job_upload_fk=$1;";
453  $statement = __METHOD__.".getJobs";
454  $result = $dbManager->getRows($query, [$upload_fk], $statement);
455 
456  // creating a list of all the job_pks
457  $allJobPks = array_column($result, 'job_pk');
458 
459  // getting the show jobs data for each job
460  $showJobData = $this->getJobQueue($allJobPks);
461 
462  // creating the response structure
463  $allJobsHistory = array();
464  foreach ($showJobData as $jobValObj) {
465  $finalJobqueue = array();
466  foreach ($jobValObj['job']['jobQueue'] as $jqVal) {
467  $depends = [];
468  if ($jqVal['depends'][0] != null) {
469  $depends = $jqVal['depends'];
470  }
471  $download = null;
472  if (!empty($jqVal['download'])) {
473  $download = [
474  "text" => $jqVal["download"],
475  "link" => ReportController::buildDownloadPath($request,
476  $jqVal['jq_job_fk'])
477  ];
478  }
479  $jobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
480  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
481  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
482  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
483  $jqVal['isReady'], $download);
484  $finalJobqueue[] = $jobQueue->getArray();
485  }
486  $job = new ShowJob($jobValObj['job']['jobId'],
487  $jobValObj['job']['jobName'], $finalJobqueue,
488  $jobValObj['upload']['uploadId']);
489  $allJobsHistory[] = $job->getArray();
490  }
491  return $response->withJson($allJobsHistory, 200);
492  }
493 
500  private function getJobQueue($allJobPks)
501  {
502  $showJobsDao = $this->restHelper->getShowJobDao();
503  $jobsInfo = $showJobsDao->getJobInfo($allJobPks);
504  usort($jobsInfo, [$this, "compareJobsInfo"]);
505 
510  $ajaxShowJobs = $this->restHelper->getPlugin('ajaxShowJobs');
511  $showJobData = $ajaxShowJobs->getShowJobsForEachJob($jobsInfo, true);
512 
513  return $showJobData;
514  }
515 
522  private function addJobQueue(&$job, $request = null)
523  {
524  $jobQueue = $this->getJobQueue([$job->getId()]);
525  $finalJobqueue = array();
526  foreach ($jobQueue[0]['job']['jobQueue'] as $jqVal) {
527  $depends = [];
528  if ($jqVal['depends'][0] != null) {
529  $depends = $jqVal['depends'];
530  }
531  $download = null;
532  if (!empty($jqVal['download'])) {
533  $download = [
534  "text" => $jqVal["download"],
535  "link" => ReportController::buildDownloadPath($request,
536  $jqVal['jq_job_fk'])
537  ];
538  }
539  $singleJobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
540  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
541  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
542  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
543  $jqVal['isReady'], $download);
544  $finalJobqueue[] = $singleJobQueue->getArray();
545  }
546  $job->setJobQueue($finalJobqueue);
547  }
548 
555  private function compareJobsInfo($JobsInfo1, $JobsInfo2)
556  {
557  return $JobsInfo2["job"]["job_pk"] - $JobsInfo1["job"]["job_pk"];
558  }
559 
566  private function sortJobsByDate($job1, $job2)
567  {
568  return strtotime($job2['queueDate']) - strtotime($job1['queueDate']);
569  }
570 
579  public function getJobStatistics($request, $response, $args)
580  {
581  $this->throwNotAdminException();
583  $statisticsPlugin = $this->restHelper->getPlugin('dashboard-statistics');
584  $res = $statisticsPlugin->CountAllJobs(true);
585  return $response->withJson($res, 200);
586  }
587 
597  public function getAllServerJobsStatus($request, $response, $args)
598  {
599  $this->throwNotAdminException();
601  $allJobStatusPlugin = $this->restHelper->getPlugin('ajax_all_job_status');
602  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
603  $res = $allJobStatusPlugin->handle($symfonyRequest);
604  return $response->withJson(json_decode($res->getContent(), true), 200);
605  }
606 
615  public function getSchedulerJobOptionsByOperation($request, $response, $args)
616  {
617  $this->throwNotAdminException();
618  $operation = $args['operationName'];
620  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
621 
622  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
623  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
624  throw new HttpBadRequestException("Operation '$operation' not allowed." .
625  " Allowed operations are: $allowedOperations");
626  }
627 
629  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
630  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
631  $symfonyRequest->request->set('operation', $operation);
632  $symfonyRequest->request->set('fromRest', true);
633  $res = $schedulerPlugin->handle($symfonyRequest);
634  return $response->withJson($res, 200);
635  }
636 
646  public function handleRunSchedulerOption($request, $response, $args)
647  {
648  $this->throwNotAdminException();
649  $body = $this->getParsedBody($request);
650  $query = $request->getQueryParams();
651 
652  $operation = $body['operation'];
654  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
655 
656  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
657  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
658  throw new HttpBadRequestException("Operation '$operation' not allowed." .
659  " Allowed operations are: $allowedOperations");
660  }
661 
663  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
664  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
665  $symfonyRequest->request->set('operation', $operation);
666  $symfonyRequest->request->set('fromRest', true);
667  $data = $schedulerPlugin->handle($symfonyRequest);
668 
669  if ($operation == 'status' || $operation == 'verbose') {
670  if (!isset($query['job']) || !in_array($query['job'], $data['jobList'])) {
671  $allowedJobs = implode(', ', $data['jobList']);
672  throw new HttpBadRequestException("Job '{$query['job']}' not " .
673  "allowed. Allowed jobs are: $allowedJobs");
674  }
675  if (($operation == 'verbose') && (!isset($query['level']) || !in_array($query['level'], $data['verboseList']))) {
676  $allowedLevels = implode(', ', $data['verboseList']);
677  throw new HttpBadRequestException("Level '{$query['level']}' not " .
678  "allowed. Allowed levels are: $allowedLevels");
679  }
680  } elseif ($operation == 'priority' && (!isset($query['priority']) || !in_array($query['priority'], $data['priorityList']))) {
681  $allowedPriorities = implode(', ', $data['priorityList']);
682  throw new HttpBadRequestException("Priority '{$query['priority']}' not " .
683  "allowed. Allowed priorities are: $allowedPriorities");
684  }
685 
686  if ($operation == 'status') {
687  $query['priority'] = null;
688  $query['level'] = null;
689  } else if ($operation == 'priority') {
690  $query['job'] = null;
691  $query['level'] = null;
692  } else if ($operation == 'verbose') {
693  $query['priority'] = null;
694  } else {
695  $query['job'] = null;
696  $query['priority'] = null;
697  $query['level'] = null;
698  }
699 
700  $response_from_scheduler = $adminSchedulerPlugin->OperationSubmit(
701  $operation, array_search($query['job'], $data['jobList']),
702  $query['priority'], $query['level']);
703  $operation_text = $adminSchedulerPlugin->GetOperationText($operation);
704  $status_msg = "";
705  $report = "";
706 
707  if (!empty($adminSchedulerPlugin->error_info)) {
708  $text = _("failed");
709  $status_msg .= "$operation_text $text.";
710  throw new HttpInternalServerErrorException($status_msg . $report);
711  }
712  $text = _("successfully");
713  $status_msg .= "$operation_text $text.";
714  if (! empty($response_from_scheduler)) {
715  $report .= $response_from_scheduler;
716  }
717 
718  $info = new Info(200, $status_msg. $report, InfoType::INFO);
719  return $response->withJson($info->getArray(), $info->getCode());
720  }
721 }
getAllUserResults($id, $uid, $request, $response, $limit, $page, $apiVersion)
createJob($request, $response, $args)
sortJobsByDate($job1, $job2)
Sort compare function to order $JobsInfo by jobqueue start time.
getAllResults($id, $request, $response, $limit, $page, $apiVersion)
deleteJob($request, $response, $args)
getFilteredResults($uploadId, $request, $response, $limit, $page, $apiVersion)
compareJobsInfo($JobsInfo1, $JobsInfo2)
Sort compare function to order $JobsInfo by job_pk.
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.