From ca5400d14bcba4ed067fb12354b64364f224283a Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Tue, 5 Nov 2013 12:43:47 -0800 Subject: [PATCH] Implement basic Harbormaster daemon and start builds. Summary: This implements a basic Harbormaster daemon that takes pending builds and builds them (currently just sleeps 15 seconds before moving to passed state). It also implements an interface to apply a build plan to a buildable, so that users can kick off builds for a buildable. Test Plan: Ran `bin/phd debug PhabricatorHarbormasterBuildDaemon` and used the interface to start some builds by applying a build plan. Observed them move from 'pending' to 'building' to 'passed'. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T1049 Differential Revision: https://secure.phabricator.com/D7498 --- src/__phutil_library_map__.php | 8 +- .../PhabricatorApplicationHarbormaster.php | 2 +- .../HarbormasterBuildableApplyController.php | 81 +++++++++++++++++ .../HarbormasterBuildableListController.php | 2 +- .../HarbormasterBuildableViewController.php | 35 ++++++++ .../HarbormasterPlanExecuteController.php | 88 ------------------- .../HarbormasterPlanViewController.php | 8 -- .../query/HarbormasterBuildQuery.php | 13 +++ .../storage/HarbormasterBuildable.php | 6 +- .../storage/build/HarbormasterBuild.php | 32 ++++++- .../worker/HarbormasterBuildWorker.php | 51 +++++++++++ .../worker/HarbormasterRunnerWorker.php | 53 ----------- 12 files changed, 221 insertions(+), 158 deletions(-) create mode 100644 src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php delete mode 100644 src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php create mode 100644 src/applications/harbormaster/worker/HarbormasterBuildWorker.php delete mode 100644 src/applications/harbormaster/worker/HarbormasterRunnerWorker.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9f8ad08066..8882859909 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -656,7 +656,9 @@ phutil_register_library_map(array( 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', + 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', + 'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php', 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php', 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', @@ -675,11 +677,9 @@ phutil_register_library_map(array( 'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php', 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', - 'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php', 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', - 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 'HeraldAction' => 'applications/herald/storage/HeraldAction.php', 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', @@ -2868,11 +2868,13 @@ phutil_register_library_map(array( 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildTarget' => 'HarbormasterDAO', 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildWorker' => 'PhabricatorWorker', 'HarbormasterBuildable' => array( 0 => 'HarbormasterDAO', 1 => 'PhabricatorPolicyInterface', ), + 'HarbormasterBuildableApplyController' => 'HarbormasterController', 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildableEditController' => 'HarbormasterController', 'HarbormasterBuildableListController' => @@ -2895,7 +2897,6 @@ phutil_register_library_map(array( 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', 'HarbormasterPlanController' => 'PhabricatorController', 'HarbormasterPlanEditController' => 'HarbormasterPlanController', - 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController', 'HarbormasterPlanListController' => array( 0 => 'HarbormasterPlanController', @@ -2903,7 +2904,6 @@ phutil_register_library_map(array( ), 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', - 'HarbormasterRunnerWorker' => 'PhabricatorWorker', 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HeraldAction' => 'HeraldDAO', 'HeraldApplyTranscript' => 'HeraldDAO', diff --git a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php index 4e41dc7a02..6bcb87d7b1 100644 --- a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php +++ b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -44,13 +44,13 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication { => 'HarbormasterBuildableListController', 'buildable/' => array( 'edit/(?:(?P\d+)/)?' => 'HarbormasterBuildableEditController', + 'apply/(?:(?P\d+)/)?' => 'HarbormasterBuildableApplyController', ), 'plan/' => array( '(?:query/(?P[^/]+)/)?' => 'HarbormasterPlanListController', 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', '(?P\d+)/' => 'HarbormasterPlanViewController', - 'execute/(?P\d+)/' => 'HarbormasterPlanExecuteController', ), ), ); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php b/src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php new file mode 100644 index 0000000000..215e117953 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php @@ -0,0 +1,81 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $this->id; + + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if ($buildable === null) { + throw new Exception("Buildable not found!"); + } + + $buildable_uri = '/B'.$buildable->getID(); + + if ($request->isDialogFormPost()) { + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getInt('build-plan'))) + ->executeOne(); + + $build = HarbormasterBuild::initializeNewBuild($viewer); + $build->setBuildablePHID($buildable->getPHID()); + $build->setBuildPlanPHID($plan->getPHID()); + $build->setBuildStatus(HarbormasterBuild::STATUS_PENDING); + $build->save(); + + PhabricatorWorker::scheduleTask( + 'HarbormasterBuildWorker', + array( + 'buildID' => $build->getID() + )); + + return id(new AphrontRedirectResponse())->setURI($buildable_uri); + } + + $plans = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->execute(); + + $options = array(); + foreach ($plans as $plan) { + $options[$plan->getID()] = $plan->getName(); + } + + // FIXME: I'd really like to use the dialog that "Edit Differential + // Revisions" uses, but that code is quite hard-coded for the particular + // uses, so for now we just give a single dropdown. + + $dialog = new AphrontDialogView(); + $dialog->setTitle(pht('Apply which plan?')) + ->setUser($viewer) + ->addSubmitButton(pht('Apply')) + ->addCancelButton($buildable_uri); + $dialog->appendChild( + phutil_tag( + 'p', + array(), + pht( + 'Select what build plan you want to apply to this buildable:'))); + $dialog->appendChild( + id(new AphrontFormSelectControl()) + ->setUser($viewer) + ->setName('build-plan') + ->setOptions($options)); + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php index 4260410474..9bf9fceef0 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -36,7 +36,7 @@ final class HarbormasterBuildableListController $id = $buildable->getID(); $item = id(new PHUIObjectItemView()) - ->setHeader(pht('Build %d', $buildable->getID())); + ->setHeader(pht('Buildable %d', $buildable->getID())); if ($id) { $item->setHref("/B{$id}"); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 7ee92981c9..afe43b1423 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -37,6 +37,32 @@ final class HarbormasterBuildableViewController $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Build %d', $build->getID())) ->setHeader($build->getName()); + switch ($build->getBuildStatus()) { + case HarbormasterBuild::STATUS_INACTIVE: + $item->setBarColor('grey'); + $item->addAttribute(pht('Inactive')); + break; + case HarbormasterBuild::STATUS_PENDING: + $item->setBarColor('blue'); + $item->addAttribute(pht('Pending')); + break; + case HarbormasterBuild::STATUS_WAITING: + $item->setBarColor('blue'); + $item->addAttribute(pht('Waiting on Resource')); + break; + case HarbormasterBuild::STATUS_BUILDING: + $item->setBarColor('yellow'); + $item->addAttribute(pht('Building')); + break; + case HarbormasterBuild::STATUS_PASSED: + $item->setBarColor('green'); + $item->addAttribute(pht('Passed')); + break; + case HarbormasterBuild::STATUS_FAILED: + $item->setBarColor('red'); + $item->addAttribute(pht('Failed')); + break; + } $build_list->addItem($item); } @@ -80,6 +106,15 @@ final class HarbormasterBuildableViewController ->setObject($buildable) ->setObjectURI("/B{$id}"); + $apply_uri = $this->getApplicationURI('/buildable/apply/'.$id.'/'); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Apply Build Plan')) + ->setIcon('edit') + ->setHref($apply_uri) + ->setWorkflow(true)); + return $list; } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php b/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php deleted file mode 100644 index c720e0b797..0000000000 --- a/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php +++ /dev/null @@ -1,88 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $this->requireApplicationCapability( - HarbormasterCapabilityManagePlans::CAPABILITY); - - $id = $this->id; - - $plan = id(new HarbormasterBuildPlanQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$plan) { - return new Aphront404Response(); - } - - $cancel_uri = $this->getApplicationURI("plan/{$id}/"); - - $v_buildable = null; - $e_buildable = null; - - $errors = array(); - if ($request->isFormPost()) { - $v_buildable = $request->getStr('buildable'); - - if ($v_buildable) { - $buildable = id(new HarbormasterBuildableQuery()) - ->setViewer($viewer) - ->withIDs(array(trim($v_buildable, 'B'))) - ->executeOne(); - if (!$buildable) { - $e_buildable = pht('Invalid'); - } - } else { - $e_buildable = pht('Required'); - $errors[] = pht('You must provide a buildable.'); - } - - if (!$errors) { - $build_plan = HarbormasterBuild::initializeNewBuild($viewer) - ->setBuildablePHID($buildable->getPHID()) - ->setBuildPlanPHID($plan->getPHID()) - ->save(); - - $buildable_id = $buildable->getID(); - - return id(new AphrontRedirectResponse()) - ->setURI("/B{$buildable_id}"); - } - } - - if ($errors) { - $errors = id(new AphrontErrorView())->setErrors($errors); - } - - $form = id(new PHUIFormLayoutView()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Buildable')) - ->setName('buildable') - ->setValue($v_buildable) - ->setError($e_buildable)); - - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Execute Build Plan')) - ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild($errors) - ->appendChild($form) - ->addSubmitButton(pht('Execute Build Plan')) - ->addCancelButton($cancel_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php index cc604e4e9a..58eefbe4ab 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -88,14 +88,6 @@ final class HarbormasterPlanViewController ->setDisabled(!$can_edit) ->setIcon('edit')); - $list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Manually Execute Plan')) - ->setHref($this->getApplicationURI("plan/execute/{$id}/")) - ->setWorkflow(true) - ->setDisabled(!$can_edit) - ->setIcon('arrow_right')); - return $list; } diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php index bd93a08ea6..cc4fe92bcf 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php @@ -5,6 +5,7 @@ final class HarbormasterBuildQuery private $ids; private $phids; + private $buildStatuses; private $buildablePHIDs; private $buildPlanPHIDs; @@ -20,6 +21,11 @@ final class HarbormasterBuildQuery return $this; } + public function withBuildStatuses(array $build_statuses) { + $this->buildStatuses = $build_statuses; + return $this; + } + public function withBuildablePHIDs(array $buildable_phids) { $this->buildablePHIDs = $buildable_phids; return $this; @@ -115,6 +121,13 @@ final class HarbormasterBuildQuery $this->phids); } + if ($this->buildStatuses) { + $where[] = qsprintf( + $conn_r, + 'buildStatus in (%Ls)', + $this->buildStatuses); + } + if ($this->buildablePHIDs) { $where[] = qsprintf( $conn_r, diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index d3100584bd..d3a95edb44 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -12,10 +12,12 @@ final class HarbormasterBuildable extends HarbormasterDAO private $containerObject = self::ATTACHABLE; private $buildableHandle = self::ATTACHABLE; + const STATUS_WHATEVER = 'whatever'; + public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) - ->setBuildStatus('new') // TODO: Define these. - ->setBuildableStatus('active'); // TODO: Define these, too. + ->setBuildStatus(self::STATUS_WHATEVER) + ->setBuildableStatus(self::STATUS_WHATEVER); } public function getConfiguration() { diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 325f30931d..c7d5aa7181 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -10,9 +10,39 @@ final class HarbormasterBuild extends HarbormasterDAO private $buildable = self::ATTACHABLE; private $buildPlan = self::ATTACHABLE; + /** + * Not currently being built. + */ + const STATUS_INACTIVE = 'inactive'; + + /** + * Pending pick up by the Harbormaster daemon. + */ + const STATUS_PENDING = 'pending'; + + /** + * Waiting for a resource to be allocated (not yet relevant). + */ + const STATUS_WAITING = 'waiting'; + + /** + * Current building the buildable. + */ + const STATUS_BUILDING = 'building'; + + /** + * The build has passed. + */ + const STATUS_PASSED = 'passed'; + + /** + * The build has failed. + */ + const STATUS_FAILED = 'failed'; + public static function initializeNewBuild(PhabricatorUser $actor) { return id(new HarbormasterBuild()) - ->setBuildStatus('building'); // TODO: Sort this. + ->setBuildStatus(self::STATUS_INACTIVE); } public function getConfiguration() { diff --git a/src/applications/harbormaster/worker/HarbormasterBuildWorker.php b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php new file mode 100644 index 0000000000..a9d8ed79e2 --- /dev/null +++ b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php @@ -0,0 +1,51 @@ +getTaskData(); + $id = idx($data, 'buildID'); + + // Get a reference to the build. + $build = id(new HarbormasterBuildQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withBuildStatuses(array(HarbormasterBuild::STATUS_PENDING)) + ->withIDs(array($id)) + ->needBuildPlans(true) + ->executeOne(); + if (!$build) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Invalid build ID "%s".', $id)); + } + + try { + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); + $build->save(); + + $buildable = $build->getBuildable(); + $plan = $build->getBuildPlan(); + + // TODO: Do the actual build here. + sleep(15); + + // If we get to here, then the build has passed. + $build->setBuildStatus(HarbormasterBuild::STATUS_PASSED); + $build->save(); + } catch (Exception $e) { + // 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_FAILED); + $build->save(); + throw $e; + } + } + +} diff --git a/src/applications/harbormaster/worker/HarbormasterRunnerWorker.php b/src/applications/harbormaster/worker/HarbormasterRunnerWorker.php deleted file mode 100644 index 088f072bf2..0000000000 --- a/src/applications/harbormaster/worker/HarbormasterRunnerWorker.php +++ /dev/null @@ -1,53 +0,0 @@ -getTaskData(); - $id = idx($data, 'commitID'); - - $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( - 'id = %d', - $id); - - if (!$commit) { - throw new PhabricatorWorkerPermanentFailureException( - "Commit '{$id}' does not exist!"); - } - - // TODO: (T603) Policy interaction? - $repository = id(new PhabricatorRepository())->loadOneWhere( - 'id = %d', - $commit->getRepositoryID()); - - if (!$repository) { - throw new PhabricatorWorkerPermanentFailureException( - "Unable to load repository for commit '{$id}'!"); - } - - $lease = id(new DrydockLease()) - ->setResourceType('working-copy') - ->setAttributes( - array( - 'repositoryID' => $repository->getID(), - 'commit' => $commit->getCommitIdentifier(), - )) - ->releaseOnDestruction() - ->waitUntilActive(); - - $cmd = $lease->getInterface('command'); - list($json) = $cmd - ->setWorkingDirectory($lease->getResource()->getAttribute('path')) - ->execx('arc unit --everything --json'); - $lease->release(); - - // TODO: Do something actually useful with this. Requires Harbormaster - // buildout. - echo $json; - } - -}