Merge pull request #1075 from Block8/dc/phpci-fixes

Fixing PHPCI Errors
This commit is contained in:
Dan Cryer 2015-10-09 10:40:12 +01:00
commit edcff1030d
14 changed files with 235 additions and 73 deletions

View file

@ -136,7 +136,8 @@ class Application extends b8\Application
protected function setLayoutVariables(View &$layout) protected function setLayoutVariables(View &$layout)
{ {
$groups = array(); $groups = array();
$groupList = b8\Store\Factory::getStore('ProjectGroup')->getWhere(array(), 100, 0, array(), array('title' => 'ASC')); $groupStore = b8\Store\Factory::getStore('ProjectGroup');
$groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groupList['items'] as $group) { foreach ($groupList['items'] as $group) {
$thisGroup = array('title' => $group->getTitle()); $thisGroup = array('title' => $group->getTitle());

View file

@ -66,6 +66,8 @@ class BuildFactory
case 'svn': case 'svn':
$type = 'SubversionBuild'; $type = 'SubversionBuild';
break; break;
default:
return $build;
} }
$class = '\\PHPCI\\Model\\Build\\' . $type; $class = '\\PHPCI\\Model\\Build\\' . $type;

View file

@ -28,11 +28,17 @@ class GroupController extends Controller
*/ */
protected $groupStore; protected $groupStore;
/**
* Set up this controller.
*/
public function init() public function init()
{ {
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup'); $this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
} }
/**
* List project groups.
*/
public function index() public function index()
{ {
$this->requireAdmin(); $this->requireAdmin();
@ -53,12 +59,17 @@ class GroupController extends Controller
$this->view->groups = $groups; $this->view->groups = $groups;
} }
public function edit($id = null) /**
* Add or edit a project group.
* @param null $groupId
* @return void|b8\Http\Response\RedirectResponse
*/
public function edit($groupId = null)
{ {
$this->requireAdmin(); $this->requireAdmin();
if (!is_null($id)) { if (!is_null($groupId)) {
$group = $this->groupStore->getById($id); $group = $this->groupStore->getById($groupId);
} else { } else {
$group = new ProjectGroup(); $group = new ProjectGroup();
} }
@ -74,7 +85,7 @@ class GroupController extends Controller
$form = new Form(); $form = new Form();
$form->setMethod('POST'); $form->setMethod('POST');
$form->setAction(PHPCI_URL . 'group/edit' . (!is_null($id) ? '/' . $id : '')); $form->setAction(PHPCI_URL . 'group/edit' . (!is_null($groupId) ? '/' . $groupId : ''));
$title = new Form\Element\Text('title'); $title = new Form\Element\Text('title');
$title->setContainerClass('form-group'); $title->setContainerClass('form-group');
@ -91,10 +102,15 @@ class GroupController extends Controller
$this->view->form = $form; $this->view->form = $form;
} }
public function delete($id) /**
* Delete a project group.
* @param $groupId
* @return b8\Http\Response\RedirectResponse
*/
public function delete($groupId)
{ {
$this->requireAdmin(); $this->requireAdmin();
$group = $this->groupStore->getById($id); $group = $this->groupStore->getById($groupId);
$this->groupStore->delete($group); $this->groupStore->delete($group);
$response = new b8\Http\Response\RedirectResponse(); $response = new b8\Http\Response\RedirectResponse();

View file

@ -144,6 +144,10 @@ class HomeController extends \PHPCI\Controller
return $view->render(); return $view->render();
} }
/**
* Get a summary of the project groups we have, and what projects they have in them.
* @return array
*/
protected function getGroupInfo() protected function getGroupInfo()
{ {
$rtn = array(); $rtn = array();
@ -159,5 +163,4 @@ class HomeController extends \PHPCI\Controller
return $rtn; return $rtn;
} }
} }

View file

