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  $this->throwNotAdminException();
71  $id = null;
72  $limit = 0;
73  $page = 1;
74  if ($request->hasHeader('limit')) {
75  $limit = $request->getHeaderLine('limit');
76  $page = $request->getHeaderLine('page');
77  if (empty($page)) {
78  $page = 1;
79  }
80  if ((! is_numeric($limit) || $limit < 0) ||
81  (! is_numeric($page) || $page < 1)) {
82  throw new HttpBadRequestException(
83  "Limit and page cannot be smaller than 1 and has to be numeric!");
84  }
85  }
86  return $this->getAllResults($id, $response, $limit, $page);
87  }
88 
98  public function getJobs($request, $response, $args)
99  {
100  $query = $request->getQueryParams();
101  $userId = $this->restHelper->getUserId();
102  $limit = 0;
103  $page = 1;
104  if ($request->hasHeader('limit')) {
105  $limit = $request->getHeaderLine('limit');
106  $page = $request->getHeaderLine('page');
107  if (empty($page)) {
108  $page = 1;
109  }
110  if ((! is_numeric($limit) || $limit < 0) ||
111  (! is_numeric($page) || $page < 1)) {
112  throw new HttpBadRequestException(
113  "Limit and page cannot be smaller than 1 and has to be numeric!");
114  }
115  }
116 
117  $id = null;
118  if (isset($args['id'])) {
119  $id = intval($args['id']);
120  if (! $this->dbHelper->doesIdExist("job", "job_pk", $id)) {
121  throw new HttpNotFoundException("Job id " . $id . " doesn't exist");
122  }
123  }
124 
125  if ($id !== null) {
126  /* If the ID is passed, don't check for upload */
127  return $this->getAllResults($id, $response, $limit, $page);
128  }
129 
130  if (array_key_exists(self::UPLOAD_PARAM, $query)) {
131  /* If the upload is passed, filter accordingly */
132  return $this->getFilteredResults(intval($query[self::UPLOAD_PARAM]),
133  $response, $limit, $page);
134  } else {
135  $id = null;
136  return $this->getAllUserResults($id,$userId, $response, $limit, $page);
137  }
138  }
139 
149  public function createJob($request, $response, $args)
150  {
151  $folder = $request->getHeaderLine("folderId");
152  $upload = $request->getHeaderLine("uploadId");
153  if (is_numeric($folder) && is_numeric($upload) && $folder > 0 && $upload > 0) {
154  $scanOptionsJSON = $this->getParsedBody($request);
155  if (empty($scanOptionsJSON)) {
156  throw new HttpBadRequestException("No agents selected!");
157  }
158  $uploadHelper = new UploadHelper();
159  $info = $uploadHelper->handleScheduleAnalysis($upload, $folder,
160  $scanOptionsJSON);
161  return $response->withJson($info->getArray(), $info->getCode());
162  }
163  throw new HttpBadRequestException(
164  "Folder id and upload id should be integers!");
165  }
166 
177  public function deleteJob($request, $response, $args)
178  {
179  $userId = $this->restHelper->getUserId();
180  $userName = $this->restHelper->getUserDao()->getUserName($userId);
181 
182  /* Check if the job exists */
183  $jobId = intval($args['id']);
184  if (! $this->dbHelper->doesIdExist("job", "job_pk", $jobId)) {
185  throw new HttpNotFoundException("Job id " . $jobId . " doesn't exist");
186  }
187 
188  /* Check if user has permission to delete this job*/
189  $canDeleteJob = $this->restHelper->getJobDao()->hasActionPermissionsOnJob($jobId, $userId, $this->restHelper->getGroupId());
190  if (! $canDeleteJob) {
191  throw new HttpForbiddenException(
192  "You don't have permission to delete this job.");
193  }
194 
195  $queueId = $args['queue'];
196 
197  /* Get Jobs that depend on the job to be deleted */
198  $JobQueue = $this->restHelper->getShowJobDao()->getJobInfo([$jobId])[$jobId]["jobqueue"];
199 
200  if (!array_key_exists($queueId, $JobQueue)) {
201  throw new HttpNotFoundException(
202  "Job queue " . $queueId . " doesn't exist in Job " . $jobId);
203  }
204 
205  $dependentJobs = [];
206  $dependentJobs[] = $queueId;
207 
208  foreach ($JobQueue as $job) {
209  if (in_array($queueId, $job["depends"])) {
210  $dependentJobs[] = $job["jq_pk"];
211  }
212  }
213 
214  /* Delete All jobs in dependentJobs */
215  foreach ($dependentJobs as $job) {
216  $Msg = "\"" . _("Killed by") . " " . $userName . "\"";
217  $command = "kill $job $Msg";
218  $rv = fo_communicate_with_scheduler($command, $response_from_scheduler, $error_info);
219  if (!$rv) {
221  "Failed to kill job $jobId");
222  }
223  }
224  $returnVal = new Info(200, "Job deleted successfully", InfoType::INFO);
225  return $response->withJson($returnVal->getArray(), $returnVal->getCode());
226  }
227 
238  private function getAllUserResults($id, $uid, $response, $limit, $page)
239  {
240  list($jobs, $count) = $this->dbHelper->getUserJobs($id, $uid, $limit, $page);
241  $finalJobs = [];
242  foreach ($jobs as $job) {
243  $this->updateEtaAndStatus($job);
244  $finalJobs[] = $job->getArray();
245  }
246  if ($id !== null) {
247  $finalJobs = $finalJobs[0];
248  }
249  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
250  }
251 
261  private function getAllResults($id, $response, $limit, $page)
262  {
263  list($jobs, $count) = $this->dbHelper->getJobs($id, $limit, $page);
264  $finalJobs = [];
265  foreach ($jobs as $job) {
266  $this->updateEtaAndStatus($job);
267  $finalJobs[] = $job->getArray();
268  }
269  if ($id !== null) {
270  $finalJobs = $finalJobs[0];
271  }
272  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
273  }
274 
285  private function getFilteredResults($uploadId, $response, $limit, $page)
286  {
287  if (! $this->dbHelper->doesIdExist("upload", "upload_pk", $uploadId)) {
288  throw new HttpNotFoundException("Upload id " . $uploadId .
289  " doesn't exist");
290  }
291  list($jobs, $count) = $this->dbHelper->getJobs(null, $limit, $page, $uploadId);
292  $finalJobs = [];
293  foreach ($jobs as $job) {
294  $this->updateEtaAndStatus($job);
295  $finalJobs[] = $job->getArray();
296  }
297  return $response->withHeader("X-Total-Pages", $count)->withJson($finalJobs, 200);
298  }
299 
305  private function updateEtaAndStatus(&$job)
306  {
307  $jobDao = $this->restHelper->getJobDao();
308 
309  $jobqueue = [];
310 
311  /* Check if the job has no upload like Maintenance job */
312  if (empty($job->getUploadId())) {
313  $sql = "SELECT jq_pk, jq_end_bits from jobqueue WHERE jq_job_fk = $1;";
314  $statement = __METHOD__ . ".getJqpk";
315  $rows = $this->dbHelper->getDbManager()->getRows($sql, [$job->getId()],
316  $statement);
317  if (count($rows) > 0) {
318  $jobqueue[$rows[0]['jq_pk']] = $rows[0]['jq_end_bits'];
319  }
320  } else {
321  $jobqueue = $jobDao->getAllJobStatus($job->getUploadId(),
322  $job->getUserId(), $job->getGroupId());
323  }
324 
325  $job->setEta($this->getUploadEtaInSeconds($job->getId(),
326  $job->getUploadId()));
327 
328  $job->setStatus($this->getJobStatus(array_keys($jobqueue)));
329  }
330 
338  private function getUploadEtaInSeconds($jobId, $uploadId)
339  {
340  $showJobDao = $this->restHelper->getShowJobDao();
341  $eta = $showJobDao->getEstimatedTime($jobId, '', 0, $uploadId);
342  $eta = explode(":", $eta);
343  if (count($eta) > 1) {
344  $eta = (intval($eta[0]) * 3600) + (intval($eta[1]) * 60) + intval($eta[2]);
345  } else {
346  $eta = 0;
347  }
348  return $eta;
349  }
350 
357  private function getJobStatus($jobqueue)
358  {
359  $showJobDao = $this->restHelper->getShowJobDao();
360  $jobStatus = 0;
361  /* Check each job in queue */
362  foreach ($jobqueue as $jobId) {
363  $jobInfo = $showJobDao->getDataForASingleJob($jobId);
364  $endtext = $jobInfo['jq_endtext'];
365  switch ($endtext) {
366  case 'Completed':
367  $jobStatus |= self::JOB_COMPLETED;
368  break;
369  case 'Started':
370  case 'Restarted':
371  case 'Paused':
372  $jobStatus |= self::JOB_STARTED;
373  break;
374  default:
375  if (empty($jobInfo['jq_endtime'])) {
376  $jobStatus |= self::JOB_QUEUED;
377  } else {
378  $jobStatus |= self::JOB_FAILED;
379  }
380  }
381  }
382 
383  $jobStatusString = "";
384  if ($jobStatus & self::JOB_FAILED) {
385  /* If at least one job is failed, set status as failed */
386  $jobStatusString = "Failed";
387  } else if ($jobStatus & self::JOB_STARTED) {
388  /* If at least one job is started, set status as processing */
389  $jobStatusString = "Processing";
390  } else if ($jobStatus & self::JOB_QUEUED) {
391  $jobStatusString = "Queued";
392  } else {
393  /* If everything completed successfully, set status as completed */
394  $jobStatusString = "Completed";
395  }
396  return $jobStatusString;
397  }
398 
408  public function getJobsHistory($request, $response, $args)
409  {
410  $query = $request->getQueryParams();
411  if (!array_key_exists(self::UPLOAD_PARAM, $query)) {
412  throw new HttpBadRequestException("'upload' is a required query param");
413  }
414  $upload_fk = intval($query[self::UPLOAD_PARAM]);
415  // checking if the upload exists and if yes, whether it is accessible
416  $this->uploadAccessible($upload_fk);
417 
422  $dbManager = $this->dbHelper->getDbManager();
423 
424  // getting all the jobs from the DB for the upload id
425  $query = "SELECT job_pk FROM job WHERE job_upload_fk=$1;";
426  $statement = __METHOD__.".getJobs";
427  $result = $dbManager->getRows($query, [$upload_fk], $statement);
428 
429  // creating a list of all the job_pks
430  $allJobPks = array_column($result, 'job_pk');
431 
436  $showJobsDao = $this->container->get('dao.show_jobs');
437 
438  $jobsInfo = $showJobsDao->getJobInfo($allJobPks);
439  usort($jobsInfo, [$this, "compareJobsInfo"]);
440 
445  $ajaxShowJobs = $this->restHelper->getPlugin('ajaxShowJobs');
446 
447  // getting the show jobs data for each job
448  $showJobData = $ajaxShowJobs->getShowJobsForEachJob($jobsInfo);
449 
450  // creating the response structure
451  $allJobsHistory = array();
452  foreach ($showJobData as $jobValObj) {
453  $finalJobqueue = array();
454  foreach ($jobValObj['job']['jobQueue'] as $jqVal) {
455  $depends = [];
456  if ($jqVal['depends'][0] != null) {
457  $depends = $jqVal['depends'];
458  }
459  $download = null;
460  if (!empty($jqVal['download'])) {
461  $download = [
462  "text" => $jqVal["download"],
463  "link" => ReportController::buildDownloadPath($request,
464  $jqVal['jq_job_fk'])
465  ];
466  }
467  $jobQueue = new JobQueue($jqVal['jq_pk'], $jqVal['jq_type'],
468  $jqVal['jq_starttime'], $jqVal['jq_endtime'], $jqVal['jq_endtext'],
469  $jqVal['jq_itemsprocessed'], $jqVal['jq_log'], $depends,
470  $jqVal['itemsPerSec'], $jqVal['canDoActions'], $jqVal['isInProgress'],
471  $jqVal['isReady'], $download);
472  $finalJobqueue[] = $jobQueue->getArray();
473  }
474  $job = new ShowJob($jobValObj['job']['jobId'],
475  $jobValObj['job']['jobName'], $finalJobqueue,
476  $jobValObj['upload']['uploadId']);
477  $allJobsHistory[] = $job->getArray();
478  }
479  return $response->withJson($allJobsHistory, 200);
480  }
481 
488  private function compareJobsInfo($JobsInfo1, $JobsInfo2)
489  {
490  return $JobsInfo2["job"]["job_pk"] - $JobsInfo1["job"]["job_pk"];
491  }
492 
501  public function getJobStatistics($request, $response, $args)
502  {
503  $this->throwNotAdminException();
505  $statisticsPlugin = $this->restHelper->getPlugin('dashboard-statistics');
506  $res = $statisticsPlugin->CountAllJobs(true);
507  return $response->withJson($res, 200);
508  }
509 
519  public function getAllServerJobsStatus($request, $response, $args)
520  {
521  $this->throwNotAdminException();
523  $allJobStatusPlugin = $this->restHelper->getPlugin('ajax_all_job_status');
524  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
525  $res = $allJobStatusPlugin->handle($symfonyRequest);
526  return $response->withJson(json_decode($res->getContent(), true), 200);
527  }
528 
537  public function getSchedulerJobOptionsByOperation($request, $response, $args)
538  {
539  $this->throwNotAdminException();
540  $operation = $args['operationName'];
542  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
543 
544  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
545  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
546  throw new HttpBadRequestException("Operation '$operation' not allowed." .
547  " Allowed operations are: $allowedOperations");
548  }
549 
551  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
552  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
553  $symfonyRequest->request->set('operation', $operation);
554  $symfonyRequest->request->set('fromRest', true);
555  $res = $schedulerPlugin->handle($symfonyRequest);
556  return $response->withJson($res, 200);
557  }
558 
568  public function handleRunSchedulerOption($request, $response, $args)
569  {
570  $this->throwNotAdminException();
571  $body = $this->getParsedBody($request);
572  $query = $request->getQueryParams();
573 
574  $operation = $body['operation'];
576  $adminSchedulerPlugin = $this->restHelper->getPlugin('admin_scheduler');
577 
578  if (!in_array($operation, array_keys($adminSchedulerPlugin->operation_array))) {
579  $allowedOperations = implode(', ', array_keys($adminSchedulerPlugin->operation_array));
580  throw new HttpBadRequestException("Operation '$operation' not allowed." .
581  " Allowed operations are: $allowedOperations");
582  }
583 
585  $schedulerPlugin = $this->restHelper->getPlugin('ajax_admin_scheduler');
586  $symfonyRequest = new \Symfony\Component\HttpFoundation\Request();
587  $symfonyRequest->request->set('operation', $operation);
588  $symfonyRequest->request->set('fromRest', true);
589  $data = $schedulerPlugin->handle($symfonyRequest);
590 
591  if ($operation == 'status' || $operation == 'verbose') {
592  if (!isset($query['job']) || !in_array($query['job'], $data['jobList'])) {
593  $allowedJobs = implode(', ', $data['jobList']);
594  throw new HttpBadRequestException("Job '{$query['job']}' not " .
595  "allowed. Allowed jobs are: $allowedJobs");
596  }
597  if (($operation == 'verbose') && (!isset($query['level']) || !in_array($query['level'], $data['verboseList']))) {
598  $allowedLevels = implode(', ', $data['verboseList']);
599  throw new HttpBadRequestException("Level '{$query['level']}' not " .
600  "allowed. Allowed levels are: $allowedLevels");
601  }
602  } elseif ($operation == 'priority' && (!isset($query['priority']) || !in_array($query['priority'], $data['priorityList']))) {
603  $allowedPriorities = implode(', ', $data['priorityList']);
604  throw new HttpBadRequestException("Priority '{$query['priority']}' not " .
605  "allowed. Allowed priorities are: $allowedPriorities");
606  }
607 
608  if ($operation == 'status') {
609  $query['priority'] = null;
610  $query['level'] = null;
611  } else if ($operation == 'priority') {
612  $query['job'] = null;
613  $query['level'] = null;
614  } else if ($operation == 'verbose') {
615  $query['priority'] = null;
616  } else {
617  $query['job'] = null;
618  $query['priority'] = null;
619  $query['level'] = null;
620  }
621 
622  $response_from_scheduler = $adminSchedulerPlugin->OperationSubmit(
623  $operation, array_search($query['job'], $data['jobList']),
624  $query['priority'], $query['level']);
625  $operation_text = $adminSchedulerPlugin->GetOperationText($operation);
626  $status_msg = "";
627  $report = "";
628 
629  if (!empty($adminSchedulerPlugin->error_info)) {
630  $text = _("failed");
631  $status_msg .= "$operation_text $text.";
632  throw new HttpInternalServerErrorException($status_msg . $report);
633  }
634  $text = _("successfully");
635  $status_msg .= "$operation_text $text.";
636  if (! empty($response_from_scheduler)) {
637  $report .= $response_from_scheduler;
638  }
639 
640  $info = new Info(200, $status_msg. $report, InfoType::INFO);
641  return $response->withJson($info->getArray(), $info->getCode());
642  }
643 }
getAllResults($id, $response, $limit, $page)
createJob($request, $response, $args)
getJobs($request, $response, $args)
getFilteredResults($uploadId, $response, $limit, $page)
deleteJob($request, $response, $args)
getAllUserResults($id, $uid, $response, $limit, $page)
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.
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.