1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-04 20:52:43 +01:00
phorge-phorge/src/applications/harbormaster/engine/HarbormasterBuildEngine.php

501 lines
14 KiB
PHP
Raw Normal View History

<?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();
private $forceBuildableUpdate;
public function setForceBuildableUpdate($force_buildable_update) {
$this->forceBuildableUpdate = $force_buildable_update;
return $this;
}
public function shouldForceBuildableUpdate() {
return $this->forceBuildableUpdate;
}
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();
$old_status = $build->getBuildStatus();
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();
$this->releaseAllArtifacts($build);
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(),
Add `objectPHID` keys to Harbormaster task schedulers Summary: Fixes T9816. It's currently hard to hunt down some particulars in the worker queue if things go awry in Harbormaster. Supplement the queue with `objectPHID` keys so we can hunt tasks down more easily if the issues in T9816 continue. Test Plan: ``` mysql> select * from worker_archivetask order by id desc limit 30; +--------+------------------------------------------------+-----------------------------------+--------------+--------------+--------+--------+----------+-------------+--------------+----------+--------------------------------+ | id | taskClass | leaseOwner | leaseExpires | failureCount | dataID | result | duration | dateCreated | dateModified | priority | objectPHID | +--------+------------------------------------------------+-----------------------------------+--------------+--------------+--------+--------+----------+-------------+--------------+----------+--------------------------------+ | 496024 | HarbormasterTargetWorker | 8514:1448232248:Orbital.local:3 | 1448318648 | 0 | 311880 | 0 | 233758 | 1448232248 | 1448232248 | 2000 | PHID-HMBT-thq4oof4byllmbc4q3tt | | 496023 | PhabricatorApplicationTransactionPublishWorker | 8514:1448232247:Orbital.local:1 | 1448239447 | 0 | 311879 | 0 | 53731 | 1448232247 | 1448232247 | 1000 | PHID-HMBD-i6zo2ltc73rre7o54s7v | | 496022 | HarbormasterBuildWorker | 8514:1448232247:Orbital.local:2 | 1448239447 | 0 | 311878 | 0 | 30736 | 1448232248 | 1448232248 | 2000 | PHID-HMBD-i6zo2ltc73rre7o54s7v | ... ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T9816 Differential Revision: https://secure.phabricator.com/D14541
2015-11-22 23:17:04 +01:00
),
array(
'objectPHID' => $target->getPHID(),
));
}
// 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());
}
// If we are no longer building for any reason, release all artifacts.
if (!$build->isBuilding()) {
$this->releaseAllArtifacts($build);
}
}
private function updateBuild(HarbormasterBuild $build) {
if ($build->isAborting()) {
$this->releaseAllArtifacts($build);
$build->setBuildStatus(HarbormasterBuild::STATUS_ABORTED);
$build->save();
}
if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) ||
($build->isRestarting())) {
$this->restartBuild($build);
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
$build->save();
}
if ($build->isResuming()) {
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
$build->save();
}
if ($build->isPausing() && !$build->isComplete()) {
$build->setBuildStatus(HarbormasterBuild::STATUS_PAUSED);
$build->save();
}
$build->deleteUnprocessedCommands();
if ($build->getBuildStatus() == HarbormasterBuild::STATUS_BUILDING) {
$this->updateBuildSteps($build);
}
}
private function restartBuild(HarbormasterBuild $build) {
// We're restarting the build, so release all previous artifacts.
$this->releaseAllArtifacts($build);
// Increment the build generation counter on the build.
$build->setBuildGeneration($build->getBuildGeneration() + 1);
// Currently running targets should periodically check their build
// generation (which won't have changed) against the build's generation.
// If it is different, they will automatically stop what they're doing
// and abort.
// Previously we used to delete targets, logs and artifacts here. Instead
// leave them around so users can view previous generations of this build.
}
private function updateBuildSteps(HarbormasterBuild $build) {
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->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);
$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.
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();
$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;
}
}
$is_complete = true;
foreach ($step_targets as $target) {
if (!$target->isComplete()) {
$is_complete = false;
break;
}
}
$is_failed = false;
foreach ($step_targets as $target) {
if ($target->isFailed()) {
$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;
$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;
}
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;
}
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;
}
}
// If any step failed, fail the whole build, then bail.
if (count($failed)) {
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
$build->save();
return;
}
// 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);
$build->save();
return;
}
// Identify all the steps which are ready to run (because all their
// dependencies are complete).
$runnable = array();
foreach ($steps as $step) {
$dependencies = $step->getStepImplementation()->getDependencies($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 (isset($queued[$step->getPHID()])) {
$can_run = true;
foreach ($dependencies as $dependency) {
if (empty($complete[$dependency])) {
$can_run = false;
break;
}
}
if ($can_run) {
$runnable[] = $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) {
// This means the build is deadlocked, and the user has configured
// circular dependencies.
$build->setBuildStatus(HarbormasterBuild::STATUS_DEADLOCKED);
$build->save();
return;
}
foreach ($runnable as $runnable_step) {
$target = HarbormasterBuildTarget::initializeNewBuildTarget(
$build,
$runnable_step,
$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()];
switch ($message->getType()) {
case HarbormasterMessageType::MESSAGE_PASS:
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
$new_status = HarbormasterBuildTarget::STATUS_PASSED;
break;
case HarbormasterMessageType::MESSAGE_FAIL:
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
$new_status = HarbormasterBuildTarget::STATUS_FAILED;
break;
case HarbormasterMessageType::MESSAGE_WORK:
default:
$new_status = null;
break;
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 ($new_status !== null) {
$message->setIsConsumed(true);
$message->save();
$target->setTargetStatus($new_status);
if ($target->isComplete()) {
$target->setDateCompleted(PhabricatorTime::getNow());
}
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
$target->save();
}
}
}
/**
* 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) {
$viewer = $this->getViewer();
$lock_key = 'harbormaster.buildable:'.$buildable->getID();
$lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15);
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->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 ||
$build->getBuildStatus() == HarbormasterBuild::STATUS_DEADLOCKED) {
$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;
}
$old_status = $buildable->getBuildableStatus();
$did_update = ($old_status != $new_status);
if ($did_update) {
$buildable->setBuildableStatus($new_status);
$buildable->save();
}
$lock->unlock();
// 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.
$should_publish = $did_update &&
$new_status != HarbormasterBuildable::STATUS_BUILDING &&
!$buildable->getIsManualBuildable();
if (!$should_publish) {
return;
}
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($buildable->getBuildablePHID()))
->executeOne();
if (!$object) {
return;
}
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
return;
}
// TODO: Publishing these transactions is causing a race. See T8650.
// We shouldn't be publishing to diffs anyway.
if ($object instanceof DifferentialDiff) {
return;
}
$template = $object->getApplicationTransactionTemplate();
if (!$template) {
return;
}
$template
->setTransactionType(PhabricatorTransactions::TYPE_BUILDABLE)
->setMetadataValue(
'harbormaster:buildablePHID',
$buildable->getPHID())
->setOldValue($old_status)
->setNewValue($new_status);
$harbormaster_phid = id(new PhabricatorHarbormasterApplication())
->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));
}
private function releaseAllArtifacts(HarbormasterBuild $build) {
$targets = id(new HarbormasterBuildTargetQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->execute();
if (count($targets) === 0) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildTargetPHIDs($target_phids)
->execute();
foreach ($artifacts as $artifact) {
Add `harbormaster.createartifact` Summary: Ref T8659. In the general case, this eventually allows build processes to do things like: - Upload build results (like a ".app" or ".exe" or other binary). - Pass complex results between build steps (e.g., build step A does something hard and build step B uses it to do something else). Today, we're a long way away from having the infrastructure for that. However, it is useful to let third party build processes (like Jenkins) upload URIs that link back to the external build results. This adds `harbormaster.createartifact` so they can do that. The only useful thing to do with this method today is have your Jenkins build do this: params = array( "uri": "https://jenkins.mycompany.com/build/23923/details/", "name": "View Build Results in Jenkins", "ui.external": true, ); harbormaster.createartifact(target, 'uri', params); Then (after the next diff) we'll show a link in Differential and a prominent link in Harbormaster. I didn't actually do the UI stuff in this diff since it's already pretty big. This change moves a lot of code around, too: - Adds PHIDs to artifacts. - It modularizes build artifact types (currently "file", "host" and "URI"). - It formalizes build artifact parameters and construction: - This lets me generate usable documentation about how to create artifacts. - This prevents users from doing dangerous or policy-violating things. - It does some other general modernization. Test Plan: {F715633} {F715634} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8659 Differential Revision: https://secure.phabricator.com/D13900
2015-08-15 16:28:56 +02:00
$artifact->releaseArtifact();
}
}
}