@ -50,10 +50,6 @@ class Executor
public function executePlugins(&$config, $stage) public function executePlugins(&$config, $stage)
{ {
$success = true; $success = true;
/** @var \PHPCI\Model\Build $build */
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$branch = $build->getBranch();
$pluginsToExecute = array(); $pluginsToExecute = array();
// If we have global plugins to execute for this stage, add them to the list to be executed: // If we have global plugins to execute for this stage, add them to the list to be executed:
@ -61,31 +57,7 @@ class Executor
$pluginsToExecute[] = $config[$stage]; $pluginsToExecute[] = $config[$stage];
} }
// If we have branch-specific plugins to execute, add them to the list to be executed: $pluginsToExecute = $this->getBranchSpecificPlugins($config, $stage, $pluginsToExecute);
if (isset($config['branch-' . $branch][$stage]) && is_array($config['branch-' . $branch][$stage])) {
$branchConfig = $config['branch-' . $branch];
$runOption = isset($branchConfig['run-option']) ? $branchConfig['run-option'] : 'after';
$plugins = $config['branch-' . $branch][$stage];
switch ($runOption) {
case 'replace':
$pluginsToExecute = array();
$pluginsToExecute[] = $plugins;
break;
case 'before':
array_unshift($pluginsToExecute, $plugins);
break;
case 'after':
array_push($pluginsToExecute, $plugins);
break;
default:
array_push($pluginsToExecute, $plugins);
break;
}
}
foreach ($pluginsToExecute as $pluginSet) { foreach ($pluginsToExecute as $pluginSet) {
if (!$this->doExecutePlugins($pluginSet, $stage)) { if (!$this->doExecutePlugins($pluginSet, $stage)) {
@ -96,6 +68,66 @@ class Executor
return $success; return $success;
} }
/**
* Check the config for any plugins specific to the branch we're currently building.
* @param $config
* @param $stage
* @param $pluginsToExecute
* @return array
*/
protected function getBranchSpecificPlugins(&$config, $stage, $pluginsToExecute)
{
/** @var \PHPCI\Model\Build $build */
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$branch = $build->getBranch();
// If we don't have any branch-specific plugins:
if (!isset($config['branch-' . $branch][$stage]) || !is_array($config['branch-' . $branch][$stage])) {
return $pluginsToExecute;
}
// If we have branch-specific plugins to execute, add them to the list to be executed:
$branchConfig = $config['branch-' . $branch];
$plugins = $branchConfig[$stage];
$runOption = 'after';
if (!empty($branchConfig['run-option'])) {
$runOption = $branchConfig['run-option'];
}
switch ($runOption) {
// Replace standard plugin set for this stage with just the branch-specific ones:
case 'replace':
$pluginsToExecute = array();
$pluginsToExecute[] = $plugins;
break;
// Run branch-specific plugins before standard plugins:
case 'before':
array_unshift($pluginsToExecute, $plugins);
break;
// Run branch-specific plugins after standard plugins:
case 'after':
array_push($pluginsToExecute, $plugins);
break;
default:
array_push($pluginsToExecute, $plugins);
break;
}
return $pluginsToExecute;
}
/**
* Execute the list of plugins found for a given testing stage.
* @param $plugins
* @param $stage
* @return bool
* @throws \Exception
*/
protected function doExecutePlugins(&$plugins, $stage) protected function doExecutePlugins(&$plugins, $stage)
{ {
$success = true; $success = true;

View file

@ -87,9 +87,13 @@ class BuildService
$build = $this->buildStore->save($build); $build = $this->buildStore->save($build);
$build = BuildFactory::getBuild($build); $buildId = $build->getId();
$build->sendStatusPostback();
$this->addBuildToQueue($build); if (!empty($buildId)) {
$build = BuildFactory::getBuild($build);
$build->sendStatusPostback();
$this->addBuildToQueue($build);
}
return $build; return $build;
} }
@ -116,9 +120,13 @@ class BuildService
$build = $this->buildStore->save($build); $build = $this->buildStore->save($build);
$build = BuildFactory::getBuild($build); $buildId = $build->getId();
$build->sendStatusPostback();
$this->addBuildToQueue($build); if (!empty($buildId)) {
$build = BuildFactory::getBuild($build);
$build->sendStatusPostback();
$this->addBuildToQueue($build);
}
return $build; return $build;
} }

View file

