...
 
Commits (26)
tmp/
vendor/
composer.lock
.idea/
......
image: lganee/fatapache:a4
cache:
key: build-cache
paths:
- vendor/
image: lganee/fatapache:a6-php7.3
variables:
COMPOSER_ALLOW_SUPERUSER: "1"
test:
before_script:
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY_DEPLOY_DEV")
- mkdir -p ~/.ssh data/ tmp/ logs/
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
script:
- composer update
- vendor/phpunit/phpunit/phpunit --coverage-text --colors=never
- date +"%T"
- wget -nv $URL_GET_VENDOR -O $PWD/asalae-vendor.zip
- unzip -q -o $PWD/asalae-vendor.zip -d $PWD
- date +"%T"
- composer dump-autoload
- vendor/pcov/clobber/bin/pcov clobber
- vendor/phpunit/phpunit/phpunit --printer "rpkamp\FancyTestdoxPrinter" --coverage-text --coverage-html coverage-output/
- date +"%T"
- cd $PWD/coverage-output/ && zip -q -r $PWD/coverage.cakephp-beanstalk.zip .
- scp -P $SSH_PORT_DEPLOY_DEV $PWD/coverage.cakephp-beanstalk.zip $URL_DEPLOY_DEV:$DEPLOY_DIR
- ssh -p $SSH_PORT_DEPLOY_DEV $URL_DEPLOY_DEV "sudo rm -rf $DEPLOY_DIR/webroot/coverage/cakephp-beanstalk"
- ssh -p $SSH_PORT_DEPLOY_DEV $URL_DEPLOY_DEV "sudo unzip -q -o $DEPLOY_DIR/coverage.cakephp-beanstalk.zip -d $DEPLOY_DIR/webroot/coverage/cakephp-beanstalk"
- ssh -p $SSH_PORT_DEPLOY_DEV $URL_DEPLOY_DEV "sudo rm -fr $DEPLOY_DIR/coverage.cakephp-beanstalk.zip"
FROM ubuntu:18.04
LABEL maintainer="Mickaël Pastor <mpastor@libriciel.coop>"
RUN apt-get update -yqq
RUN apt-get install beanstalkd -yqq
EXPOSE 11300
ENTRYPOINT ["/usr/bin/beanstalkd"]
# Beanstalk
[![License](https://img.shields.io/badge/licence-CeCILL%20v2-blue.svg)](http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html)
[![build status](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/badges/master/build.svg)](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/pipelines)
[![coverage report](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/badges/master/coverage.svg)](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/pipelines)
[![build status](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/badges/master/pipeline.svg)](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/pipelines/latest)
[![coverage report](https://gitlab.libriciel.fr/CakePHP/cakephp-beanstalk/badges/master/coverage.svg)](https://asalae2.dev.libriciel.fr/coverage/cakephp-beanstalk/index.html)
Plugin Cakephp 3.
......
......@@ -12,7 +12,7 @@
"php": ">=5.6",
"ext-json": "*",
"pda/pheanstalk": "~3.1",
"cakephp/cakephp": "^3.7"
"cakephp/cakephp": "^3.8"
},
"suggest": {
"cakephp/migrations": "Pour une installation via le plugin migration (voir config/Migrations/)"
......
......@@ -9,7 +9,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......
......@@ -17,6 +17,16 @@
</testsuite>
</testsuites>
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector"
file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager" />
</arguments>
</listener>
</listeners>
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
......
......@@ -16,7 +16,7 @@ use Exception;
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......
......@@ -7,7 +7,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......@@ -17,6 +17,9 @@ namespace Beanstalk\Model\Entity;
use Beanstalk\Utility\Beanstalk;
use Cake\Core\Configure;
use Cake\ORM\Entity;
use Exception;
use Pheanstalk\Exception\ServerException;
use Pheanstalk\Response;
/**
* Classe de l'entité
......@@ -54,7 +57,7 @@ class BeanstalkJob extends Entity
/**
* Récupère les informations beanstalk sur un job en fonction de son id
* @return array
* @throws \Exception
* @throws Exception
*/
public function beanstalkStats()
{
......@@ -71,8 +74,8 @@ class BeanstalkJob extends Entity
/**
* Demande le statut du serveur Beanstalkd
* @return array|object|\Pheanstalk\Response
* @throws \Exception
* @return array|object|Response
* @throws Exception
*/
public function getServerBeanstalkStatus()
{
......@@ -95,7 +98,7 @@ class BeanstalkJob extends Entity
/**
* Champ virtuel
* @return string
* @throws \Exception
* @throws Exception
*/
protected function _getState()
{
......@@ -106,7 +109,7 @@ class BeanstalkJob extends Entity
/**
* Champ virtuel
* @return string
* @throws \Exception
* @throws Exception
*/
protected function _getAge()
{
......@@ -117,14 +120,18 @@ class BeanstalkJob extends Entity
/**
* Champ virtuel
* @return mixed
* @throws \Exception
* @throws Exception
*/
protected function _getData()
{
$Beanstalk = $this->getBeanstalk();
if ($Beanstalk->isConnected() && $this->id) {
$Beanstalk->selectJob($this->_properties['jobid']);
return $Beanstalk->getData();
try {
return $Beanstalk->getData();
} catch (ServerException $e) {
return false;
}
} else {
return false;
}
......@@ -133,7 +140,7 @@ class BeanstalkJob extends Entity
/**
* Champ virtuel
* @return string
* @throws \Exception
* @throws Exception
*/
protected function _getStatetrad()
{
......@@ -153,7 +160,7 @@ class BeanstalkJob extends Entity
/**
* Champ virtuel
* @return string
* @throws \Exception
* @throws Exception
*/
protected function _getRelatedId()
{
......
......@@ -6,7 +6,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......
......@@ -6,7 +6,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......@@ -50,20 +50,24 @@ class BeanstalkJobsTable extends Table
{
$validator
->integer('id')
->allowEmptyString('id', 'create');
->allowEmptyString('id', null, 'create');
$validator
->integer('jobid')
->allowEmptyString('jobid', 'create');
->allowEmptyString('jobid', null, 'create');
$validator
->add('tube', 'validFormat', [
'rule' => [
'custom',
"/^[a-z0-9\+\/;\.\$\(\)][a-z0-9\+\/;\.\$\(\)\-]*$/i"
],
'message' => "Format de text invalid"
])
->add(
'tube',
'validFormat',
[
'rule' => [
'custom',
"/^[a-z0-9\+\/;\.\$\(\)][a-z0-9\+\/;\.\$\(\)\-]*$/i"
],
'message' => "Format de text invalid"
]
)
->allowEmptyString('tube'); // default: "default"
$validator
......
......@@ -6,7 +6,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......@@ -43,7 +43,7 @@ class BeanstalkWorkersTable extends Table
{
$validator
->integer('id')
->allowEmptyString('id', 'create');
->allowEmptyString('id', null, 'create');
return $validator;
}
......@@ -75,7 +75,6 @@ class BeanstalkWorkersTable extends Table
*/
public function sync()
{
$hostname = gethostname();
$query = $this->find()->select(['id', 'hostname', 'pid']);
/** @var BeanstalkWorker $entity */
foreach ($query as $entity) {
......
......@@ -7,13 +7,17 @@ namespace Beanstalk\Shell;
use Beanstalk\Exception\CantWorkException;
use Beanstalk\Utility\Beanstalk;
use Cake\Error\Debugger;
use Cake\Console\Exception\StopException;
use Cake\Console\Shell;
use Cake\Database\Connection;
use Cake\Datasource\ConnectionManager;
use Cake\Log\Log;
use Exception;
use Pheanstalk\Exception\ServerException;
use ReflectionClass;
use ReflectionException;
use Throwable;
/**
* Squelette d'un worker
......@@ -22,7 +26,7 @@ use ReflectionClass;
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......@@ -68,9 +72,9 @@ abstract class AbstractWorker implements WorkerInterface
* Constructeur de classe
*
* @param Beanstalk $Beanstalk
* @param boolean $keepAlive
* @param array $params
* @throws \ReflectionException
* @param boolean $keepAlive
* @param array $params
* @throws ReflectionException
*/
public function __construct(
Beanstalk $Beanstalk,
......@@ -94,24 +98,65 @@ abstract class AbstractWorker implements WorkerInterface
public function start()
{
$data = [];
$this->log(sprintf(
"Starting %s ; id=%d pid=%d",
get_called_class(),
(isset($this->params['id']) ? " ".$this->params['id'] : ''),
getmypid()
));
$this->log(
sprintf(
"Starting %s ; id=%d pid=%d",
get_called_class(),
(isset($this->params['id']) ? " ".$this->params['id'] : ''),
getmypid()
)
);
$this->resetConnection();
$stats = [
'min' => INF,
'max' => 0,
'moy' => 0,
'times' => [],
'start' => 0,
'end' => 0,
'jobs' => [
'total' => 0,
'command' => 0,
'done' => 0,
'skipped' => 0,
'errors' => 0,
]
];
while ($this->Worker->getNext()) {
$stats['start'] = microtime(true);
$this->log(sprintf("New job reserved ; jobid=%d", $this->Worker->getJob()->getId()));
try {
$data = $this->Worker->getData();
if ($data === 'shutdown') {
$this->Worker->done();
$this->shutdown();
return;
} elseif ($data === 'getprerequisites') {
$this->submitPrerequisites();
$this->Worker->done();
$this->log("Prerequisites submited");
$this->chronoStop($stats, 'command');
continue;
} elseif (isset($data['command'])
&& $data['command'] === 'getstats'
&& isset($data['beanstalk_worker_id'])
&& $this->params['id'] === (int)$data['beanstalk_worker_id']
) {
$this->Worker->done();
$this->chronoStop($stats, 'command');
$this->submitGetstats(
$sta = [
'durations' => [
'min' => $stats['min'] === INF ? -1 : $stats['min'],
'max' => $stats['max'],
'moy' => $stats['moy'],
],
'jobs' => $stats['jobs'],
]
);
$this->log(Debugger::exportVar($sta));
continue;
} elseif (isset($data['command']) && isset($data['beanstalk_worker_id'])) {
if (isset($this->params['id'])
......@@ -125,7 +170,7 @@ abstract class AbstractWorker implements WorkerInterface
$this->log(sprintf("The command for worker %d was not executed", $data['beanstalk_worker_id']));
$this->Worker->done();
} else {
$sleepTime = isset($params['sleep_time']) ? $params['sleep_time'] : 1;
$sleepTime = isset($this->params['sleep_time']) ? $this->params['sleep_time'] : 1;
$this->log(
sprintf(
"Worker command sent for id=%d (sleeping for %d second(s))",
......@@ -137,25 +182,28 @@ abstract class AbstractWorker implements WorkerInterface
$this->Worker->cancel();
sleep($sleepTime);
}
$this->chronoStop($stats, 'command');
continue;
}
$this->beforeWork($data);
$this->work($data);
$this->afterWork($data);
$this->Worker->done();
$this->log("Job work done");
$this->log("Job work done in ".$this->chronoStop($stats, 'done'));
} catch (CantWorkException $ex) {
$this->afterWork($data);
$this->Worker->done();
$this->log("Job skipped");
$this->chronoStop($stats, 'skipped');
} catch (StopException $e) {
$this->log("Shutdown triggered");
$this->Worker->getPheanstalk()->getConnection()->disconnect();
return $e->getCode();
} catch (\Throwable $ex) {
} catch (Throwable $ex) {
$this->resetConnection();// rollback avant d'éffacer le job
$this->fail($ex, $data);
$this->Worker->fail((string)$ex);
$this->chronoStop($stats, 'errors');
} finally {
$this->resetConnection();
}
......@@ -169,6 +217,41 @@ abstract class AbstractWorker implements WorkerInterface
exit(0);
}
/**
* Arret du chrono de durée du job
* @param array $stats
* @param string $type
* @return string
*/
private function chronoStop(array &$stats, string $type): string
{
$stats['end'] = microtime(true);
$duration = $stats['end'] - $stats['start'];
if ($type === 'done') {
$stats['times'][] = $duration;
if (count($stats['times']) > 100) {
array_shift($stats['times']);
}
$stats['moy'] = array_sum($stats['times']) / count($stats['times']);
$stats['min'] = min($duration, $stats['min']);
$stats['max'] = max($duration, $stats['max']);
}
$stats['jobs']['total']++;
$stats['jobs'][$type]++;
if ($duration < 1) {
return round($duration * 1000).'ms';
} elseif ($duration < 60) {
return round($duration, 3).'s';
} elseif ($duration < 3600) {
return floor($duration / 60).'m '.round($duration % 60).'s';
} else {
$duration /= 60;
return floor($duration / 60).'h '.round($duration % 60).'m';
}
}
/**
* Lancé avant work()
* @param mixed $data
......@@ -180,6 +263,8 @@ abstract class AbstractWorker implements WorkerInterface
/**
* Lancé après work()
* @param mixed $data
* @throws Exception
* @noinspection PhpUnusedParameterInspection
*/
protected function afterWork($data)
{
......@@ -209,6 +294,7 @@ abstract class AbstractWorker implements WorkerInterface
* {@inheritdoc}
*
* @param array $params
* @throws ServerException
*/
public function touch($params = array())
{
......@@ -233,12 +319,21 @@ abstract class AbstractWorker implements WorkerInterface
/**
* {@inheritdoc}
*
* @param \Throwable $ex Exception envoyé depui le bloc start()
* @param mixed $data Data obtenu par Beanstalkd
* @param Throwable $ex Exception envoyé depui le bloc
* start()
* @param mixed $data Data obtenu par Beanstalkd
*/
public function fail(\Throwable $ex, $data)
public function fail(Throwable $ex, $data)
{
$this->log(sprintf("Job work fail on %s:%d with message:\n%s", $ex->getFile(), $ex->getLine(), $ex->getMessage()));
$this->log(
sprintf(
"Job work fail on %s:%d with message:\n%s",
$ex->getFile(),
$ex->getLine(),
$ex->getMessage()
)
);
$this->log($ex->getTraceAsString());
Log::error((string)$ex);
}
......@@ -257,6 +352,13 @@ abstract class AbstractWorker implements WorkerInterface
{
}
/**
* {@inheritdoc}
*/
public function submitGetStats(array $stats)
{
}
/**
* Assure un bon néttoyage de la connection à la base de données pour éviter
* les connections bloqués
......@@ -269,4 +371,13 @@ abstract class AbstractWorker implements WorkerInterface
$conn->rollback();
}
}
/**
* Assure que le job ne soit plus réservé lors de la coupure du worker
* @throws ServerException
*/
public function __destruct()
{
$this->Worker->cancel();
}
}
......@@ -7,7 +7,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......@@ -16,7 +16,7 @@ namespace Beanstalk\Shell;
use Beanstalk\Exception\CantWorkException;
use Beanstalk\Utility\Beanstalk;
use Exception;
use Throwable;
/**
* Interface d'un worker
......@@ -30,8 +30,8 @@ interface WorkerInterface
/**
* WorkerInterface constructor.
* @param Beanstalk $Beanstalk
* @param bool $keepAlive
* @param array $params
* @param bool $keepAlive
* @param array $params
*/
public function __construct(Beanstalk $Beanstalk, $keepAlive = true, array $params = []);
......@@ -75,10 +75,11 @@ interface WorkerInterface
/**
* Callback utilisé en cas d'Exception dans le bloc d'execution du worker
*
* @param \Throwable $ex Exception envoyé depui le bloc start()
* @param mixed $data Data obtenu par Beanstalkd
* @param Throwable $ex Exception envoyé depui le bloc
* start()
* @param mixed $data Data obtenu par Beanstalkd
*/
public function fail(\Throwable $ex, $data);
public function fail(Throwable $ex, $data);
/**
* Met fin à l'execution du worker
......@@ -95,4 +96,11 @@ interface WorkerInterface
* ]
*/
public function submitPrerequisites();
/**
* Permet d'obtenir des stats d'un worker
* @param array $stats
* @return mixed
*/
public function submitGetstats(array $stats);
}
......@@ -7,7 +7,7 @@
*
* @category Beanstalk
*
* @author Ludovic Ganée <ludovic.ganee@libriciel.coop>
* @author Libriciel SCOP <contact@libriciel.coop>
* @copyright (c) 2017, Libriciel
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
*/
......@@ -19,9 +19,12 @@ use Cake\Console\ConsoleOptionParser;
use Cake\Console\Shell;
use Cake\Core\Configure;
use Cake\Datasource\EntityInterface;
use Cake\I18n\Time;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use Exception;
use ReflectionClass;
use ReflectionException;
/**
* Classe du shell
......@@ -57,39 +60,65 @@ class WorkerShell extends Shell
{
$parser = new ConsoleOptionParser();
$parser->addArgument('worker', [
'help' => __("Nom du worker"),
]);
$parser->addOption('dir', [
'help' => __("Dossier de la classe du worker"),
]);
$parser->addOption('suffix', [
'default' => 'Worker',
'help' => __("Permet de spécifier le dossier worker"),
]);
$parser->addOption('tube', [
'help' => __("Nom du tube (par défaut: <nom du worker>)"),
]);
$parser->addOption('one-job', [
'boolean' => true,
'help' => __("N'effectue qu'un seul job - utile pour un worker sous docker"),
]);
$parser->addOption('unique', [
'boolean' => true,
'help' => __("Lance le worker seulement s'il est seul sur son tube"),
]);
$parser->addOption('table-workers', [
'default' => Configure::read('Beanstalk.table_workers', 'Beanstalk.BeanstalkWorkers'),
'help' => __("Permet de spécifier la table utilisée pour stocker les informations sur le worker lancé"),
]);
$parser->addOption('log-file', [
'help' => __("Fichier log du worker <default: logs/worker_<tube>.log>"),
]);
$parser->setDescription([
"--------------------------------------------------------------------",
__("Le cron 'worker' permet de lancer un worker type Beanstalk"),
"--------------------------------------------------------------------",
]);
$parser->addArgument(
'worker',
[
'help' => __("Nom du worker"),
]
);
$parser->addOption(
'dir',
[
'help' => __("Dossier de la classe du worker"),
]
);
$parser->addOption(
'suffix',
[
'default' => 'Worker',
'help' => __("Permet de spécifier le dossier worker"),
]
);
$parser->addOption(
'tube',
[
'help' => __("Nom du tube (par défaut: <nom du worker>)"),
]
);
$parser->addOption(
'one-job',
[
'boolean' => true,
'help' => __("N'effectue qu'un seul job - utile pour un worker sous docker"),
]
);
$parser->addOption(
'unique',
[
'boolean' => true,
'help' => __("Lance le worker seulement s'il est seul sur son tube"),
]
);
$parser->addOption(
'table-workers',
[
'default' => Configure::read('Beanstalk.table_workers', 'Beanstalk.BeanstalkWorkers'),
'help' => __("Permet de spécifier la table utilisée pour stocker les informations sur le worker lancé"),
]
);
$parser->addOption(
'log-file',
[
'help' => __("Fichier log du worker default: logs/worker_<tube>.log>"),
]
);
$parser->setDescription(
[
"--------------------------------------------------------------------",
__("Le cron 'worker' permet de lancer un worker type Beanstalk"),
"--------------------------------------------------------------------",
]
);
return $parser;
}
......@@ -102,15 +131,7 @@ class WorkerShell extends Shell
public function main($worker = null)
{
// On récupère la liste des workers selon les valeurs par défaut ou le param dir
$dir = $this->param('dir');
if ($dir && !is_dir($dir) && is_dir(ROOT . DS . $dir)) {
$dir = ROOT . DS . $dir;
}
$dirs = (array)$dir;
if (!$dirs) {
$dirs = (array)Configure::read('Beanstalk.workerPaths', [APP.'Shell'.DS.'Worker']);
}
$workers = $this->availablesWorkers($dirs);
$workers = WorkerShell::availablesWorkers($this->param('dir'), $this->param('suffix'));
$availables = array_keys($workers);
// On récupère le worker défini en s'assurant qu'il soit parmis les dispos
......@@ -147,9 +168,10 @@ class WorkerShell extends Shell
'tube' => $tube,
'path' => $className,
'pid' => getmypid(),
'last_launch' => new \Cake\I18n\Time,
'last_launch' => new Time,
'hostname' => $hostname,
'logfile' => $this->param('log-file') ?: LOGS.'worker_'.$tube.'.log',
'logfile' => $this->param('log-file')
?: Configure::read('App.paths.logs', LOGS).'worker_'.$tube.'.log',
];
$this->workerEntity = $BeanstalkWorkers->newEntity($data);
$BeanstalkWorkers->saveOrFail($this->workerEntity);
......@@ -185,7 +207,7 @@ class WorkerShell extends Shell
/**
* Donne une instance de Beanstalk
* @param $tube
* @param string $tube
* @return Beanstalk
*/
protected function getBeanstalk($tube)
......@@ -196,36 +218,54 @@ class WorkerShell extends Shell
/**
* Donne la liste des workers Disponibles sur un ensemble de répertoires
* @param array $dirs
* @param string|array $dirs
* @param string $suffix
* @return array[\ReflectionClass]
* @throws \ReflectionException
* @throws ReflectionException
*/
protected function availablesWorkers(array $dirs)
public static function availablesWorkers($dirs = [], $suffix = 'Worker')
{
if (is_null($dirs)) {
$dirs = [];
} elseif (is_string($dirs)) {
$dirs = (array)$dirs;
}
$dirs = array_filter($dirs);
foreach ($dirs as $key => $dir) {
// compatible chemins relatif
if (!is_dir($dir) && is_dir(ROOT . DS . $dir)) {
$dirs[$key] = ROOT . DS . $dir;
}
}
if (!$dirs) {
$dirs = (array)Configure::read('Beanstalk.workerPaths', [APP.'Shell'.DS.'Worker']);
}
$workers = [];
foreach ($dirs as $dir) {
foreach (glob($dir.DS.'*'.$this->param('suffix').'.php') as $filename) {
$this->parseWorkerClassname($filename, $workers);
foreach (glob($dir.DS.'*'.$suffix.'.php') as $filename) {
WorkerShell::parseWorkerClassname($filename, $workers, $suffix);
}
}
ksort($workers);
return $workers;
}
/**
* $filename est tokenized et ne namespace\classname et ajouté à $workers,
* si la classe utilise WorkerInterface et est instanciable
* @param $filename
* @param $workers
* @throws \ReflectionException
* @param string $filename
* @param array $workers
* @param string $suffix
* @throws ReflectionException
*/
protected function parseWorkerClassname($filename, &$workers)
protected static function parseWorkerClassname($filename, &$workers, $suffix = 'Worker')
{
$tokens = token_get_all(file_get_contents($filename));
$inNamespace = false;
$inClassname = false;
$classname = '';
$workername = Inflector::dasherize(
basename($filename, $this->param('suffix').'.php')
basename($filename, $suffix.'.php')
);
// Récupère le contenu du namespace et du classname par tokens dans le fichier
foreach ($tokens as $token) {
......@@ -245,7 +285,7 @@ class WorkerShell extends Shell
$classname .= $token[1];
} elseif ($inClassname) {
$classname = ltrim($classname.'\\'.$token[1], '\\');
$reflection = new \ReflectionClass($classname);
$reflection = new ReflectionClass($classname);
if ($reflection->implementsInterface(WorkerInterface::class) && $reflection->isInstantiable()) {
$workers[$workername] = $classname;
}
......
This diff is collapsed.
......@@ -20,6 +20,7 @@ class MockedBeanstalk extends Beanstalk
'timeout' => Configure::read('Beanstalk.timeout', null),
'persistant' => Configure::read('Beanstalk.persistant', false),
'table_jobs' => Configure::read('Beanstalk.table_jobs', 'Beanstalk.BeanstalkJobs'),
'disable_check_ttr' => Configure::read('Beanstalk.disable_check_ttr', true),
];
$this->tube = $tube;
}
......
......@@ -173,8 +173,8 @@ class MockedPheanstalk implements PheanstalkInterface
/**
* Temporarily prevent jobs being reserved from the given tube.
*
* @param string $tube The tube to pause
* @param int $delay Seconds before jobs may be reserved from this queue.
* @param string $tube The tube to pause
* @param int $delay Seconds before jobs may be reserved from this queue.
*
* @return $this
*/
......@@ -249,10 +249,10 @@ class MockedPheanstalk implements PheanstalkInterface
/**
* Puts a job on the queue.
*
* @param string $data The job data
* @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent)
* @param int $delay Seconds to wait before job becomes ready
* @param int $ttr Time To Run: seconds a job can be reserved for
* @param string $data The job data
* @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent)
* @param int $delay Seconds to wait before job becomes ready
* @param int $ttr Time To Run: seconds a job can be reserved for
*
* @return int The new job ID
*/
......@@ -268,11 +268,11 @@ class MockedPheanstalk implements PheanstalkInterface
* the added benefit that it will not execute the USE command if the client
* is already using the specified tube.
*
* @param string $tube The tube to use
* @param string $data The job data
* @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent)
* @param int $delay Seconds to wait before job becomes ready
* @param int $ttr Time To Run: seconds a job can be reserved for
* @param string $tube The tube to use
* @param string $data The job data
* @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent)
* @param int $delay Seconds to wait before job becomes ready
* @param int $ttr Time To Run: seconds a job can be reserved for
*
* @return int The new job ID
*/
......@@ -287,9 +287,9 @@ class MockedPheanstalk implements PheanstalkInterface
* Marks the jobs state as "ready" to be run by any client.
* It is normally used when the job fails because of a transitory error.
*
* @param object $job Job
* @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent)
* @param int $delay Seconds to wait before job becomes ready
* @param object $job Job
* @param int $priority From 0 (most urgent) to 0xFFFFFFFF (least urgent)
* @param int $delay Seconds to wait before job becomes ready
*
* @return $this
*/
......@@ -333,7 +333,7 @@ class MockedPheanstalk implements PheanstalkInterface
* specified tube.
*
* @param string $tube
* @param int $timeout
* @param int $timeout
*
* @return object Job
*/
......
......@@ -39,17 +39,6 @@ class BeanstalkJobTest extends TestCase
*/
private $BeanstalkJobs;
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
if (empty($this->fixtureManager)) {
$this->fixtureManager = new FixtureManager;
$this->fixtureManager->fixturize($this);
$this->loadFixtures();
}
Cache::disable();
}
public function setUp()
{
parent::setUp();
......@@ -80,47 +69,53 @@ class BeanstalkJobTest extends TestCase
$Pheanstalk->method('reserve')->willReturn($Job);
$Pheanstalk->method('peek')->willReturn($Job);
}
$Pheanstalk->method('statsJob')->willReturn([
"id" => "1",
"tube" => self::TUBE,
"state" => "reserved",
"pri" => "1024",
"age" => "2229",
"delay" => "0",
"ttr" => "60",
"time-left" => "51",
"file" => "540",
"reserves" => "2",
"timeouts" => "0",
"releases" => "1",
"buries" => "0",
"kicks" => "0",
]);
$Pheanstalk->method('statsTube')->willReturn((object)[
"name" => self::TUBE,
"current-jobs-urgent" => "0",
"current-jobs-ready" => "1",
"current-jobs-reserved" => "0",
"current-jobs-delayed" => "0",
"current-jobs-buried" => "0",
"total-jobs" => "2",
"current-using" => "1",
"current-watching" => "1",
"current-waiting" => "0",
"cmd-delete" => "0",
"cmd-pause-tube" => "0",
"pause" => "0",
"pause-time-left" => "0",
]);
$Pheanstalk->method('stats')->willReturn([
'current-jobs-urgent' => "0",
'current-jobs-ready' => "1",
'current-jobs-reserved' => "0",
'current-jobs-delayed' => "0",
'current-jobs-buried' => "0",
'current-workers' => "0",
'uptime' => "0",
]);
$Pheanstalk->method('statsJob')->willReturn(
[
"id" => "1",
"tube" => self::TUBE,
"state" => "reserved",
"pri" => "1024",
"age" => "2229",
"delay" => "0",
"ttr" => "60",
"time-left" => "51",
"file" => "540",
"reserves" => "2",
"timeouts" => "0",
"releases" => "1",
"buries" => "0",
"kicks" => "0",
]
);
$Pheanstalk->method('statsTube')->willReturn(
(object)[
"name" => self::TUBE,
"current-jobs-urgent" => "0",
"current-jobs-ready" => "1",
"current-jobs-reserved" => "0",
"current-jobs-delayed" => "0",
"current-jobs-buried" => "0",
"total-jobs" => "2",
"current-using" => "1",
"current-watching" => "1",
"current-waiting" => "0",
"cmd-delete" => "0",
"cmd-pause-tube" => "0",
"pause" => "0",
"pause-time-left" => "0",
]
);
$Pheanstalk->method('stats')->willReturn(
[
'current-jobs-urgent' => "0",
'current-jobs-ready' => "1",
'current-jobs-reserved' => "0",
'current-jobs-delayed' => "0",
'current-jobs-buried' => "0",
'current-workers' => "0",
'uptime' => "0",
]
);
$Pheanstalk->method('getConnection')->willReturn($Connection);
MockedBeanstalk::$StaticPheanstalk = $Pheanstalk;
......@@ -174,10 +169,12 @@ class BeanstalkJobTest extends TestCase
}
$this->assertEquals($expected, $actual);
$success = $this->Beanstalk->emit([
'message' => 'Hello world',
'user_id' => 123
]);
$success = $this->Beanstalk->emit(
[
'message' => 'Hello world',
'user_id' => 123
]
);
$this->assertTrue((bool)$success);
$actual = $this->Beanstalk->count();
$expected = 1;
......@@ -230,15 +227,17 @@ class BeanstalkJobTest extends TestCase
$Pheanstalk->method('peek')->willThrowException(new \Exception);
$this->BeanstalkJobs->deleteAll([]);
$this->BeanstalkJobs->sync = false;
$entity = $this->BeanstalkJobs->newEntity([
'jobid' => 1456,
'tube' => 'testunit',
'priority' => 1024,
'last_status' => 'ready',
'user_id' => 1,
'delay' => 0,
'ttr' => 60
]);
$entity = $this->BeanstalkJobs->newEntity(
[
'jobid' => 1456,
'tube' => 'testunit',
'priority' => 1024,
'last_status' => 'ready',
'user_id' => 1,
'delay' => 0,
'ttr' => 60
]
);
$this->BeanstalkJobs->save($entity);
$this->assertNotEmpty($entity->get('id'));
......
......@@ -25,17 +25,6 @@ class WorkerShellTest extends TestCase
'app.BeanstalkJobs',
];
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
if (empty($this->fixtureManager)) {
$this->fixtureManager = new FixtureManager;
$this->fixtureManager->fixturize($this);
$this->loadFixtures();
}
Cache::disable();
}
public function setUp()
{
parent::setUp();
......@@ -63,14 +52,16 @@ class WorkerShellTest extends TestCase
/** @var BeanstalkWorkersTable $BeanstalkWorkers */
$BeanstalkWorkers = TableRegistry::getTableLocator()->get('Beanstalk.BeanstalkWorkers');
$BeanstalkWorkers->sync = true;
$entity = $BeanstalkWorkers->newEntity([
'name' => 'test',
'tube' => 'test',
'path' => TestWorker::class,
'pid' => 1,
'last_launch' => new Time,
'hostname' => 'testunit',
]);
$entity = $BeanstalkWorkers->newEntity(
[
'name' => 'test',
'tube' => 'test',
'path' => TestWorker::class,
'pid' => 1,
'last_launch' => new Time,
'hostname' => 'testunit',
]
);
$BeanstalkWorkers->save($entity);
TestWorker::$workData = ['foo', 'bar', 'baz'];
TestWorker::$keepworkingData = [true];// 1 true = 2 boucle
......
......@@ -23,17 +23,6 @@ class BeanstalkTest extends TestCase
'app.BeanstalkJobs',
];
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
if (empty($this->fixtureManager)) {
$this->fixtureManager = new FixtureManager;
$this->fixtureManager->fixturize($this);
$this->loadFixtures();
}
Cache::disable();
}
public function setUp()
{
parent::setUp();
......@@ -110,14 +99,16 @@ class BeanstalkTest extends TestCase
{
$BeanstalkJobs = TableRegistry::getTableLocator()->get('BeanstalkJobs');
$BeanstalkJobs->deleteAll([]);
$entity = $BeanstalkJobs->newEntity([
'jobid' => 123,
'tube' => 'testunit-validtubename',
'data' => 'message',
'priority' => 1024,
'delay' => 0,
'ttr' => 60
]);
$entity = $BeanstalkJobs->newEntity(
[
'jobid' => 123,
'tube' => 'testunit-validtubename',
'data' => 'message',
'priority' => 1024,
'delay' => 0,
'ttr' => 60
]
);
$BeanstalkJobs->saveOrFail($entity);
$job = $this->createMock(Job::class);
$job->method('getId')->willReturn(123);
......@@ -132,15 +123,17 @@ class BeanstalkTest extends TestCase
{
$BeanstalkJobs = TableRegistry::getTableLocator()->get('BeanstalkJobs');
$BeanstalkJobs->deleteAll([]);
$entity = $BeanstalkJobs->newEntity([
'jobid' => 123,
'tube' => 'testunit-validtubename',
'data' => 'message',
'priority' => 1024,
'delay' => 0,
'ttr' => 60,
'last_status' => 'ready',
]);
$entity = $BeanstalkJobs->newEntity(
[
'jobid' => 123,
'tube' => 'testunit-validtubename',
'data' => 'message',
'priority' => 1024,
'delay' => 0,
'ttr' => 60,
'last_status' => 'ready',
]
);
$BeanstalkJobs->saveOrFail($entity);
$job = $this->createMock(Job::class);
$job->method('getId')->willReturn(123);
......@@ -155,15 +148,17 @@ class BeanstalkTest extends TestCase
{
$BeanstalkJobs = TableRegistry::getTableLocator()->get('BeanstalkJobs');
$BeanstalkJobs->deleteAll([]);
$entity = $BeanstalkJobs->newEntity([
'jobid' => 123,
'tube' => 'testunit-validtubename',
'data' => 'message',
'priority' => 1024,
'delay' => 0,
'ttr' => 60,
'last_status' => 'reserved',
]);
$entity = $BeanstalkJobs->newEntity(
[
'jobid' => 123,
'tube' => 'testunit-validtubename',
'data' => 'message',
'priority' => 1024,
'delay' => 0,
'ttr' => 60,
'last_status' => 'reserved',
]
);
$BeanstalkJobs->saveOrFail($entity);
$job = $this->createMock(Job::class);
$job->method('getId')->willReturn(123);
......
......@@ -75,8 +75,9 @@ class TestWorker implements WorkerInterface
/**
* Callback utilisé en cas d'Exception dans le bloc d'execution du worker
*
* @param Throwable $ex Exception envoyé depui le bloc start()
* @param mixed $data Data obtenu par Beanstalkd
* @param Throwable $ex Exception envoyé depui le bloc
* start()
* @param mixed $data Data obtenu par Beanstalkd
*/
public function fail(Throwable $ex, $data)
{
......@@ -106,10 +107,19 @@ class TestWorker implements WorkerInterface
/**
* WorkerInterface constructor.
* @param Beanstalk $Beanstalk
* @param bool $keepAlive
* @param array $params
* @param bool $keepAlive
* @param array $params
*/
public function __construct(Beanstalk $Beanstalk, $keepAlive = true, array $params = [])
{
}
/**
* Permet d'obtenir des stats d'un worker
* @param array $stats
* @return mixed
*/
public function submitGetstats(array $stats)
{
}
}
......@@ -6,6 +6,7 @@
* unit tests in this file.
*/
use Cake\Cache\Cache;
use Cake\Core\Configure;
require dirname(__DIR__) . '/vendor/autoload.php';
......@@ -31,34 +32,64 @@ if (extension_loaded('sqlite3')) {
try {
Cake\Datasource\ConnectionManager::get('test');
} catch (Exception $e) {
Cake\Datasource\ConnectionManager::setConfig('test', [
'className' => Cake\Database\Connection::class,
'driver' => Cake\Database\Driver\Sqlite::class,
'database' => DATABASE_TEST_SQLITE,
]);
Cake\Datasource\ConnectionManager::setConfig(
'test',
[
'className' => Cake\Database\Connection::class,
'driver' => Cake\Database\Driver\Sqlite::class,
'database' => DATABASE_TEST_SQLITE,
]
);
}
}
if (!Configure::read('App')) {
Configure::write([
'debug' => true,
'App' => [
'namespace' => 'Beanstalk',
'encoding' => env('APP_ENCODING', 'UTF-8'),
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'fr_FR'),
'base' => false,
'dir' => 'src',
'webroot' => 'webroot',
'wwwRoot' => WWW_ROOT,
'fullBaseUrl' => false,
'imageBaseUrl' => 'img/',
'cssBaseUrl' => 'css/',
'jsBaseUrl' => 'js/',
'paths' => [
'plugins' => [ROOT . DS . 'plugins' . DS],
'templates' => [APP . 'Template' . DS],
'locales' => [APP . 'Locale' . DS],
Configure::write(
[
'debug' => true,
'App' => [
'namespace' => 'Beanstalk',
'encoding' => env('APP_ENCODING', 'UTF-8'),
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'fr_FR'),
'base' => false,
'dir' => 'src',
'webroot' => 'webroot',
'wwwRoot' => WWW_ROOT,
'fullBaseUrl' => false,
'imageBaseUrl' => 'img/',
'cssBaseUrl' => 'css/',
'jsBaseUrl' => 'js/',
'paths' => [
'plugins' => [ROOT . DS . 'plugins' . DS],
'templates' => [APP . 'Template' . DS],
'locales' => [APP . 'Locale' . DS],
],
],
],
]);
]
);
Cache::setConfig(
[
'default' => [
'className' => 'File',
'path' => CACHE,
'url' => env('CACHE_DEFAULT_URL', null),
],
'_cake_core_' => [
'className' => 'File',
'prefix' => 'myapp_cake_core_',
'path' => CACHE . 'persistent/',
'serialize' => true,
'duration' => '+1 years',
'url' => env('CACHE_CAKECORE_URL', null),
],
'_cake_model_' => [
'className' => 'File',
'prefix' => 'myapp_cake_model_',
'path' => CACHE . 'models/',
'serialize' => true,
'duration' => '+1 years',
'url' => env('CACHE_CAKEMODEL_URL', null),
],
]
);
}