FOSSology  4.7.1
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 cannot be negative and page must be >= 1.");
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 cannot be negative and page must be >= 1.");
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, ignore any upload query parameter and let
125  * getAllResults() authorize access to the job's own upload. */
126  return $this->getAllResults($id, $status, $request, $response, $sort, $limit, $page, $apiVersion);
127  }
128 
129  if (array_key_exists(self::UPLOAD_PARAM, $queryParams)) {
130  /* If the upload is passed, filter accordingly */
131  return $this->getFilteredResults(intval($queryParams[self::UPLOAD_PARAM]),
132  $status, $request, $response, $sort, $limit, $page, $apiVersion);
133  }
134 
135  /* Otherwise return all jobs for the current user */
136  return $this->getAllUserResults($userId, $status, $request, $response, $sort, $limit, $page, $apiVersion);
137  }
138 
148  public function createJob($request, $response, $args)
149  {
150  $apiVersion = ApiVersion::getVersion($request);
151  $folder = null;
152  $upload = null;
153  if ($apiVersion == ApiVersion::V2) {
154  $query = $request->getQueryParams();
155  $folder = $query["folderId"] ?? null;
156  $upload = $query["uploadId"] ?? null;
157  } else {
158  $folder = $request->hasHeader('folderId') ? $request->getHeaderLine('folderId') : null;
159  $upload = $request->hasHeader('uploadId') ? $request->getHeaderLine('uploadId') : null;
160  }
161  if (is_numeric($folder) && is_numeric($upload) && $folder > 0 && $upload > 0) {
162  $scanOptionsJSON = $this->getParsedBody($request);
163  if (empty($scanOptionsJSON)) {
164  throw new HttpBadRequestException("No agents selected!");
165  }
166  $uploadHelper = new UploadHelper();
167  $info = $uploadHelper->handleScheduleAnalysis($upload, $folder,
168  $scanOptionsJSON, false, $apiVersion);
169  return $response->withJson($info->getArray(), $info->getCode());
170  }
171  throw new HttpBadRequestException(
172  "Folder id and upload id should be integers!");
173  }
174 
185  public function deleteJob($request, $response, $args)
186  {
187  $userId = $this->restHelper->getUserId();
188  $userName = $this->restHelper->getUserDao()->getUserName($userId);
189 
190  /* Check if the job exists */
191  $jobId = intval($args['id']);
192  if (! $this->dbHelper->doesIdExist("job", "job_pk", $jobId)) {
193  throw new HttpNotFoundException("Job id " . $jobId . " doesn't exist");
194  }
195 
196  /* Check if user has permission to delete this job*/
197  $canDeleteJob = $this->restHelper->getJobDao()->hasActionPermissionsOnJob($jobId, $userId, $this->restHelper->getGroupId());
198  if (! $canDeleteJob) {
199  throw new HttpForbiddenException(
200  "You don't have permission to delete this job.");
201  }
202 
203  $queueId = $args['queue'];
204 
205  /* Get Jobs that depend on the job to be deleted */
206  $JobQueue = $this->restHelper->getShowJobDao()->getJobInfo([$jobId])[$jobId]["jobqueue"];
207 
208  if (!array_key_exists($queueId, $JobQueue)) {
209  throw new HttpNotFoundException(
210  "Job queue " . $queueId . " doesn't exist in Job " . $jobId);
211  }
212 
213  $dependentJobs = [];
214  $dependentJobs[] = $queueId;
215 
216  foreach ($JobQueue as $job) {
217  if (in_array($queueId, $job["depends"])) {
218  $dependentJobs[] = $job["jq_pk"];
219  }
220  }
221 
222  /* Delete All jobs in dependentJobs */
223  foreach ($dependentJobs as $job) {
224  $Msg = "\"" . _("Killed by") . " " . $userName . "\"";
225  $command = "kill $job $Msg";
226  $rv = fo_communicate_with_scheduler($command, $response_from_scheduler, $error_info);
227  if (!$rv) {
229  "Failed to kill job $jobId");
230  }
231  }
232  $returnVal = new Info(200, "Job deleted successfully", InfoType::INFO);
233  return $response->withJson($returnVal->getArray(), $returnVal->getCode());
234  }
235 
249  private function getAllUserResults($userId, $status, $request, $response, $sort, $limit, $page, $apiVersion)
250  {
251  list($jobs, $count) = $this->dbHelper->getUserJobs($userId, $status, $sort, $limit, $page);
252  $finalJobs = [];
253  foreach ($jobs as $job) {
254  $this->updateEta($job);
255  if ($apiVersion == ApiVersion::V2) {
256  $this->addJobQueue($job, $request);
257  }
258  $finalJobs[] = $job->getArray($apiVersion);
259  }
260  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
261  }
262 
278  private function getAllResults($id, $status, $request, $response, $sort, $limit, $page, $apiVersion)
279  {
280  list($jobs, $count) = $this->dbHelper->getJobs($id, $status, $sort, $limit, $page, null);
281  if ($id !== null && !empty($jobs)) {
282  /* A specific job was requested, make sure the caller can access the
283  * upload it belongs to before returning any of its data. */
284  $this->uploadAccessible($jobs[0]->getUploadId());
285  }
286  $finalJobs = [];
287  foreach ($jobs as $job) {
288  $this->updateEta($job);
289  if ($apiVersion == ApiVersion::V2) {
290  $this->addJobQueue($job, $request);
291  }
292  $finalJobs[] = $job->getArray($apiVersion);
293  }
294  if ($id !== null) {
295  $finalJobs = $finalJobs[0];
296  }
297  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
298  }
299 
315  private function getFilteredResults($uploadId, $status, $request, $response, $sort, $limit, $page, $apiVersion)
316  {
317  $this->uploadAccessible($uploadId);
318  list($jobs, $count) = $this->dbHelper->getJobs(null, $status, $sort, $limit, $page, $uploadId);
319  $finalJobs = [];
320  foreach ($jobs as $job) {
321  $this->updateEta($job);
322  if ($apiVersion == ApiVersion::V2) {
323  $this->addJobQueue($job, $request);
324  }
325  $finalJobs[] = $job->getArray($apiVersion);
326  }
327  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
328  }
329 
335  private function updateEta(&$job)
336  {
337  $job->setEta($this->getUploadEtaInSeconds($job->getId(),
338  $job->getUploadId()));
339  }
340 
348  private function getUploadEtaInSeconds($jobId, $uploadId)
349  {
350  $showJobDao = $this->restHelper->getShowJobDao();
351  $eta = $showJobDao->getEstimatedTime($jobId, '', 0, $uploadId);
352  $eta = explode(":", $eta);
353  if (count($eta) > 1) {
354  $eta = (intval($eta[0]) * 3600) + (intval($eta[1]) * 60) + intval($eta[2]);
355  } else {
356  $eta = 0;
357  }
358  return $eta;
359  }
360 
370  public function getJobsHistory($request, $response, $args)
371  {
372  $query = $request->getQueryParams();
373  if (!array_key_exists(self::UPLOAD_PARAM, $query)) {
374  throw new HttpBadRequestException("'upload' is a required query param");
375  }
376  $upload_fk = intval($query[self::UPLOAD_PARAM]);
377  // checking if the upload exists and if yes, whether it is accessible
378  $this->uploadAccessible($upload_fk);
379 
384  $dbManager = $this->dbHelper->getDbManager();
385 
386  // getting all the jobs from the DB for the upload id
387  $query = "SELECT job_pk FROM job WHERE job_upload_fk=$1;";
388  $statement = __METHOD__.".getJobs";
389  $result = $dbManager->getRows($query, [$upload_fk], $statement);
390 
391  // creating a list of all the job_pks
392  $allJobPks = array_column($result, 'job_pk');
393 
394  // getting the show jobs data for each job
395  $showJobData = $this->getJobQueue($allJobPks);
396 
397  // creating the response structure
398  $allJobsHistory = array();
399  foreach ($showJobData as $jobValObj) {
400  $finalJobqueue = array();
401  foreach ($jobValObj['job']['jobQueue'] as $jqVal) {
402  $depends = [];
403  if ($jqVal['depends'][0] != null) {
404  $depends = $jqVal['depends'];
405  }
406  $download = null;
407  if (!empty($jqVal['download'])) {
408  $download = [
409  "text" => $jqVal["download"],
410  "link" => ReportController::buildDownloadPath($request,
411  $jqVal['jq_job_fk'])
412  ];
413  }
414  $jobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
415  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
416  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
417  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
418  $jqVal['isReady'], $download);
419  $finalJobqueue[] = $jobQueue->getArray();
420  }
421  $job = new ShowJob($jobValObj['job']['jobId'],
422  $jobValObj['job']['jobName'], $finalJobqueue,
423  $jobValObj['upload']['uploadId']);
424  $allJobsHistory[] = $job->getArray();
425  }
426  return $response->withJson($allJobsHistory, 200);
427  }
428 
435  private function getJobQueue($allJobPks)
436  {
437  $showJobsDao = $this->restHelper->getShowJobDao();
438  $jobsInfo = $showJobsDao->getJobInfo($allJobPks);
439  usort($jobsInfo, [$this, "compareJobsInfo"]);
440 
445  $ajaxShowJobs = $this->restHelper->getPlugin('ajaxShowJobs');
446  $showJobData = $ajaxShowJobs->getShowJobsForEachJob($jobsInfo, true);
447 
448  return $showJobData;
449  }
450 
457  private function addJobQueue(&$job, $request = null)
458  {
459  $jobQueue = $this->getJobQueue([$job->getId()]);
460  $finalJobqueue = array();
461  foreach ($jobQueue[0]['job']['jobQueue'] as $jqVal) {
462  $depends = [];
463  if ($jqVal['depends'][0] != null) {
464  $depends = $jqVal['depends'];
465  }
466  $download = null;
467  if (!empty($jqVal['download'])) {
468  $download = [
469  "text" => $jqVal["download"],
470  "link" => ReportController::buildDownloadPath($request,
471  $jqVal['jq_job_fk'])
472  ];
473  }
474  $singleJobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
475  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
476  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
477  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
478  $jqVal['isReady'], $download);
479  $finalJobqueue[] = $singleJobQueue->getArray();
480  }
481  $job->setJobQueue($finalJobqueue);
482  }
483 
490  private function compareJobsInfo($JobsInfo1, $JobsInfo2)
491  {
492  return $JobsInfo2["job"]["job_pk"] - $JobsInfo1["job"]["job_pk"];
493  }
494 
503  public function getJobStatistics($request, $response, $args)
504  {
505  $this->throwNotAdminException();
507  $statisticsPlugin = $this->restHelper->getPlugin('dashboard-statistics');
508  $res = $statisticsPlugin->CountAllJobs(true);
509  return $response->withJson($res, 200);
510  }
511 
521  public function getAllServerJobsStatus($request, $response, $args)
522  {
523  $this->throwNotAdminException();
525  $allJobStatusPlugin = $this->restHelper->getPlugin('ajax_all_job_status');
526  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
527  $res = $allJobStatusPlugin->handle($symfonyRequest);
528  return $response->withJson(json_decode($res->getContent(), true), 200);
529  }
530 
539  public function getSchedulerJobOptionsByOperation($request, $response, $args)
540  {
541  $this->throwNotAdminException();
542  $operation = $args['operationName'];
544  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
545 
546  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
547  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
548  throw new HttpBadRequestException("Operation '$operation' not allowed." .
549  " Allowed operations are: $allowedOperations");
550  }
551 
553  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
554  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
555  $symfonyRequest->request->set('operation', $operation);
556  $symfonyRequest->request->set('fromRest', true);
557  $res = $schedulerPlugin->handle($symfonyRequest);
558  return $response->withJson($res, 200);
559  }
560 
570  public function handleRunSchedulerOption($request, $response, $args)
571  {
572  $this->throwNotAdminException();
573  $body = $this->getParsedBody($request);
574  $query = $request->getQueryParams();
575 
576  $operation = $body['operation'];
578  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
579 
580  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
581  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
582  throw new HttpBadRequestException("Operation '$operation' not allowed." .
583  " Allowed operations are: $allowedOperations");
584  }
585 
587  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
588  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
589  $symfonyRequest->request->set('operation', $operation);
590  $symfonyRequest->request->set('fromRest', true);
591  $data = $schedulerPlugin->handle($symfonyRequest);
592 
593  if ($operation == 'status' || $operation == 'verbose') {
594  if (!isset($query['job']) || !in_array($query['job'], $data['jobList'])) {
595  $allowedJobs = implode(', ', $data['jobList']);
596  throw new HttpBadRequestException("Job '{$query['job']}' not " .
597  "allowed. Allowed jobs are: $allowedJobs");
598  }
599  if (($operation == 'verbose') && (!isset($query['level']) || !in_array($query['level'], $data['verboseList']))) {
600  $allowedLevels = implode(', ', $data['verboseList']);
601  throw new HttpBadRequestException("Level '{$query['level']}' not " .
602  "allowed. Allowed levels are: $allowedLevels");
603  }
604  } elseif ($operation == 'priority' && (!isset($query['priority']) || !in_array($query['priority'], $data['priorityList']))) {
605  $allowedPriorities = implode(', ', $data['priorityList']);
606  throw new HttpBadRequestException("Priority '{$query['priority']}' not " .
607  "allowed. Allowed priorities are: $allowedPriorities");
608  }
609 
610  if ($operation == 'status') {
611  $query['priority'] = null;
612  $query['level'] = null;
613  } else if ($operation == 'priority') {
614  $query['job'] = null;
615  $query['level'] = null;
616  } else if ($operation == 'verbose') {
617  $query['priority'] = null;
618  } else {
619  $query['job'] = null;
620  $query['priority'] = null;
621  $query['level'] = null;
622  }
623 
624  $response_from_scheduler = $adminSchedulerPlugin->OperationSubmit(
625  $operation, array_search($query['job'], $data['jobList']),
626  $query['priority'], $query['level']);
627  $operation_text = $adminSchedulerPlugin->GetOperationText($operation);
628  $status_msg = "";
629  $report = "";
630 
631  if (!empty($adminSchedulerPlugin->error_info)) {
632  $text = _("failed");
633  $status_msg .= "$operation_text $text.";
634  throw new HttpInternalServerErrorException($status_msg . $report);
635  }
636  $text = _("successfully");
637  $status_msg .= "$operation_text $text.";
638  if (! empty($response_from_scheduler)) {
639  $report .= $response_from_scheduler;
640  }
641 
642  $info = new Info(200, $status_msg. $report, InfoType::INFO);
643  return $response->withJson($info->getArray(), $info->getCode());
644  }
645 }
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.