@ -20,11 +20,18 @@ class BuildMetaStoreBase extends Store
protected $modelName = '\PHPCI\Model\BuildMeta'; protected $modelName = '\PHPCI\Model\BuildMeta';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/**
* Get a BuildMeta by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
return $this->getById($value, $useConnection); return $this->getById($value, $useConnection);
} }
/**
* Get a single BuildMeta by Id.
* @return null|BuildMeta
*/
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -44,6 +51,10 @@ class BuildMetaStoreBase extends Store
return null; return null;
} }
/**
* Get multiple BuildMeta by ProjectId.
* @return array
*/
public function getByProjectId($value, $limit = 1000, $useConnection = 'read') public function getByProjectId($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -72,6 +83,10 @@ class BuildMetaStoreBase extends Store
} }
} }
/**
* Get multiple BuildMeta by BuildId.
* @return array
*/
public function getByBuildId($value, $limit = 1000, $useConnection = 'read') public function getByBuildId($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {

View file

@ -20,11 +20,18 @@ class BuildStoreBase extends Store
protected $modelName = '\PHPCI\Model\Build'; protected $modelName = '\PHPCI\Model\Build';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/**
* Get a Build by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
return $this->getById($value, $useConnection); return $this->getById($value, $useConnection);
} }
/**
* Get a single Build by Id.
* @return null|Build
*/
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -44,6 +51,10 @@ class BuildStoreBase extends Store
return null; return null;
} }
/**
* Get multiple Build by ProjectId.
* @return array
*/
public function getByProjectId($value, $limit = 1000, $useConnection = 'read') public function getByProjectId($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -72,6 +83,10 @@ class BuildStoreBase extends Store
} }
} }
/**
* Get multiple Build by Status.
* @return array
*/
public function getByStatus($value, $limit = 1000, $useConnection = 'read') public function getByStatus($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {

View file

@ -20,11 +20,18 @@ class ProjectGroupStoreBase extends Store
protected $modelName = '\PHPCI\Model\ProjectGroup'; protected $modelName = '\PHPCI\Model\ProjectGroup';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/**
* Get a ProjectGroup by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
return $this->getById($value, $useConnection); return $this->getById($value, $useConnection);
} }
/**
* Get a single ProjectGroup by Id.
* @return null|ProjectGroup
*/
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {

View file

@ -20,11 +20,18 @@ class ProjectStoreBase extends Store
protected $modelName = '\PHPCI\Model\Project'; protected $modelName = '\PHPCI\Model\Project';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/**
* Get a Project by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
return $this->getById($value, $useConnection); return $this->getById($value, $useConnection);
} }
/**
* Get a single Project by Id.
* @return null|Project
*/
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -44,6 +51,10 @@ class ProjectStoreBase extends Store
return null; return null;
} }
/**
* Get multiple Project by Title.
* @return array
*/
public function getByTitle($value, $limit = 1000, $useConnection = 'read') public function getByTitle($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -72,6 +83,10 @@ class ProjectStoreBase extends Store
} }
} }
/**
* Get multiple Project by GroupId.
* @return array
*/
public function getByGroupId($value, $limit = 1000, $useConnection = 'read') public function getByGroupId($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {

View file

@ -20,11 +20,18 @@ class UserStoreBase extends Store
protected $modelName = '\PHPCI\Model\User'; protected $modelName = '\PHPCI\Model\User';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/**
* Get a User by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
return $this->getById($value, $useConnection); return $this->getById($value, $useConnection);
} }
/**
* Get a single User by Id.
* @return null|User
*/
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -44,6 +51,10 @@ class UserStoreBase extends Store
return null; return null;
} }
/**
* Get a single User by Email.
* @return null|User
*/
public function getByEmail($value, $useConnection = 'read') public function getByEmail($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
@ -63,6 +74,10 @@ class UserStoreBase extends Store
return null; return null;
} }
/**
* Get a single User by Name.
* @return null|User
*/
public function getByName($value, $useConnection = 'read') public function getByName($value, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {

View file

@ -6,6 +6,7 @@ use b8\Config;
use b8\Database; use b8\Database;
use b8\Store\Factory; use b8\Store\Factory;
use Monolog\Logger; use Monolog\Logger;
use Pheanstalk\Job;
use Pheanstalk\Pheanstalk; use Pheanstalk\Pheanstalk;
use PHPCI\Builder; use PHPCI\Builder;
use PHPCI\BuildFactory; use PHPCI\BuildFactory;
@ -49,6 +50,16 @@ class BuildWorker
*/ */
protected $queue; protected $queue;
/**
* @var \Pheanstalk\Pheanstalk
*/
protected $pheanstalk;
/**
* @var int
*/
protected $totalJobs = 0;
/** /**
* @param $host * @param $host
* @param $queue * @param $queue
@ -57,6 +68,7 @@ class BuildWorker
{ {
$this->host = $host; $this->host = $host;
$this->queue = $queue; $this->queue = $queue;
$this->pheanstalk = new Pheanstalk($this->host);
} }
/** /**
@ -80,36 +92,20 @@ class BuildWorker
*/ */
public function startWorker() public function startWorker()
{ {
$pheanstalk = new Pheanstalk($this->host); $this->pheanstalk->watch($this->queue);
$pheanstalk->watch($this->queue); $this->pheanstalk->ignore('default');
$pheanstalk->ignore('default');
$buildStore = Factory::getStore('Build'); $buildStore = Factory::getStore('Build');
$jobs = 0;
while ($this->run) { while ($this->run) {
// Get a job from the queue: // Get a job from the queue:
$job = $pheanstalk->reserve(); $job = $this->pheanstalk->reserve();
// Make sure we don't run more than maxJobs jobs on this worker: $this->checkJobLimit();
$jobs++;
if ($this->maxJobs != -1 && $this->maxJobs <= $jobs) {
$this->run = false;
}
// Get the job data and run the job: // Get the job data and run the job:
$jobData = json_decode($job->getData(), true); $jobData = json_decode($job->getData(), true);
if (empty($jobData) || !is_array($jobData)) { if (!$this->verifyJob($job, $jobData)) {
// Probably not from PHPCI.
$pheanstalk->release($job);
continue;
}
if (!array_key_exists('type', $jobData) || $jobData['type'] !== 'phpci.build') {
// Probably not from PHPCI.
$pheanstalk->delete($job);
continue; continue;
} }
@ -128,7 +124,7 @@ class BuildWorker
$build = BuildFactory::getBuildById($jobData['build_id']); $build = BuildFactory::getBuildById($jobData['build_id']);
} catch (\Exception $ex) { } catch (\Exception $ex) {
$this->logger->addWarning('Build #' . $jobData['build_id'] . ' does not exist in the database.'); $this->logger->addWarning('Build #' . $jobData['build_id'] . ' does not exist in the database.');
$pheanstalk->delete($job); $this->pheanstalk->delete($job);
} }
try { try {
@ -147,7 +143,7 @@ class BuildWorker
// If we've caught a PDO Exception, it is probably not the fault of the build, but of a failed // If we've caught a PDO Exception, it is probably not the fault of the build, but of a failed
// connection or similar. Release the job and kill the worker. // connection or similar. Release the job and kill the worker.
$this->run = false; $this->run = false;
$pheanstalk->release($job); $this->pheanstalk->release($job);
} catch (\Exception $ex) { } catch (\Exception $ex) {
$build->setStatus(Build::STATUS_FAILED); $build->setStatus(Build::STATUS_FAILED);
$build->setFinished(new \DateTime()); $build->setFinished(new \DateTime());
@ -163,7 +159,7 @@ class BuildWorker
} }
// Delete the job when we're done: // Delete the job when we're done:
$pheanstalk->delete($job); $this->pheanstalk->delete($job);
} }
} }
@ -174,4 +170,41 @@ class BuildWorker
{ {
$this->run = false; $this->run = false;
} }
/**
* Checks if this worker has done the amount of jobs it is allowed to do, and if so tells it to stop
* after this job completes.
*/
protected function checkJobLimit()
{
// Make sure we don't run more than maxJobs jobs on this worker:
$this->totalJobs++;
if ($this->maxJobs != -1 && $this->maxJobs <= $this->totalJobs) {
$this->stopWorker();
}
}
/**
* Checks that the job received is actually from PHPCI, and has a valid type.
* @param Job $job
* @param $jobData
* @return bool
*/
protected function verifyJob(Job $job, $jobData)
{
if (empty($jobData) || !is_array($jobData)) {
// Probably not from PHPCI.
$this->pheanstalk->delete($job);
return false;
}
if (!array_key_exists('type', $jobData) || $jobData['type'] !== 'phpci.build') {
// Probably not from PHPCI.
$this->pheanstalk->delete($job);
return false;
}
return true;
}
} }

View file

@ -103,7 +103,7 @@ class BuildServiceTest extends \PHPUnit_Framework_TestCase
{ {
$build = new Build(); $build = new Build();
$build->setId(1); $build->setId(1);
$build->setProjectId(101); $build->setProject(101);
$build->setCommitId('abcde'); $build->setCommitId('abcde');
$build->setStatus(Build::STATUS_FAILED); $build->setStatus(Build::STATUS_FAILED);
$build->setLog('Test'); $build->setLog('Test');

10
composer.lock generated
View file

@ -1781,16 +1781,16 @@
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",
"version": "1.0.0", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git", "url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" "reference": "23af31f402993cfd94e99cbc4b782e9a78eb0e97"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23af31f402993cfd94e99cbc4b782e9a78eb0e97",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", "reference": "23af31f402993cfd94e99cbc4b782e9a78eb0e97",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1828,7 +1828,7 @@
"keywords": [ "keywords": [
"global state" "global state"
], ],
"time": "2014-10-06 09:23:50" "time": "2015-06-21 15:11:22"
}, },
{ {
"name": "sebastian/recursion-context", "name": "sebastian/recursion-context",