mirror of https://github.com/MISP/MISP
add: [WiP] background jobs basic ui
parent
1151d4779b
commit
14c675fa33
|
@ -18,6 +18,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Lib\Tools\BackgroundJobsTool;
|
||||
use Cake\Controller\Controller;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Event\EventInterface;
|
||||
|
@ -41,6 +42,9 @@ class AppController extends Controller
|
|||
public $breadcrumb = [];
|
||||
public $request_ip = null;
|
||||
|
||||
/** @var BackgroundJobsTool */
|
||||
private static $loadedBackgroundJobsTool;
|
||||
|
||||
/**
|
||||
* Initialization hook method.
|
||||
*
|
||||
|
@ -60,7 +64,7 @@ class AppController extends Controller
|
|||
$this->loadComponent(
|
||||
'ParamHandler',
|
||||
[
|
||||
'request' => $this->request
|
||||
'request' => $this->request
|
||||
]
|
||||
);
|
||||
$this->loadModel('MetaFields');
|
||||
|
@ -69,30 +73,30 @@ class AppController extends Controller
|
|||
$this->loadComponent(
|
||||
'CRUD',
|
||||
[
|
||||
'request' => $this->request,
|
||||
'table' => $table,
|
||||
'MetaFields' => $this->MetaFields,
|
||||
'MetaTemplates' => $this->MetaTemplates
|
||||
'request' => $this->request,
|
||||
'table' => $table,
|
||||
'MetaFields' => $this->MetaFields,
|
||||
'MetaTemplates' => $this->MetaTemplates
|
||||
]
|
||||
);
|
||||
$this->loadComponent('Authentication.Authentication');
|
||||
$this->loadComponent(
|
||||
'ACL',
|
||||
[
|
||||
'request' => $this->request,
|
||||
'Authentication' => $this->Authentication
|
||||
'request' => $this->request,
|
||||
'Authentication' => $this->Authentication
|
||||
]
|
||||
);
|
||||
$this->loadComponent(
|
||||
'Navigation',
|
||||
[
|
||||
'request' => $this->request,
|
||||
'request' => $this->request,
|
||||
]
|
||||
);
|
||||
$this->loadComponent(
|
||||
'Notification',
|
||||
[
|
||||
'request' => $this->request,
|
||||
'request' => $this->request,
|
||||
]
|
||||
);
|
||||
if (Configure::read('debug')) {
|
||||
|
@ -121,7 +125,7 @@ class AppController extends Controller
|
|||
$user = $this->Users->get(
|
||||
$this->request->getAttribute('identity')->getIdentifier(),
|
||||
[
|
||||
'contain' => ['Roles', /*'UserSettings',*/ 'Organisations']
|
||||
'contain' => ['Roles', /*'UserSettings',*/ 'Organisations']
|
||||
]
|
||||
);
|
||||
if (!empty($user['disabled'])) {
|
||||
|
@ -204,11 +208,11 @@ class AppController extends Controller
|
|||
$user = $this->Users->get($authKey['user_id']);
|
||||
$logModel->insert(
|
||||
[
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['username'],
|
||||
'changed' => []
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['username'],
|
||||
'changed' => []
|
||||
]
|
||||
);
|
||||
if (!empty($user)) {
|
||||
|
@ -218,11 +222,11 @@ class AppController extends Controller
|
|||
$user = $logModel->userInfo();
|
||||
$logModel->insert(
|
||||
[
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['name'],
|
||||
'changed' => []
|
||||
'request_action' => 'login',
|
||||
'model' => 'Users',
|
||||
'model_id' => $user['id'],
|
||||
'model_title' => $user['name'],
|
||||
'changed' => []
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -273,4 +277,16 @@ class AppController extends Controller
|
|||
session_abort();
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackgroundJobsTool
|
||||
*/
|
||||
public function getBackgroundJobsTool(): BackgroundJobsTool
|
||||
{
|
||||
if (!self::$loadedBackgroundJobsTool) {
|
||||
$backgroundJobsTool = new BackgroundJobsTool(Configure::read('BackgroundJobs'));
|
||||
self::$loadedBackgroundJobsTool = $backgroundJobsTool;
|
||||
}
|
||||
return self::$loadedBackgroundJobsTool;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Response;
|
||||
use Cake\ORM\Locator\LocatorAwareTrait;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Event\EventInterface;
|
||||
|
||||
class JobsController extends AppController
|
||||
{
|
||||
use LocatorAwareTrait;
|
||||
|
||||
public $paginate = [
|
||||
'limit' => 20,
|
||||
'recursive' => 0,
|
||||
'order' => [
|
||||
'Job.id' => 'DESC'
|
||||
],
|
||||
'contain' => [
|
||||
'Organisations' => [
|
||||
'fields' => ['id', 'name', 'uuid'],
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
public function beforeFilter(EventInterface $event)
|
||||
{
|
||||
parent::beforeFilter($event);
|
||||
if ($this->request->getParam('action') === 'getGenerateCorrelationProgress') {
|
||||
$this->Security->doNotGenerateToken = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function index($queue = false)
|
||||
{
|
||||
if (!Configure::read('BackgroundJobs.enabled')) {
|
||||
throw new NotFoundException('Background jobs are not enabled on this instance.');
|
||||
}
|
||||
$ServerTable = $this->fetchTable('Servers');
|
||||
$issueCount = 0;
|
||||
$workers = $ServerTable->workerDiagnostics($issueCount);
|
||||
$queues = ['email', 'default', 'cache', 'prio', 'update'];
|
||||
if ($queue && in_array($queue, $queues, true)) {
|
||||
$this->paginate['conditions'] = ['Job.worker' => $queue];
|
||||
}
|
||||
$jobs = $this->paginate()->toArray();
|
||||
foreach ($jobs as &$job) {
|
||||
if (!empty($job['process_id'])) {
|
||||
$job['job_status'] = $this->getJobStatus($job['process_id']);
|
||||
$job['failed'] = $job['job_status'] === 'Failed';
|
||||
} else {
|
||||
$job['job_status'] = 'Unknown';
|
||||
$job['failed'] = null;
|
||||
}
|
||||
if (Configure::read('BackgroundJobs.enabled')) {
|
||||
$job['worker_status'] = true;
|
||||
} else {
|
||||
$job['worker_status'] = isset($workers[$job['worker']]) && $workers[$job['worker']]['ok'];
|
||||
}
|
||||
}
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData($jobs);
|
||||
}
|
||||
$this->set('jobs', $jobs);
|
||||
$this->set('queue', $queue);
|
||||
}
|
||||
|
||||
public function getError($id)
|
||||
{
|
||||
$fields = array(
|
||||
'Failed at' => 'failed_at',
|
||||
'Exception' => 'exception',
|
||||
'Error' => 'error'
|
||||
);
|
||||
$this->set('fields', $fields);
|
||||
$this->set('response', $this->getFailedJobLog($id));
|
||||
$this->render('/Jobs/ajax/error');
|
||||
}
|
||||
|
||||
private function jobStatusConverter($status)
|
||||
{
|
||||
switch ($status) {
|
||||
case 1:
|
||||
return 'Waiting';
|
||||
case 2:
|
||||
return 'Running';
|
||||
case 3:
|
||||
return 'Failed';
|
||||
case 4:
|
||||
return 'Completed';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
public function getGenerateCorrelationProgress($ids)
|
||||
{
|
||||
$this->closeSession();
|
||||
|
||||
$ids = explode(",", $ids);
|
||||
$jobs = $this->Jobs->find('all', [
|
||||
'fields' => ['id', 'progress', 'process_id'],
|
||||
'conditions' => ['id' => $ids],
|
||||
'recursive' => -1,
|
||||
]);
|
||||
if (empty($jobs)) {
|
||||
throw new NotFoundException('No jobs found');
|
||||
}
|
||||
|
||||
$output = [];
|
||||
foreach ($jobs as $job) {
|
||||
$output[$job['id']] = [
|
||||
'job_status' => $this->getJobStatus($job['process_id']),
|
||||
'progress' => (int)$job['progress'],
|
||||
];
|
||||
}
|
||||
return $this->RestResponse->viewData($output, 'json');
|
||||
}
|
||||
|
||||
public function getProgress($type)
|
||||
{
|
||||
$org_id = $this->Auth->user('org_id');
|
||||
if ($this->isSiteAdmin()) {
|
||||
$org_id = 0;
|
||||
}
|
||||
|
||||
if (is_numeric($type)) {
|
||||
$progress = $this->Jobs->find('first', array(
|
||||
'conditions' => array(
|
||||
'Job.id' => $type,
|
||||
'org_id' => $org_id
|
||||
),
|
||||
'fields' => array('id', 'progress'),
|
||||
'order' => array('Job.id' => 'desc'),
|
||||
));
|
||||
} else {
|
||||
$progress = $this->Jobs->find('first', array(
|
||||
'conditions' => array(
|
||||
'job_type' => $type,
|
||||
'org_id' => $org_id
|
||||
),
|
||||
'fields' => array('id', 'progress'),
|
||||
'order' => array('Job.id' => 'desc'),
|
||||
));
|
||||
}
|
||||
if (!$progress) {
|
||||
$progress = 0;
|
||||
} else {
|
||||
$progress = $progress['progress'];
|
||||
}
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData(array('progress' => $progress . '%'));
|
||||
} else {
|
||||
return new Response(array('body' => json_encode($progress), 'type' => 'json'));
|
||||
}
|
||||
}
|
||||
|
||||
public function cache($type)
|
||||
{
|
||||
if (Configure::read('MISP.disable_cached_exports')) {
|
||||
throw new MethodNotAllowedException('This feature is currently disabled');
|
||||
}
|
||||
if ($this->isSiteAdmin()) {
|
||||
$target = 'All events.';
|
||||
} else {
|
||||
$target = 'Events visible to: ' . $this->Auth->user('Organisation')['name'];
|
||||
}
|
||||
$id = $this->Job->cache($type, $this->Auth->user());
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData(array('job_id' => $id));
|
||||
} else {
|
||||
return new Response(array('body' => json_encode($id), 'type' => 'json'));
|
||||
}
|
||||
}
|
||||
|
||||
public function clearJobs($type = 'completed')
|
||||
{
|
||||
if ($this->request->is('post')) {
|
||||
if ($type === 'all') {
|
||||
$conditions = array('Job.id !=' => 0);
|
||||
$message = __('All jobs have been purged');
|
||||
} else {
|
||||
$conditions = array('Job.progress' => 100);
|
||||
$message = __('All completed jobs have been purged');
|
||||
}
|
||||
$this->Jobs->deleteAll($conditions, false);
|
||||
$this->Flash->success($message);
|
||||
$this->redirect(array('action' => 'index'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getJobStatus($id): string
|
||||
{
|
||||
$status = null;
|
||||
if (!empty($id)) {
|
||||
$job = $this->getBackgroundJobsTool()->getJob($id);
|
||||
$status = $job ? $job->status() : $status;
|
||||
}
|
||||
|
||||
return $this->jobStatusConverter($status);
|
||||
}
|
||||
|
||||
private function getFailedJobLog(string $id): array
|
||||
{
|
||||
$job = $this->getBackgroundJobsTool()->getJob($id);
|
||||
$output = $job ? $job->output() : __('Job status not found.');
|
||||
$backtrace = $job ? explode("\n", $job->error()) : [];
|
||||
|
||||
return [
|
||||
'error' => $output ?? $backtrace[0] ?? '',
|
||||
'backtrace' => $backtrace
|
||||
];
|
||||
}
|
||||
}
|
|
@ -4,17 +4,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Lib\Tools;
|
||||
|
||||
use Cake\Utility\Text;
|
||||
|
||||
use App\Model\Entity\Worker;
|
||||
use App\Model\Entity\BackgroundJob;
|
||||
use Redis;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use Cake\Log\LogTrait;
|
||||
use InvalidArgumentException;
|
||||
use Cake\ORM\Locator\LocatorAwareTrait;
|
||||
use App\Model\Entity\Worker;
|
||||
use Cake\Datasource\Exception\RecordNotFoundException;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Log\LogTrait;
|
||||
use Cake\ORM\Locator\LocatorAwareTrait;
|
||||
use Cake\Utility\Text;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Redis;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* BackgroundJobs Tool
|
||||
|
@ -41,53 +41,52 @@ class BackgroundJobsTool
|
|||
/** @var \Supervisor\Supervisor */
|
||||
private $Supervisor;
|
||||
|
||||
const MISP_WORKERS_PROCESS_GROUP = 'misp-workers';
|
||||
public const MISP_WORKERS_PROCESS_GROUP = 'misp-workers';
|
||||
|
||||
const
|
||||
public const
|
||||
STATUS_RUNNING = 0,
|
||||
STATUS_NOT_ENABLED = 1,
|
||||
STATUS_REDIS_NOT_OK = 2,
|
||||
STATUS_SUPERVISOR_NOT_OK = 3,
|
||||
STATUS_REDIS_AND_SUPERVISOR_NOT_OK = 4;
|
||||
|
||||
const
|
||||
public const
|
||||
DEFAULT_QUEUE = 'default',
|
||||
EMAIL_QUEUE = 'email',
|
||||
CACHE_QUEUE = 'cache',
|
||||
PRIO_QUEUE = 'prio',
|
||||
UPDATE_QUEUE = 'update',
|
||||
SCHEDULER_QUEUE = 'scheduler';
|
||||
UPDATE_QUEUE = 'update';
|
||||
|
||||
const VALID_QUEUES = [
|
||||
self::DEFAULT_QUEUE,
|
||||
self::EMAIL_QUEUE,
|
||||
self::CACHE_QUEUE,
|
||||
self::PRIO_QUEUE,
|
||||
self::UPDATE_QUEUE,
|
||||
self::SCHEDULER_QUEUE,
|
||||
];
|
||||
public const
|
||||
VALID_QUEUES = [
|
||||
self::DEFAULT_QUEUE,
|
||||
self::EMAIL_QUEUE,
|
||||
self::CACHE_QUEUE,
|
||||
self::PRIO_QUEUE,
|
||||
self::UPDATE_QUEUE
|
||||
];
|
||||
|
||||
const
|
||||
public const
|
||||
CMD_EVENT = 'event',
|
||||
CMD_SERVER = 'server',
|
||||
CMD_ADMIN = 'admin',
|
||||
CMD_WORKFLOW = 'workflow';
|
||||
|
||||
const ALLOWED_COMMANDS = [
|
||||
public const ALLOWED_COMMANDS = [
|
||||
self::CMD_EVENT,
|
||||
self::CMD_SERVER,
|
||||
self::CMD_ADMIN,
|
||||
self::CMD_WORKFLOW,
|
||||
self::CMD_WORKFLOW
|
||||
];
|
||||
|
||||
const CMD_TO_SHELL_DICT = [
|
||||
public const CMD_TO_SHELL_DICT = [
|
||||
self::CMD_EVENT => 'EventShell',
|
||||
self::CMD_SERVER => 'ServerShell',
|
||||
self::CMD_ADMIN => 'AdminShell',
|
||||
self::CMD_WORKFLOW => 'WorkflowShell',
|
||||
self::CMD_WORKFLOW => 'WorkflowShell'
|
||||
];
|
||||
|
||||
const JOB_STATUS_PREFIX = 'job_status',
|
||||
private const JOB_STATUS_PREFIX = 'job_status',
|
||||
DATA_CONTENT_PREFIX = 'data_content';
|
||||
|
||||
/** @var array */
|
||||
|
@ -193,7 +192,8 @@ class BackgroundJobsTool
|
|||
'id' => Text::uuid(),
|
||||
'command' => $command,
|
||||
'args' => $args,
|
||||
'metadata' => $metadata
|
||||
'metadata' => $metadata,
|
||||
'worker' => $queue,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -203,7 +203,7 @@ class BackgroundJobsTool
|
|||
$this->RedisConnection->exec();
|
||||
|
||||
if ($jobId) {
|
||||
$this->updateJobProcessId($jobId, $backgroundJob->id());
|
||||
$this->updateJobProcessId($jobId, $backgroundJob);
|
||||
}
|
||||
|
||||
return $backgroundJob->id();
|
||||
|
@ -301,14 +301,16 @@ class BackgroundJobsTool
|
|||
foreach ($procs as $proc) {
|
||||
if ($proc->offsetGet('group') === self::MISP_WORKERS_PROCESS_GROUP) {
|
||||
if ($proc->offsetGet('pid') > 0) {
|
||||
$workers[] = new Worker([
|
||||
'pid' => $proc->offsetGet('pid'),
|
||||
'queue' => explode("_", $proc->offsetGet('name'))[0],
|
||||
'user' => $this->processUser((int) $proc->offsetGet('pid')),
|
||||
'createdAt' => $proc->offsetGet('start'),
|
||||
'updatedAt' => $proc->offsetGet('now'),
|
||||
'status' => $this->convertProcessStatus($proc->offsetGet('state'))
|
||||
]);
|
||||
$workers[] = new Worker(
|
||||
[
|
||||
'pid' => $proc->offsetGet('pid'),
|
||||
'queue' => explode("_", $proc->offsetGet('name'))[0],
|
||||
'user' => $this->processUser((int) $proc->offsetGet('pid')),
|
||||
'createdAt' => $proc->offsetGet('start'),
|
||||
'updatedAt' => $proc->offsetGet('now'),
|
||||
'status' => $this->convertProcessStatus($proc->offsetGet('state'))
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -672,11 +674,24 @@ class BackgroundJobsTool
|
|||
return new \Supervisor\Supervisor($client);
|
||||
}
|
||||
|
||||
private function updateJobProcessId(int $jobId, string $processId)
|
||||
private function updateJobProcessId(int $jobId, BackgroundJob $backgroundJob)
|
||||
{
|
||||
$JobTable = $this->fetchTable('Jobs');
|
||||
$jobEntity = $JobTable->get($jobId);
|
||||
$jobEntity->set('process_id', $processId);
|
||||
try {
|
||||
$jobEntity = $JobTable->get($jobId);
|
||||
} catch (RecordNotFoundException $e) {
|
||||
$this->log("Job ID does not exist in the database, creating Job database record.", 'warning');
|
||||
$jobEntity = $JobTable->newEntity(
|
||||
[
|
||||
'id' => $jobId,
|
||||
'worker' => $backgroundJob->worker(),
|
||||
'job_type' => '?',
|
||||
'job_input' => '?',
|
||||
'message' => '?'
|
||||
]
|
||||
);
|
||||
}
|
||||
$jobEntity->set('process_id', $backgroundJob->id());
|
||||
$JobTable->save($jobEntity);
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@ class BackgroundJob implements JsonSerializable
|
|||
/** @var integer */
|
||||
private $returnCode;
|
||||
|
||||
/** @var string */
|
||||
private $worker;
|
||||
|
||||
public function __construct(array $properties)
|
||||
{
|
||||
$this->id = $properties['id'];
|
||||
|
@ -67,6 +70,7 @@ class BackgroundJob implements JsonSerializable
|
|||
$this->error = $properties['error'] ?? null;
|
||||
$this->progress = $properties['progress'] ?? 0;
|
||||
$this->metadata = $properties['metadata'] ?? [];
|
||||
$this->worker = $properties['worker'] ?? Job::WORKER_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,7 +118,7 @@ class BackgroundJob implements JsonSerializable
|
|||
while (true) {
|
||||
$read = [$pipes[1], $pipes[2]];
|
||||
$write = null;
|
||||
$fcept = null;
|
||||
$except = null;
|
||||
|
||||
if (false === ($changedStreams = stream_select($read, $write, $except, 5))) {
|
||||
throw new RuntimeException("Could not select stream");
|
||||
|
@ -213,6 +217,11 @@ class BackgroundJob implements JsonSerializable
|
|||
return $this->returnCode;
|
||||
}
|
||||
|
||||
public function worker(): string
|
||||
{
|
||||
return $this->worker;
|
||||
}
|
||||
|
||||
public function setStatus(int $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
|
@ -237,4 +246,9 @@ class BackgroundJob implements JsonSerializable
|
|||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
}
|
||||
|
||||
public function setWorker(string $worker)
|
||||
{
|
||||
$this->worker = $worker;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
|
||||
class Job extends AppModel
|
||||
{
|
||||
public const STATUS_WAITING = 1,
|
||||
STATUS_RUNNING = 2,
|
||||
STATUS_FAILED = 3,
|
||||
STATUS_COMPLETED = 4;
|
||||
|
||||
public const WORKER_EMAIL = 'email',
|
||||
WORKER_PRIO = 'prio',
|
||||
WORKER_DEFAULT = 'default',
|
||||
WORKER_CACHE = 'cache',
|
||||
WORKER_UPDATE = 'update';
|
||||
}
|
|
@ -2,14 +2,189 @@
|
|||
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Lib\Tools\BackgroundJobsTool;
|
||||
use App\Model\Entity\Job;
|
||||
use App\Model\Table\AppTable;
|
||||
use ArrayObject;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\ORM\Locator\LocatorAwareTrait;
|
||||
use Exception;
|
||||
|
||||
class JobsTable extends AppTable
|
||||
{
|
||||
use LocatorAwareTrait;
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->belongsTo(
|
||||
'Organisations',
|
||||
[
|
||||
'dependent' => false,
|
||||
'cascadeCallbacks' => false,
|
||||
'foreignKey' => 'org_id',
|
||||
'propertyName' => 'Organisation'
|
||||
]
|
||||
);
|
||||
|
||||
$this->setDisplayField('name');
|
||||
}
|
||||
|
||||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
||||
{
|
||||
$date = date('Y-m-d H:i:s');
|
||||
if (!isset($data['date_created'])) {
|
||||
$data['date_created'] = $date;
|
||||
}
|
||||
$data['date_modified'] = $date;
|
||||
}
|
||||
|
||||
public function cache($type, $user)
|
||||
{
|
||||
$jobId = $this->createJob(
|
||||
$user,
|
||||
Job::WORKER_CACHE,
|
||||
'cache_' . $type,
|
||||
$user['Role']['perm_site_admin'] ? 'All events.' : 'Events visible to: ' . $user['Organisation']['name'],
|
||||
'Fetching events.'
|
||||
);
|
||||
|
||||
$EventsTable = $this->fetchTable('Events');
|
||||
|
||||
if (in_array($type, array_keys($EventsTable->exportTypes())) && $type !== 'bro') {
|
||||
|
||||
$this->getBackgroundJobsTool()->enqueue(
|
||||
BackgroundJobsTool::CACHE_QUEUE,
|
||||
BackgroundJobsTool::CMD_EVENT,
|
||||
[
|
||||
'cache',
|
||||
$user['id'],
|
||||
$jobId,
|
||||
$type
|
||||
],
|
||||
true,
|
||||
$jobId
|
||||
);
|
||||
} elseif ($type === 'bro') {
|
||||
|
||||
$this->getBackgroundJobsTool()->enqueue(
|
||||
BackgroundJobsTool::CACHE_QUEUE,
|
||||
BackgroundJobsTool::CMD_EVENT,
|
||||
[
|
||||
'cachebro',
|
||||
$user['id'],
|
||||
$jobId,
|
||||
$type
|
||||
],
|
||||
true,
|
||||
$jobId
|
||||
);
|
||||
} else {
|
||||
throw new MethodNotAllowedException('Invalid export type.');
|
||||
}
|
||||
|
||||
return $jobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $user
|
||||
* @param string $worker
|
||||
* @param string $jobType
|
||||
* @param string$jobInput
|
||||
* @param string $message
|
||||
* @return int Job ID
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createJob($user, $worker, $jobType, $jobInput, $message = '')
|
||||
{
|
||||
$job = $this->newEntity(
|
||||
[
|
||||
'worker' => $worker,
|
||||
'status' => 0,
|
||||
'retries' => 0,
|
||||
'org_id' => $user === 'SYSTEM' ? 0 : $user['org_id'],
|
||||
'job_type' => $jobType,
|
||||
'job_input' => $jobInput,
|
||||
'message' => $message,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$this->save($job, ['atomic' => false])) { // no need to start transaction for single insert
|
||||
throw new Exception("Could not save job.");
|
||||
}
|
||||
return $job->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $jobId
|
||||
* @param string|null $message
|
||||
* @param int|null $progress
|
||||
* @return bool|null
|
||||
*/
|
||||
public function saveProgress($jobId = null, $message = null, $progress = null)
|
||||
{
|
||||
if ($jobId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$jobData = [
|
||||
$this->primaryKey => $jobId,
|
||||
];
|
||||
if ($message !== null) {
|
||||
$jobData['message'] = $message;
|
||||
}
|
||||
if ($progress !== null) {
|
||||
$jobData['progress'] = $progress;
|
||||
if ($progress >= 100) {
|
||||
$jobData['status'] = Job::STATUS_COMPLETED;
|
||||
}
|
||||
}
|
||||
$jobEntity = $this->newEntity($jobData);
|
||||
try {
|
||||
if ($this->save($jobEntity, ['atomic' => false])) {
|
||||
return true;
|
||||
}
|
||||
$this->log("Could not save progress for job $jobId because of validation errors: " . json_encode($this->validationErrors), LOG_NOTICE);
|
||||
} catch (Exception $e) {
|
||||
$this->logException("Could not save progress for job $jobId", $e, LOG_NOTICE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $jobId
|
||||
* @param bool $success
|
||||
* @param string|null $message
|
||||
* @return bool|null
|
||||
*/
|
||||
public function saveStatus($jobId = null, $success = true, $message = null)
|
||||
{
|
||||
if ($jobId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
$message = $success ? __('Job done.') : __('Job failed.');
|
||||
}
|
||||
|
||||
$jobData = $this->newEntity(
|
||||
[
|
||||
$this->primaryKey => $jobId,
|
||||
'status' => $success ? Job::STATUS_COMPLETED : Job::STATUS_FAILED,
|
||||
'message' => $message,
|
||||
'progress' => 100,
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
if ($this->save($jobData)) {
|
||||
return true;
|
||||
}
|
||||
$this->log("Could not save status for job $jobId because of validation errors: " . json_encode($this->validationErrors), LOG_NOTICE);
|
||||
} catch (Exception $e) {
|
||||
$this->logException("Could not save progress for job $jobId", $e, LOG_NOTICE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Lib\Tools\ProcessTool;
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\Core\Configure;
|
||||
use Exception;
|
||||
|
||||
class ServersTable extends AppTable
|
||||
{
|
||||
|
@ -46,15 +48,99 @@ class ServersTable extends AppTable
|
|||
$conditions = ['OR' => [
|
||||
'LOWER(Servers.name)' => strtolower($id),
|
||||
'LOWER(Servers.url)' => strtolower($id)
|
||||
]];
|
||||
]
|
||||
];
|
||||
}
|
||||
$server = $this->find(
|
||||
'all',
|
||||
[
|
||||
'conditions' => $conditions,
|
||||
'recursive' => -1
|
||||
'conditions' => $conditions,
|
||||
'recursive' => -1
|
||||
]
|
||||
)->disableHydration()->first();
|
||||
return (empty($server)) ? false : $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $workerIssueCount
|
||||
* @return array
|
||||
* @throws ProcessException
|
||||
*/
|
||||
public function workerDiagnostics(&$workerIssueCount)
|
||||
{
|
||||
$worker_array = [
|
||||
'cache' => ['ok' => false],
|
||||
'default' => ['ok' => false],
|
||||
'email' => ['ok' => false],
|
||||
'prio' => ['ok' => false],
|
||||
'update' => ['ok' => false]
|
||||
];
|
||||
|
||||
try {
|
||||
$workers = $this->getWorkers();
|
||||
} catch (Exception $e) {
|
||||
// TODO: [3.x-MIGRATION] check exception logging in 3.x
|
||||
// $this->logException('Could not get list of workers.', $e);
|
||||
return $worker_array;
|
||||
}
|
||||
|
||||
$currentUser = ProcessTool::whoami();
|
||||
$procAccessible = file_exists('/proc');
|
||||
foreach ($workers as $pid => $worker) {
|
||||
if (!is_numeric($pid)) {
|
||||
throw new Exception('Non numeric PID found.');
|
||||
}
|
||||
$entry = $worker['type'] === 'regular' ? $worker['queue'] : $worker['type'];
|
||||
$correctUser = ($currentUser === $worker['user']);
|
||||
if ($procAccessible) {
|
||||
$alive = $correctUser && file_exists("/proc/$pid");
|
||||
} else {
|
||||
$alive = 'N/A';
|
||||
}
|
||||
$ok = true;
|
||||
if (!$alive || !$correctUser) {
|
||||
$ok = false;
|
||||
$workerIssueCount++;
|
||||
}
|
||||
$worker_array[$entry]['workers'][] = [
|
||||
'pid' => $pid,
|
||||
'user' => $worker['user'],
|
||||
'alive' => $alive,
|
||||
'correct_user' => $correctUser,
|
||||
'ok' => $ok
|
||||
];
|
||||
}
|
||||
foreach ($worker_array as $k => $queue) {
|
||||
if (isset($queue['workers'])) {
|
||||
foreach ($queue['workers'] as $worker) {
|
||||
if ($worker['ok']) {
|
||||
$worker_array[$k]['ok'] = true; // If at least one worker is up, the queue can be considered working
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$worker_array[$k]['jobCount'] = $this->getBackgroundJobsTool()->getQueueSize($k);
|
||||
|
||||
if (!isset($queue['workers'])) {
|
||||
$workerIssueCount++;
|
||||
$worker_array[$k]['ok'] = false;
|
||||
}
|
||||
}
|
||||
$worker_array['proc_accessible'] = $procAccessible;
|
||||
$worker_array['controls'] = 1;
|
||||
if (Configure::check('MISP.manage_workers')) {
|
||||
$worker_array['controls'] = Configure::read('MISP.manage_workers');
|
||||
}
|
||||
|
||||
if (Configure::read('BackgroundJobs.enabled')) {
|
||||
try {
|
||||
$worker_array['supervisord_status'] = $this->getBackgroundJobsTool()->getSupervisorStatus();
|
||||
} catch (Exception $exception) {
|
||||
$this->logException('Error getting supervisor status.', $exception);
|
||||
$worker_array['supervisord_status'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $worker_array;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<div class="confirmation">
|
||||
<legend><?php echo __('Background Job Error Browser');?></legend>
|
||||
<div style="padding-left:5px;padding-right:5px;padding-bottom:5px;">
|
||||
<div>
|
||||
<?php
|
||||
if (!empty($response)):
|
||||
$stackTrace = "";
|
||||
if (isset($response['backtrace']) && !empty($response['backtrace'])) {
|
||||
foreach ($response['backtrace'] as $line) {
|
||||
$stackTrace .= h($line) . '<br>';
|
||||
}
|
||||
}
|
||||
foreach ($fields as $name => $content):
|
||||
if (isset($response[$content])):
|
||||
?>
|
||||
<span class="bold red"><?php echo h($name); ?></span>: <?php echo h($response[$content]); ?><br />
|
||||
<?php
|
||||
endif;
|
||||
endforeach;
|
||||
?>
|
||||
<a href="#" id="show_stacktrace">(<?php echo __('Click to show stack trace');?>)</a>
|
||||
<a href="#" id="hide_stacktrace" class="hidden">(<?php echo __('Click to hide stack trace');?>)</a>
|
||||
<div id="stacktrace" class="hidden">
|
||||
<?php echo $stackTrace; ?>
|
||||
</div>
|
||||
<?php
|
||||
else:
|
||||
?>
|
||||
<p><?php echo __('No error data found. Generally job error data is purged from Redis after 24 hours, however, you can still view the errors in the log files in "/app/tmp/logs".');?></p>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
<span role="button" tabindex="0" aria-label="<?php echo __('Cancel');?>" title="<?php echo __('Cancel');?>" class="btn btn-inverse" id="PromptNoButton" onClick="cancelPopoverForm();"><?php echo __('Close');?></span>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$("#show_stacktrace").click(function() {
|
||||
$("#show_stacktrace").hide();
|
||||
$("#hide_stacktrace").show();
|
||||
$("#stacktrace").show();
|
||||
});
|
||||
$("#hide_stacktrace").click(function() {
|
||||
$("#hide_stacktrace").hide();
|
||||
$("#show_stacktrace").show();
|
||||
$("#stacktrace").hide();
|
||||
});
|
||||
$(document).ready(function() {
|
||||
resizePopoverBody();
|
||||
});
|
||||
|
||||
$(window).resize(function() {
|
||||
resizePopoverBody();
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'name' => __('ID'),
|
||||
'sort' => 'id',
|
||||
'data_path' => 'id'
|
||||
],
|
||||
[
|
||||
'name' => __('Date Created'),
|
||||
'sort' => 'date_created',
|
||||
'data_path' => 'date_created'
|
||||
],
|
||||
[
|
||||
'name' => __('Date Modified'),
|
||||
'sort' => 'date_modified',
|
||||
'data_path' => 'date_modified'
|
||||
],
|
||||
[
|
||||
'name' => __('Process ID'),
|
||||
'data_path' => 'process_id'
|
||||
],
|
||||
[
|
||||
'name' => __('Worker'),
|
||||
'sort' => 'worker',
|
||||
'data_path' => 'worker'
|
||||
],
|
||||
[
|
||||
'name' => __('Job Type'),
|
||||
'sort' => 'job_type',
|
||||
'data_path' => 'job_type'
|
||||
],
|
||||
[
|
||||
'name' => __('Job Input'),
|
||||
'data_path' => 'job_input'
|
||||
],
|
||||
[
|
||||
'name' => __('Message'),
|
||||
'data_path' => 'message'
|
||||
],
|
||||
[
|
||||
'name' => __('Organisation'),
|
||||
'sort' => 'Organisation.name',
|
||||
'element' => 'org',
|
||||
'data_path' => 'Organisation',
|
||||
'class' => 'short',
|
||||
],
|
||||
[
|
||||
'name' => __('Status'),
|
||||
'sort' => 'job_status',
|
||||
'data_path' => 'job_status'
|
||||
],
|
||||
[
|
||||
'name' => __('Progress'),
|
||||
'data_path' => 'progress'
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
echo $this->element(
|
||||
'genericElements/IndexTable/index_table',
|
||||
[
|
||||
'data' => [
|
||||
'data' => $jobs,
|
||||
'top_bar' => [
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'context_filters',
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => $fields,
|
||||
'title' => empty($ajax) ? __('Jobs') : false
|
||||
]
|
||||
]
|
||||
);
|
Loading…
Reference in New Issue