2014-01-06 21:32:10 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Moves a build forward by queuing build tasks, canceling or restarting the
|
|
|
|
* build, or failing it in response to task failures.
|
|
|
|
*/
|
|
|
|
final class HarbormasterBuildEngine extends Phobject {
|
|
|
|
|
|
|
|
private $build;
|
|
|
|
private $viewer;
|
|
|
|
private $newBuildTargets = array();
|
2014-04-18 01:01:16 +02:00
|
|
|
private $forceBuildableUpdate;
|
|
|
|
|
|
|
|
public function setForceBuildableUpdate($force_buildable_update) {
|
|
|
|
$this->forceBuildableUpdate = $force_buildable_update;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldForceBuildableUpdate() {
|
|
|
|
return $this->forceBuildableUpdate;
|
|
|
|
}
|
2014-01-06 21:32:10 +01:00
|
|
|
|
|
|
|
public function queueNewBuildTarget(HarbormasterBuildTarget $target) {
|
|
|
|
$this->newBuildTargets[] = $target;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getNewBuildTargets() {
|
|
|
|
return $this->newBuildTargets;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setViewer(PhabricatorUser $viewer) {
|
|
|
|
$this->viewer = $viewer;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getViewer() {
|
|
|
|
return $this->viewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setBuild(HarbormasterBuild $build) {
|
|
|
|
$this->build = $build;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getBuild() {
|
|
|
|
return $this->build;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function continueBuild() {
|
|
|
|
$build = $this->getBuild();
|
|
|
|
|
|
|
|
$lock_key = 'harbormaster.build:'.$build->getID();
|
|
|
|
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
|
|
|
|
|
|
|
|
$build->reload();
|
2014-04-18 01:01:16 +02:00
|
|
|
$old_status = $build->getBuildStatus();
|
2014-01-06 21:32:10 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
$this->updateBuild($build);
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
// If any exception is raised, the build is marked as a failure and the
|
|
|
|
// exception is re-thrown (this ensures we don't leave builds in an
|
|
|
|
// inconsistent state).
|
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_ERROR);
|
|
|
|
$build->save();
|
|
|
|
|
|
|
|
$lock->unlock();
|
|
|
|
throw $ex;
|
|
|
|
}
|
|
|
|
|
|
|
|
$lock->unlock();
|
|
|
|
|
|
|
|
// NOTE: We queue new targets after releasing the lock so that in-process
|
|
|
|
// execution via `bin/harbormaster` does not reenter the locked region.
|
|
|
|
foreach ($this->getNewBuildTargets() as $target) {
|
|
|
|
$task = PhabricatorWorker::scheduleTask(
|
|
|
|
'HarbormasterTargetWorker',
|
|
|
|
array(
|
|
|
|
'targetID' => $target->getID(),
|
|
|
|
));
|
|
|
|
}
|
2014-04-18 01:01:16 +02:00
|
|
|
|
|
|
|
// If the build changed status, we might need to update the overall status
|
|
|
|
// on the buildable.
|
|
|
|
$new_status = $build->getBuildStatus();
|
|
|
|
if ($new_status != $old_status || $this->shouldForceBuildableUpdate()) {
|
|
|
|
$this->updateBuildable($build->getBuildable());
|
|
|
|
}
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private function updateBuild(HarbormasterBuild $build) {
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) ||
|
2014-01-06 23:11:59 +01:00
|
|
|
($build->isRestarting())) {
|
2014-01-06 21:32:10 +01:00
|
|
|
$this->destroyBuildTargets($build);
|
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
|
|
|
|
$build->save();
|
|
|
|
}
|
|
|
|
|
2014-01-06 23:11:59 +01:00
|
|
|
if ($build->isResuming()) {
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
|
|
|
|
$build->save();
|
|
|
|
}
|
|
|
|
|
2014-01-06 23:11:59 +01:00
|
|
|
if ($build->isStopping() && !$build->isComplete()) {
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_STOPPED);
|
|
|
|
$build->save();
|
|
|
|
}
|
|
|
|
|
2014-01-06 23:11:59 +01:00
|
|
|
$build->deleteUnprocessedCommands();
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
|
2014-01-06 21:32:10 +01:00
|
|
|
if ($build->getBuildStatus() == HarbormasterBuild::STATUS_BUILDING) {
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
$this->updateBuildSteps($build);
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function destroyBuildTargets(HarbormasterBuild $build) {
|
|
|
|
$targets = id(new HarbormasterBuildTargetQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withBuildPHIDs(array($build->getPHID()))
|
|
|
|
->execute();
|
2014-01-29 05:17:03 +01:00
|
|
|
|
|
|
|
if (!$targets) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$target_phids = mpull($targets, 'getPHID');
|
|
|
|
|
|
|
|
$artifacts = id(new HarbormasterBuildArtifactQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withBuildTargetPHIDs($target_phids)
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
foreach ($artifacts as $artifact) {
|
|
|
|
$artifact->delete();
|
|
|
|
}
|
|
|
|
|
2014-01-06 21:32:10 +01:00
|
|
|
foreach ($targets as $target) {
|
|
|
|
$target->delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function updateBuildSteps(HarbormasterBuild $build) {
|
|
|
|
$targets = id(new HarbormasterBuildTargetQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withBuildPHIDs(array($build->getPHID()))
|
|
|
|
->execute();
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
|
|
|
|
$this->updateWaitingTargets($targets);
|
|
|
|
|
2014-01-06 21:32:10 +01:00
|
|
|
$targets = mgroup($targets, 'getBuildStepPHID');
|
|
|
|
|
|
|
|
$steps = id(new HarbormasterBuildStepQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
|
|
|
|
->execute();
|
|
|
|
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
// Identify steps which are in various states.
|
2014-01-06 21:32:10 +01:00
|
|
|
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
$queued = array();
|
|
|
|
$underway = array();
|
|
|
|
$waiting = array();
|
2014-01-06 21:32:10 +01:00
|
|
|
$complete = array();
|
|
|
|
$failed = array();
|
|
|
|
foreach ($steps as $step) {
|
|
|
|
$step_targets = idx($targets, $step->getPHID(), array());
|
|
|
|
|
|
|
|
if ($step_targets) {
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
$is_queued = false;
|
|
|
|
|
|
|
|
$is_underway = false;
|
|
|
|
foreach ($step_targets as $target) {
|
|
|
|
if ($target->isUnderway()) {
|
|
|
|
$is_underway = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$is_waiting = false;
|
|
|
|
foreach ($step_targets as $target) {
|
|
|
|
if ($target->isWaiting()) {
|
|
|
|
$is_waiting = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-06 21:32:10 +01:00
|
|
|
$is_complete = true;
|
|
|
|
foreach ($step_targets as $target) {
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
if (!$target->isComplete()) {
|
2014-01-06 21:32:10 +01:00
|
|
|
$is_complete = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$is_failed = false;
|
|
|
|
foreach ($step_targets as $target) {
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
if ($target->isFailed()) {
|
2014-01-06 21:32:10 +01:00
|
|
|
$is_failed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
$is_queued = true;
|
|
|
|
$is_underway = false;
|
|
|
|
$is_waiting = false;
|
2014-01-06 21:32:10 +01:00
|
|
|
$is_complete = false;
|
|
|
|
$is_failed = false;
|
|
|
|
}
|
|
|
|
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
if ($is_queued) {
|
|
|
|
$queued[$step->getPHID()] = true;
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|
|
|
|
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
if ($is_underway) {
|
|
|
|
$underway[$step->getPHID()] = true;
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($is_waiting) {
|
|
|
|
$waiting[$step->getPHID()] = true;
|
|
|
|
}
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
|
|
|
|
if ($is_complete) {
|
|
|
|
$complete[$step->getPHID()] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($is_failed) {
|
|
|
|
$failed[$step->getPHID()] = true;
|
|
|
|
}
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|
|
|
|
|
2014-01-13 21:21:49 +01:00
|
|
|
// If any step failed, fail the whole build, then bail.
|
|
|
|
if (count($failed)) {
|
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
|
2014-01-06 21:32:10 +01:00
|
|
|
$build->save();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-13 21:21:49 +01:00
|
|
|
// If every step is complete, we're done with this build. Mark it passed
|
|
|
|
// and bail.
|
|
|
|
if (count($complete) == count($steps)) {
|
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_PASSED);
|
2014-01-06 21:32:10 +01:00
|
|
|
$build->save();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Identify all the steps which are ready to run (because all their
|
2014-07-23 02:03:09 +02:00
|
|
|
// dependencies are complete).
|
2014-01-06 21:32:10 +01:00
|
|
|
|
|
|
|
$previous_step = null;
|
|
|
|
$runnable = array();
|
|
|
|
foreach ($steps as $step) {
|
|
|
|
// TODO: For now, we're hard coding sequential dependencies into build
|
|
|
|
// steps. In the future, we can be smart about this instead.
|
|
|
|
|
|
|
|
if ($previous_step) {
|
|
|
|
$dependencies = array($previous_step);
|
|
|
|
} else {
|
|
|
|
$dependencies = array();
|
|
|
|
}
|
|
|
|
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
if (isset($queued[$step->getPHID()])) {
|
2014-01-06 21:32:10 +01:00
|
|
|
$can_run = true;
|
|
|
|
foreach ($dependencies as $dependency) {
|
|
|
|
if (empty($complete[$dependency->getPHID()])) {
|
|
|
|
$can_run = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($can_run) {
|
|
|
|
$runnable[] = $step;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$previous_step = $step;
|
|
|
|
}
|
|
|
|
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
if (!$runnable && !$waiting && !$underway) {
|
2014-01-06 21:32:10 +01:00
|
|
|
// TODO: This means the build is deadlocked, probably? It should not
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
// normally be possible yet, but we should communicate it more clearly.
|
2014-01-06 21:32:10 +01:00
|
|
|
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
|
|
|
|
$build->save();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($runnable as $runnable_step) {
|
|
|
|
$target = HarbormasterBuildTarget::initializeNewBuildTarget(
|
|
|
|
$build,
|
Replace "Cancel Build" with "Stop", "Resume" and "Restart"
Summary:
Ref T1049. Currently you can cancel a build, but now that we're tracking a lot more state we can stop, resume, and restart builds.
When the user issues a command against a build, I'm writing it into an auxiliary queue (`HarbormasterBuildCommand`) and then reading them out in the worker. This is mostly to avoid race messes where we try to `save()` the object in multiple places: basically, the BuildEngine is the //only// thing that writes to Build objects, and it holds a lock while it does it.
Test Plan:
- Created a plan which runs "sleep 2" a bunch of times in a row.
- Stopped, resumed, and restarted it.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, chad
Maniphest Tasks: T1049
Differential Revision: https://secure.phabricator.com/D7892
2014-01-06 21:32:20 +01:00
|
|
|
$runnable_step,
|
2014-01-06 21:32:10 +01:00
|
|
|
$build->retrieveVariablesFromBuild());
|
|
|
|
$target->save();
|
|
|
|
|
|
|
|
$this->queueNewBuildTarget($target);
|
|
|
|
}
|
Allow Harbormaster build targets to wait for messages
Summary:
This hooks up all the pieces of the build pipeline so `harbormaster.sendmessage` actually works. Particularly:
- Candidate build steps (i.e., those which interact with external systems) can now "Wait for Message". This pauses them indefinitely when they complete, until something calls `harbormaster.sendmessage`.
- After processing a target, we check if we should move it to PASSED or WAITING.
- Before updating a build, we move WAITING targets with pending messages to either PASSED or FAILED.
- I added an explicit "Building" state, which doesn't affect workflows but communicates more information to human users.
A big part of this is avoiding races. I believe we get the correct behavior no matter which order events occur in:
- We update builds after targets complete and after we receive messages, so we're guaranteed to update once both these conditions are true. This means messages can't be lost (even if they arrive before a build completes).
- The minor changes to the build engine logic mean that firing additional build updates is always safe, no matter what the current state of the build is.
- The build itself is protected by a lock in the build engine.
- The target is not covered by an explicit lock, but for all states only the engine (waiting) //or// the worker (all other states) can interact with it. All of the interactions also move the target state forward to the same destination and have no other side effects.
- Messages are only consumed inside the engine lock, so they don't need an explicit lock.
Test Plan:
- Made an HTTP request wait after completion, then ran a pile of builds through it using `bin/harbormaster build` and the web UI.
- Passed and failed message-awaiting builds with `harbormaster.sendmessage`.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley, zeeg
Differential Revision: https://secure.phabricator.com/D8788
2014-04-16 22:01:46 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process messages which were sent to these targets, kicking applicable
|
|
|
|
* targets out of "Waiting" and into either "Passed" or "Failed".
|
|
|
|
*
|
|
|
|
* @param list<HarbormasterBuildTarget> List of targets to process.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function updateWaitingTargets(array $targets) {
|
|
|
|
assert_instances_of($targets, 'HarbormasterBuildTarget');
|
|
|
|
|
|
|
|
// We only care about messages for targets which are actually in a waiting
|
|
|
|
// state.
|
|
|
|
$waiting_targets = array();
|
|
|
|
foreach ($targets as $target) {
|
|
|
|
if ($target->isWaiting()) {
|
|
|
|
$waiting_targets[$target->getPHID()] = $target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$waiting_targets) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$messages = id(new HarbormasterBuildMessageQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withBuildTargetPHIDs(array_keys($waiting_targets))
|
|
|
|
->withConsumed(false)
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
foreach ($messages as $message) {
|
|
|
|
$target = $waiting_targets[$message->getBuildTargetPHID()];
|
|
|
|
|
|
|
|
$new_status = null;
|
|
|
|
switch ($message->getType()) {
|
|
|
|
case 'pass':
|
|
|
|
$new_status = HarbormasterBuildTarget::STATUS_PASSED;
|
|
|
|
break;
|
|
|
|
case 'fail':
|
|
|
|
$new_status = HarbormasterBuildTarget::STATUS_FAILED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($new_status !== null) {
|
|
|
|
$message->setIsConsumed(true);
|
|
|
|
$message->save();
|
|
|
|
|
|
|
|
$target->setTargetStatus($new_status);
|
|
|
|
$target->save();
|
|
|
|
}
|
|
|
|
}
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|
|
|
|
|
2014-04-18 01:01:16 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the overall status of the buildable this build is attached to.
|
|
|
|
*
|
|
|
|
* After a build changes state (for example, passes or fails) it may affect
|
|
|
|
* the overall state of the associated buildable. Compute the new aggregate
|
|
|
|
* state and save it on the buildable.
|
|
|
|
*
|
|
|
|
* @param HarbormasterBuild The buildable to update.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function updateBuildable(HarbormasterBuildable $buildable) {
|
2014-04-18 01:04:14 +02:00
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
2014-04-18 01:01:16 +02:00
|
|
|
$lock_key = 'harbormaster.buildable:'.$buildable->getID();
|
|
|
|
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
|
|
|
|
|
|
|
|
$buildable = id(new HarbormasterBuildableQuery())
|
2014-04-18 01:04:14 +02:00
|
|
|
->setViewer($viewer)
|
2014-04-18 01:01:16 +02:00
|
|
|
->withIDs(array($buildable->getID()))
|
|
|
|
->needBuilds(true)
|
|
|
|
->executeOne();
|
|
|
|
|
|
|
|
$all_pass = true;
|
|
|
|
$any_fail = false;
|
|
|
|
foreach ($buildable->getBuilds() as $build) {
|
|
|
|
if ($build->getBuildStatus() != HarbormasterBuild::STATUS_PASSED) {
|
|
|
|
$all_pass = false;
|
|
|
|
}
|
|
|
|
if ($build->getBuildStatus() == HarbormasterBuild::STATUS_FAILED ||
|
|
|
|
$build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR) {
|
|
|
|
$any_fail = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($any_fail) {
|
|
|
|
$new_status = HarbormasterBuildable::STATUS_FAILED;
|
|
|
|
} else if ($all_pass) {
|
|
|
|
$new_status = HarbormasterBuildable::STATUS_PASSED;
|
|
|
|
} else {
|
|
|
|
$new_status = HarbormasterBuildable::STATUS_BUILDING;
|
|
|
|
}
|
|
|
|
|
2014-04-18 01:04:14 +02:00
|
|
|
$old_status = $buildable->getBuildableStatus();
|
|
|
|
$did_update = ($old_status != $new_status);
|
|
|
|
if ($did_update) {
|
2014-04-18 01:01:16 +02:00
|
|
|
$buildable->setBuildableStatus($new_status);
|
|
|
|
$buildable->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
$lock->unlock();
|
2014-04-18 01:04:14 +02:00
|
|
|
|
|
|
|
// If we changed the buildable status, try to post a transaction to the
|
|
|
|
// object about it. We can safely do this outside of the locked region.
|
|
|
|
|
|
|
|
// NOTE: We only post transactions for automatic buildables, not for
|
|
|
|
// manual ones: manual builds are test builds, whoever is doing tests
|
|
|
|
// can look at the results themselves, and other users generally don't
|
|
|
|
// care about the outcome.
|
|
|
|
|
2014-05-15 16:02:30 +02:00
|
|
|
$should_publish = $did_update &&
|
|
|
|
$new_status != HarbormasterBuildable::STATUS_BUILDING &&
|
|
|
|
!$buildable->getIsManualBuildable();
|
|
|
|
if ($should_publish) {
|
2014-04-18 01:04:14 +02:00
|
|
|
$object = id(new PhabricatorObjectQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withPHIDs(array($buildable->getBuildablePHID()))
|
|
|
|
->executeOne();
|
|
|
|
|
|
|
|
if ($object instanceof PhabricatorApplicationTransactionInterface) {
|
|
|
|
$template = $object->getApplicationTransactionTemplate();
|
|
|
|
if ($template) {
|
|
|
|
$template
|
|
|
|
->setTransactionType(PhabricatorTransactions::TYPE_BUILDABLE)
|
|
|
|
->setMetadataValue(
|
|
|
|
'harbormaster:buildablePHID',
|
|
|
|
$buildable->getPHID())
|
|
|
|
->setOldValue($old_status)
|
|
|
|
->setNewValue($new_status);
|
|
|
|
|
2014-07-23 02:03:09 +02:00
|
|
|
$harbormaster_phid = id(new PhabricatorHarbormasterApplication())
|
2014-04-18 01:04:14 +02:00
|
|
|
->getPHID();
|
|
|
|
|
|
|
|
$daemon_source = PhabricatorContentSource::newForSource(
|
|
|
|
PhabricatorContentSource::SOURCE_DAEMON,
|
|
|
|
array());
|
|
|
|
|
|
|
|
$editor = $object->getApplicationTransactionEditor()
|
|
|
|
->setActor($viewer)
|
|
|
|
->setActingAsPHID($harbormaster_phid)
|
|
|
|
->setContentSource($daemon_source)
|
|
|
|
->setContinueOnNoEffect(true)
|
|
|
|
->setContinueOnMissingFields(true);
|
|
|
|
|
|
|
|
$editor->applyTransactions(
|
|
|
|
$object->getApplicationTransactionObject(),
|
|
|
|
array($template));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-18 01:01:16 +02:00
|
|
|
}
|
|
|
|
|
2014-01-06 21:32:10 +01:00
|
|
|
}
|