From 666f19e504916e8df6fb9949d660b86180a99132 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 19 Sep 2015 11:29:01 -0700 Subject: [PATCH 01/43] Make icon setting in Section Headers easier/consistent Summary: You can already pass other icons, but this makes it a bit simpler. Test Plan: Test Maniphest, Badges Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14131 --- .../badges/controller/PhabricatorBadgesViewController.php | 3 ++- .../controller/PhabricatorConduitConsoleController.php | 3 ++- .../diffusion/controller/DiffusionBrowseController.php | 3 ++- .../diffusion/controller/DiffusionRepositoryController.php | 3 ++- .../controller/DiffusionRepositoryEditMainController.php | 3 ++- .../drydock/controller/DrydockLeaseViewController.php | 3 ++- .../drydock/controller/DrydockResourceViewController.php | 3 ++- .../fund/controller/FundInitiativeViewController.php | 6 ++++-- .../controller/HarbormasterBuildViewController.php | 3 ++- .../controller/LegalpadDocumentManageController.php | 3 ++- .../PhabricatorApplicationDetailViewController.php | 6 +++--- .../controller/PhabricatorMetaMTAMailViewController.php | 3 ++- .../nuance/source/NuancePhabricatorFormSourceDefinition.php | 3 ++- .../owners/controller/PhabricatorOwnersDetailController.php | 3 ++- .../phortune/controller/PhortuneMerchantViewController.php | 3 ++- src/view/phui/PHUIPropertyListView.php | 6 +++--- 16 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php index 387b5d1f98..abaafe6d0f 100644 --- a/src/applications/badges/controller/PhabricatorBadgesViewController.php +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -109,7 +109,8 @@ final class PhabricatorBadgesViewController 'default', $viewer); - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); } diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php index 04d2c6ee6d..79312b5480 100644 --- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php @@ -201,7 +201,8 @@ final class PhabricatorConduitConsoleController id(new PhabricatorMarkupOneOff())->setContent($description), 'default', $viewer); - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); return $view; diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index e8bd46b735..c924a9b0b3 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -156,7 +156,8 @@ abstract class DiffusionBrowseController extends DiffusionController { $tag = idx($tags, $symbolic); if ($tag && strlen($tag->getMessage())) { - $view->addSectionHeader(pht('Tag Content')); + $view->addSectionHeader( + pht('Tag Content'), 'fa-tag'); $view->addTextContent($this->markupText($tag->getMessage())); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 5b63f16d71..cfa4f8f0d6 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -289,7 +289,8 @@ final class DiffusionRepositoryController extends DiffusionController { $repository, 'description', $user); - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index cd387537b0..6519a4380e 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -294,7 +294,8 @@ final class DiffusionRepositoryEditMainController $this->buildRepositoryUpdateInterval($repository)); $description = $repository->getDetail('description'); - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); if (!strlen($description)) { $description = phutil_tag('em', array(), pht('No description provided.')); } else { diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 92d215bcbb..562e466311 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -121,7 +121,8 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $attributes = $lease->getAttributes(); if ($attributes) { - $view->addSectionHeader(pht('Attributes')); + $view->addSectionHeader( + pht('Attributes'), 'fa-list-ul'); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 4cc7349dac..afa0fb49fd 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -117,7 +117,8 @@ final class DrydockResourceViewController extends DrydockResourceController { $attributes = $resource->getAttributes(); if ($attributes) { - $view->addSectionHeader(pht('Attributes')); + $view->addSectionHeader( + pht('Attributes'), 'fa-list-ul'); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 3d12f1549c..71cebc842d 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -103,7 +103,8 @@ final class FundInitiativeViewController 'default', $viewer); - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); } @@ -114,7 +115,8 @@ final class FundInitiativeViewController 'default', $viewer); - $view->addSectionHeader(pht('Risks/Challenges')); + $view->addSectionHeader( + pht('Risks/Challenges'), 'fa-ambulance'); $view->addTextContent($risks); } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 1a9832c588..b6f4473c5a 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -186,7 +186,8 @@ final class HarbormasterBuildViewController 'default', $viewer); - $properties->addSectionHeader(pht('Description')); + $properties->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent($rendered); } } else { diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php index 9fc773258f..1ca838258b 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php @@ -84,7 +84,8 @@ final class LegalpadDocumentManageController extends LegalpadController { $view = new PHUIPropertyListView(); $view->addClass('legalpad'); - $view->addSectionHeader(pht('Document')); + $view->addSectionHeader( + pht('Document'), 'fa-file-text-o'); $view->addTextContent( $engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT)); diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index da23cc31ff..702d0b2130 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -106,8 +106,7 @@ final class PhabricatorApplicationDetailViewController $overview = $application->getOverview(); if ($overview) { $properties->addSectionHeader( - pht('Overview'), - PHUIPropertyListView::ICON_SUMMARY); + pht('Overview'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent( PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($overview), @@ -119,7 +118,8 @@ final class PhabricatorApplicationDetailViewController $viewer, $application); - $properties->addSectionHeader(pht('Policies')); + $properties->addSectionHeader( + pht('Policies'), 'fa-lock'); foreach ($application->getCapabilities() as $capability) { $properties->addProperty( diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php index 7cb1ad71d6..ec43312e23 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php @@ -218,7 +218,8 @@ final class PhabricatorMetaMTAMailViewController 'Delivery reasons are listed from weakest to strongest.'))); } - $properties->addSectionHeader(pht('Routing Rules')); + $properties->addSectionHeader( + pht('Routing Rules'), 'fa-paper-plane-o'); $map = $mail->getDeliveredRoutingMap(); $routing_detail = null; diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index 607b0eda6c..675b2634a9 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -125,7 +125,8 @@ final class NuancePhabricatorFormSourceDefinition 'default', $viewer); - $view->addSectionHeader(pht('Complaint')); + $view->addSectionHeader( + pht('Complaint'), 'fa-exclamation-circle'); $view->addTextContent($complaint); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 7edcf010b6..bb178cfbcb 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -189,7 +189,8 @@ final class PhabricatorOwnersDetailController $description = $package->getDescription(); if (strlen($description)) { - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent( $output = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($description), diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index 4b515c44c5..4bedbd9e9c 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -141,7 +141,8 @@ final class PhortuneMerchantViewController 'default', $viewer); - $view->addSectionHeader(pht('Description')); + $view->addSectionHeader( + pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); } diff --git a/src/view/phui/PHUIPropertyListView.php b/src/view/phui/PHUIPropertyListView.php index 96c249d866..8a44ecd941 100644 --- a/src/view/phui/PHUIPropertyListView.php +++ b/src/view/phui/PHUIPropertyListView.php @@ -10,8 +10,8 @@ final class PHUIPropertyListView extends AphrontView { private $classes = array(); private $stacked; - const ICON_SUMMARY = 'fa-align-left bluegrey'; - const ICON_TESTPLAN = 'fa-file-text-o bluegrey'; + const ICON_SUMMARY = 'fa-align-left'; + const ICON_TESTPLAN = 'fa-file-text-o'; protected function canAppendChild() { return false; @@ -247,7 +247,7 @@ final class PHUIPropertyListView extends AphrontView { $name = $part['name']; if ($part['icon']) { $icon = id(new PHUIIconView()) - ->setIconFont($part['icon']); + ->setIconFont($part['icon'].' bluegrey'); $name = phutil_tag( 'span', array( From 9ea6249a188dc395bf9ce9b5d3825a84faad876a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 19 Sep 2015 19:42:19 -0700 Subject: [PATCH 02/43] Shrink aphront table headers Summary: These already have a larger font, the extra height isn't needed. Make them the same size as `td` Test Plan: Look at a bunch of tables. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14135 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/aphront/table-view.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f1a20bf002..064a961080 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '5eabac59', + 'core.pkg.css' => '521656c5', 'core.pkg.js' => '47dc9ebb', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '9c279160', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'a24cb589', - 'rsrc/css/aphront/table-view.css' => '34ee903e', + 'rsrc/css/aphront/table-view.css' => '63985f5b', 'rsrc/css/aphront/tokenizer.css' => '04875312', 'rsrc/css/aphront/tooltip.css' => '7672b60f', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', @@ -492,7 +492,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '34ee903e', + 'aphront-table-view-css' => '63985f5b', 'aphront-tokenizer-control-css' => '04875312', 'aphront-tooltip-css' => '7672b60f', 'aphront-typeahead-control-css' => '0e403212', diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 8b31b33b8d..e0e06317c8 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -102,7 +102,7 @@ th.aphront-table-view-sortable-selected { */ .aphront-table-view th { - padding: 10px 10px; + padding: 8px 10px; font-size: {$normalfontsize}; } From a0ed843d472ac38a702cfd60ad0f8f7d6e0ad5e2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 20 Sep 2015 04:28:33 -0700 Subject: [PATCH 03/43] Don't allow welcome mail to be sent to users who can't login Summary: Fixes T9446. We allow administrators to send "Welcome" mail to bots and mailing lists. This is harmless (these links do not function), but confusing. Instead, disable this option in the UI and explain why it is disabled when it is clicked. Also prevent generation of this mail lower in the stack. Test Plan: - Viewed a bot page, saw action disabled, clicked it, got explanation. - Viewed a normal user page, saw action enabled, clicked it, sent welcome email. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9446 Differential Revision: https://secure.phabricator.com/D14134 --- .../PhabricatorPeopleProfileController.php | 3 +++ .../PhabricatorPeopleWelcomeController.php | 25 +++++++++++-------- .../people/storage/PhabricatorUser.php | 7 ++++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php index dbdaf7e8b9..30af5057dd 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php @@ -136,11 +136,14 @@ final class PhabricatorPeopleProfileController ->setWorkflow(true) ->setHref($this->getApplicationURI('delete/'.$user->getID().'/'))); + $can_welcome = $user->canEstablishWebSessions(); + $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-envelope') ->setName(pht('Send Welcome Email')) ->setWorkflow(true) + ->setDisabled(!$can_welcome) ->setHref($this->getApplicationURI('welcome/'.$user->getID().'/'))); } diff --git a/src/applications/people/controller/PhabricatorPeopleWelcomeController.php b/src/applications/people/controller/PhabricatorPeopleWelcomeController.php index b3762e67c9..14b1544b7f 100644 --- a/src/applications/people/controller/PhabricatorPeopleWelcomeController.php +++ b/src/applications/people/controller/PhabricatorPeopleWelcomeController.php @@ -3,19 +3,12 @@ final class PhabricatorPeopleWelcomeController extends PhabricatorPeopleController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $admin = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $admin = $this->getViewer(); $user = id(new PhabricatorPeopleQuery()) ->setViewer($admin) - ->withIDs(array($this->id)) + ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$user) { return new Aphront404Response(); @@ -23,6 +16,18 @@ final class PhabricatorPeopleWelcomeController $profile_uri = '/p/'.$user->getUsername().'/'; + if (!$user->canEstablishWebSessions()) { + return $this->newDialog() + ->setTitle(pht('Not a Normal User')) + ->appendParagraph( + pht( + 'You can not send this user a welcome mail because they are not '. + 'a normal user and can not log in to the web interface. Special '. + 'users (like bots and mailing lists) are unable to establish web '. + 'sessions.')) + ->addCancelButton($profile_uri, pht('Done')); + } + if ($request->isFormPost()) { $user->sendWelcomeEmail($admin); return id(new AphrontRedirectResponse())->setURI($profile_uri); diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 82d17983e8..77d1d41603 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -587,6 +587,13 @@ final class PhabricatorUser } public function sendWelcomeEmail(PhabricatorUser $admin) { + if (!$this->canEstablishWebSessions()) { + throw new Exception( + pht( + 'Can not send welcome mail to users who can not establish '. + 'web sessions!')); + } + $admin_username = $admin->getUserName(); $admin_realname = $admin->getRealName(); $user_username = $this->getUserName(); From d6514321b161539d7c22b3fc00c05d61b1d93f00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:41:23 -0700 Subject: [PATCH 04/43] Add an Almanac service type for Drydock to lease against Summary: Ref T9253. See D13843 for some discussion. This is very bare-bones for now since I believe that almost all interesting configuration (e.g., credentials) should live in Drydock, although I imagine it getting some configuration eventually. Test Plan: Used {nav Almanac > Services > Create Service} to create a new service of this type. Reviewers: hach-que, chad Reviewed By: hach-que, chad Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14109 --- src/__phutil_library_map__.php | 2 ++ .../AlmanacDrydockPoolServiceType.php | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6fba6e6dcc..dfa24ba51a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -44,6 +44,7 @@ phutil_register_library_map(array( 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', + 'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php', 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', @@ -3666,6 +3667,7 @@ phutil_register_library_map(array( 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceViewController' => 'AlmanacDeviceController', + 'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType', 'AlmanacInterface' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php b/src/applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php new file mode 100644 index 0000000000..24880565d0 --- /dev/null +++ b/src/applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php @@ -0,0 +1,18 @@ + Date: Mon, 21 Sep 2015 04:41:40 -0700 Subject: [PATCH 05/43] Remove DrydockPreallocatedHostBlueprintImplementation Summary: Ref T9253. This comes from a time before Almanac. Now that we have Almanac, it makes much more sense to put this logic there than to try to put it in Drydock itself. Remove the preallocated host blueprint, a relic of a bygone time. Test Plan: Grepped for callsites. Reviewers: hach-que, chad Reviewed By: hach-que, chad Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14110 --- src/__phutil_library_map__.php | 2 - ...reallocatedHostBlueprintImplementation.php | 116 ------------------ 2 files changed, 118 deletions(-) delete mode 100644 src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index dfa24ba51a..cd5c6c5b65 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -849,7 +849,6 @@ phutil_register_library_map(array( 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', - 'DrydockPreallocatedHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php', @@ -4567,7 +4566,6 @@ phutil_register_library_map(array( 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', - 'DrydockPreallocatedHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DrydockResource' => array( 'DrydockDAO', diff --git a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php deleted file mode 100644 index 9e5d3a2320..0000000000 --- a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php +++ /dev/null @@ -1,116 +0,0 @@ -getAttribute('platform') === $resource->getAttribute('platform'); - } - - protected function shouldAllocateLease( - DrydockResource $resource, - DrydockLease $lease, - array $other_leases) { - return true; - } - - protected function executeAcquireLease( - DrydockResource $resource, - DrydockLease $lease) { - - // Because preallocated resources are manually created, we should verify - // we have all the information we need. - PhutilTypeSpec::checkMap( - $resource->getAttributesForTypeSpec( - array('platform', 'host', 'port', 'credential', 'path')), - array( - 'platform' => 'string', - 'host' => 'string', - 'port' => 'string', // Value is a string from the command line - 'credential' => 'string', - 'path' => 'string', - )); - $v_platform = $resource->getAttribute('platform'); - $v_path = $resource->getAttribute('path'); - - // Similar to DrydockLocalHostBlueprint, we create a folder - // on the remote host that the lease can use. - - $lease_id = $lease->getID(); - - // Can't use DIRECTORY_SEPERATOR here because that is relevant to - // the platform we're currently running on, not the platform we are - // remoting to. - $separator = '/'; - if ($v_platform === 'windows') { - $separator = '\\'; - } - - // Clean up the directory path a little. - $base_path = rtrim($v_path, '/'); - $base_path = rtrim($base_path, '\\'); - $full_path = $base_path.$separator.$lease_id; - - $cmd = $lease->getInterface('command'); - - $cmd->execx('mkdir %s', $full_path); - - $lease->setAttribute('path', $full_path); - } - - public function getType() { - return 'host'; - } - - public function getInterface( - DrydockResource $resource, - DrydockLease $lease, - $type) { - - switch ($type) { - case 'command': - return id(new DrydockSSHCommandInterface()) - ->setConfiguration(array( - 'host' => $resource->getAttribute('host'), - 'port' => $resource->getAttribute('port'), - 'credential' => $resource->getAttribute('credential'), - 'platform' => $resource->getAttribute('platform'), - )) - ->setWorkingDirectory($lease->getAttribute('path')); - case 'filesystem': - return id(new DrydockSFTPFilesystemInterface()) - ->setConfiguration(array( - 'host' => $resource->getAttribute('host'), - 'port' => $resource->getAttribute('port'), - 'credential' => $resource->getAttribute('credential'), - )); - } - - throw new Exception(pht("No interface of type '%s'.", $type)); - } - -} From 635e9c6075273fd3479a8903f8082db1b0bd72b7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:41:52 -0700 Subject: [PATCH 06/43] Provide a generic "Datasource" StandardCustomField Summary: Ref T9253. See discussion in D13843. I want to let Drydock blueprints for Almanac services choose those services from a typeahead, but only list appropriate services in the typeahead. To do this: - Provide a StandardCustomField for an arbitrary datasource. - Adjust the AlmanacServiceDatasource to allow filtering by service class. This implementation is substantially the same as the one in D13843, with some adjustments: - I lifted most of the code in the `Users` standard custom field into a new `Tokenizer` standard custom field. - The `Users` and `Datasource` custom fields now extend the `Tokenizer` custom field and can share most of the code it uses. - I exposed this field fully as a configurable field. I don't think anyone will ever use it, but this generality costs us nearly nothing and improves consistency. - The code in D13843 didn't actually pass the parameters over the wire, since the object that responds to the request is not the same object that renders the field. Use the "parameters" mechanism in datasources to get things passed over the wire. Test Plan: - Created a custom "users" field in Maniphest and made sure it still wokred. - Created a custom "almanc services" field in Maniphest and selected some services for a task. - With additional changes from D13843, selected an appropriate Almanac service in a new Drydock blueprint. Reviewers: hach-que, chad Reviewed By: hach-que, chad Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14111 --- src/__phutil_library_map__.php | 6 ++- .../typeahead/AlmanacServiceDatasource.php | 10 ++++ .../user/configuration/custom_fields.diviner | 3 ++ ...abricatorStandardCustomFieldDatasource.php | 28 +++++++++++ ...habricatorStandardCustomFieldTokenizer.php | 47 +++++++++++++++++++ .../PhabricatorStandardCustomFieldUsers.php | 41 ++-------------- 6 files changed, 96 insertions(+), 39 deletions(-) create mode 100644 src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php create mode 100644 src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cd5c6c5b65..15395094dd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2915,6 +2915,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php', 'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php', 'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php', + 'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php', 'PhabricatorStandardCustomFieldDate' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php', 'PhabricatorStandardCustomFieldHeader' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php', 'PhabricatorStandardCustomFieldInt' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php', @@ -2924,6 +2925,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldRemarkup' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php', 'PhabricatorStandardCustomFieldSelect' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php', 'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php', + 'PhabricatorStandardCustomFieldTokenizer' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php', 'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php', 'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php', 'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php', @@ -7022,6 +7024,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomField' => 'PhabricatorCustomField', 'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField', + 'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer', 'PhabricatorStandardCustomFieldDate' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldHeader' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldInt' => 'PhabricatorStandardCustomField', @@ -7030,7 +7033,8 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldRemarkup' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldSelect' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField', - 'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldPHIDs', + 'PhabricatorStandardCustomFieldTokenizer' => 'PhabricatorStandardCustomFieldPHIDs', + 'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldTokenizer', 'PhabricatorStandardPageView' => 'PhabricatorBarePageView', 'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorStatusController' => 'PhabricatorController', diff --git a/src/applications/almanac/typeahead/AlmanacServiceDatasource.php b/src/applications/almanac/typeahead/AlmanacServiceDatasource.php index e5728e1c26..621b0408ae 100644 --- a/src/applications/almanac/typeahead/AlmanacServiceDatasource.php +++ b/src/applications/almanac/typeahead/AlmanacServiceDatasource.php @@ -23,6 +23,16 @@ final class AlmanacServiceDatasource ->withNamePrefix($raw_query) ->setOrder('name'); + // TODO: When service classes are restricted, it might be nice to customize + // the title and placeholder text to show which service types can be + // selected, or show all services but mark the invalid ones disabled and + // prevent their selection. + + $service_classes = $this->getParameter('serviceClasses'); + if ($service_classes) { + $services->withServiceClasses($service_classes); + } + $services = $this->executeQuery($services); if ($services) { diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner index 6643a0518f..9ada0efa06 100644 --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -144,6 +144,9 @@ change, but are documented here for completeness: - **Credentials**: Controls with type `credential` allow selection of a Passphrase credential which provides `credential.provides`, and creation of credentials of `credential.type`. + - **Datasource**: Controls with type `datasource` allow selection of tokens + from an arbitrary datasource, controlled with `datasource.class` and + `datasource.parameters`. = Advanced Custom Fields = diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php new file mode 100644 index 0000000000..49b9ab2cb5 --- /dev/null +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php @@ -0,0 +1,28 @@ +getFieldConfigValue('datasource.parameters', array()); + + $class = $this->getFieldConfigValue('datasource.class'); + $parent = 'PhabricatorTypeaheadDatasource'; + if (!is_subclass_of($class, $parent)) { + throw new Exception( + pht( + 'Configured datasource class "%s" must be a valid subclass of '. + '"%s".', + $class, + $parent)); + } + + return newv($class, array()) + ->setParameters($parameters); + } + +} diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php new file mode 100644 index 0000000000..f6a542ec7f --- /dev/null +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php @@ -0,0 +1,47 @@ +getFieldValue(); + + $control = id(new AphrontFormTokenizerControl()) + ->setUser($this->getViewer()) + ->setLabel($this->getFieldName()) + ->setName($this->getFieldKey()) + ->setDatasource($this->getDatasource()) + ->setCaption($this->getCaption()) + ->setValue(nonempty($value, array())); + + $limit = $this->getFieldConfigValue('limit'); + if ($limit) { + $control->setLimit($limit); + } + + return $control; + } + + public function appendToApplicationSearchForm( + PhabricatorApplicationSearchEngine $engine, + AphrontFormView $form, + $value) { + + $control = id(new AphrontFormTokenizerControl()) + ->setLabel($this->getFieldName()) + ->setName($this->getFieldKey()) + ->setDatasource($this->getDatasource()) + ->setValue(nonempty($value, array())); + + $form->appendControl($control); + } + + public function getHeraldFieldValueType($condition) { + return id(new HeraldTokenizerFieldValue()) + ->setKey('custom.'.$this->getFieldKey()) + ->setDatasource($this->getDatasource()); + } + +} diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php index 7efe873a1d..2f1e6db53a 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php @@ -1,49 +1,14 @@ getFieldValue(); - - $control = id(new AphrontFormTokenizerControl()) - ->setUser($this->getViewer()) - ->setLabel($this->getFieldName()) - ->setName($this->getFieldKey()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setCaption($this->getCaption()) - ->setValue(nonempty($value, array())); - - $limit = $this->getFieldConfigValue('limit'); - if ($limit) { - $control->setLimit($limit); - } - - return $control; - } - - public function appendToApplicationSearchForm( - PhabricatorApplicationSearchEngine $engine, - AphrontFormView $form, - $value) { - - $control = id(new AphrontFormTokenizerControl()) - ->setLabel($this->getFieldName()) - ->setName($this->getFieldKey()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setValue(nonempty($value, array())); - - $form->appendControl($control); - } - - public function getHeraldFieldValueType($condition) { - return id(new HeraldTokenizerFieldValue()) - ->setKey('custom.'.$this->getFieldKey()) - ->setDatasource(new PhabricatorPeopleDatasource()); + public function getDatasource() { + return new PhabricatorPeopleDatasource(); } } From 5362d3366ce228d5cee87e9b068cb7c264d5f2ce Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:42:04 -0700 Subject: [PATCH 07/43] Modernize Drydock Query + Attach code Summary: Ref T9253. Some of the Drydock code is pretty old. This applies standard modernizations to it: - Modernize Query classes to use stuff like `buildWhereClauseParts()` and `loadStandardPage()`. - Modernize all the getX() / attachX() stuff. In particular: - Require and attach implementations to Blueprints. - Require and attach Blueprints to Resources. - BlueprintImplementations are now always unique per-Blueprint so they can store/cache state if they want without running over one another. - BlueprintImplementations are now passed a `$blueprint`, like other similar APIs (this could go various ways but I generally like this as a balance of concerns). NOTE: This probably doesn't run on its own, I'm just trying to split the next diff (core allocator stuff) up a bit and these pieces are all pretty standard. Test Plan: - Not much; see next revision or two. - Clicked around Resource and Blueprint lists. Reviewers: chad, hach-que Reviewed By: chad, hach-que Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14113 --- .../drydock/query/DrydockBlueprintQuery.php | 77 +++++++++++++------ .../drydock/query/DrydockLeaseQuery.php | 8 +- .../drydock/query/DrydockResourceQuery.php | 53 ++++++++----- .../drydock/storage/DrydockResource.php | 24 ++++-- 4 files changed, 104 insertions(+), 58 deletions(-) diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php index abad714d8f..c8e53217c9 100644 --- a/src/applications/drydock/query/DrydockBlueprintQuery.php +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -4,6 +4,7 @@ final class DrydockBlueprintQuery extends DrydockQuery { private $ids; private $phids; + private $blueprintClasses; private $datasourceQuery; public function withIDs(array $ids) { @@ -16,63 +17,89 @@ final class DrydockBlueprintQuery extends DrydockQuery { return $this; } + public function withBlueprintClasses(array $classes) { + $this->blueprintClasses = $classes; + return $this; + } + public function withDatasourceQuery($query) { $this->datasourceQuery = $query; return $this; } + public function newResultObject() { + return new DrydockBlueprint(); + } + protected function loadPage() { - $table = new DrydockBlueprint(); - $conn_r = $table->establishConnection('r'); + return $this->loadStandardPage($this->newResultObject()); + } - $data = queryfx_all( - $conn_r, - 'SELECT blueprint.* FROM %T blueprint %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $blueprints = $table->loadAllFromArray($data); - - $implementations = - DrydockBlueprintImplementation::getAllBlueprintImplementations(); - - foreach ($blueprints as $blueprint) { - if (array_key_exists($blueprint->getClassName(), $implementations)) { - $blueprint->attachImplementation( - $implementations[$blueprint->getClassName()]); + protected function willFilterPage(array $blueprints) { + $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); + foreach ($blueprints as $key => $blueprint) { + $impl = idx($impls, $blueprint->getClassName()); + if (!$impl) { + $this->didRejectResult($blueprint); + unset($blueprints[$key]); + continue; } + $impl = clone $impl; + $blueprint->attachImplementation($impl); } return $blueprints; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->datasourceQuery !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'blueprintName LIKE %>', $this->datasourceQuery); } - return $this->formatWhereClause($where); + if ($this->blueprintClasses !== null) { + $where[] = qsprintf( + $conn, + 'className IN (%Ls)', + $this->blueprintClasses); + } + + return $where; + } + + public function getOrderableColumns() { + // TODO: Blueprints implement CustomFields, but can not be ordered by + // custom field classes because the custom fields are not global. There + // is no graceful way to handle this in ApplicationSearch at the moment. + // Just brute force around it until we can clean this up. + + return array( + 'id' => array( + 'table' => $this->getPrimaryTableAlias(), + 'column' => 'id', + 'reverse' => false, + 'type' => 'int', + 'unique' => true, + ), + ); } } diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index 37f02bd748..f7adc07cce 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -28,15 +28,15 @@ final class DrydockLeaseQuery extends DrydockQuery { return $this; } - public function newResultObject() { - return new DrydockLease(); - } - public function withDatasourceQuery($query) { $this->datasourceQuery = $query; return $this; } + public function newResultObject() { + return new DrydockLease(); + } + protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } diff --git a/src/applications/drydock/query/DrydockResourceQuery.php b/src/applications/drydock/query/DrydockResourceQuery.php index d07e729276..d15b737141 100644 --- a/src/applications/drydock/query/DrydockResourceQuery.php +++ b/src/applications/drydock/query/DrydockResourceQuery.php @@ -39,71 +39,82 @@ final class DrydockResourceQuery extends DrydockQuery { return $this; } + public function newResultObject() { + return new DrydockResource(); + } + protected function loadPage() { - $table = new DrydockResource(); - $conn_r = $table->establishConnection('r'); + return $this->loadStandardPage($this->newResultObject()); + } - $data = queryfx_all( - $conn_r, - 'SELECT resource.* FROM %T resource %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + protected function willFilterPage(array $resources) { + $blueprint_phids = mpull($resources, 'getBlueprintPHID'); - $resources = $table->loadAllFromArray($data); + $blueprints = id(new DrydockBlueprintQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($blueprint_phids) + ->execute(); + $blueprints = mpull($blueprints, null, 'getPHID'); + + foreach ($resources as $key => $resource) { + $blueprint = idx($blueprints, $resource->getBlueprintPHID()); + if (!$blueprint) { + $this->didRejectResult($resource); + unset($resources[$key]); + continue; + } + $resource->attachBlueprint($blueprint); + } return $resources; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->types !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'type IN (%Ls)', $this->types); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ls)', $this->statuses); } if ($this->blueprintPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'blueprintPHID IN (%Ls)', $this->blueprintPHIDs); } if ($this->datasourceQuery !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'name LIKE %>', $this->datasourceQuery); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index d078c76c2f..87638d1ae0 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -14,7 +14,7 @@ final class DrydockResource extends DrydockDAO protected $capabilities = array(); protected $ownerPHID; - private $blueprint; + private $blueprint = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -65,16 +65,24 @@ final class DrydockResource extends DrydockDAO } public function getBlueprint() { - // TODO: Policy stuff. - if (empty($this->blueprint)) { - $blueprint = id(new DrydockBlueprint()) - ->loadOneWhere('phid = %s', $this->blueprintPHID); - $this->blueprint = $blueprint->getImplementation(); - } - return $this->blueprint; + return $this->assertAttached($this->blueprint); + } + + public function attachBlueprint(DrydockBlueprint $blueprint) { + $this->blueprint = $blueprint; + return $this; + } + + public function canAllocateLease(DrydockLease $lease) { + return $this->getBlueprint()->canAllocateLeaseOnResource( + $this, + $lease); } public function closeResource() { + + // TODO: This is super broken and will race other lease writers! + $this->openTransaction(); $statuses = array( DrydockLeaseStatus::STATUS_PENDING, From bb28f94f9b646520d156d7d90c8007b6f64956dd Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:43:25 -0700 Subject: [PATCH 08/43] Reduce garbage-level of Drydock Allocator implementation Summary: Ref T9253. The Drydock allocator is very pseudocodey right now. Particularly, it was written before Blueprints were concrete. Reorganize it to make its responsibilities and error handling behaviors more clear. In particular, the Allocator does not manage locks. It's primarily trying to reject allocations which can not possibly work. Blueprints are responsible for locks. See some discussion in D10304. NOTE: This code probably doesn't work as written, see future diffs. Test Plan: See future diffs. Reviewers: hach-que, chad Reviewed By: hach-que, chad Subscribers: cburroughs Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14114 --- .../DrydockBlueprintImplementation.php | 147 ++++-- ...dockWorkingCopyBlueprintImplementation.php | 31 +- .../drydock/storage/DrydockBlueprint.php | 31 +- .../drydock/worker/DrydockAllocatorWorker.php | 426 +++++++++++------- 4 files changed, 430 insertions(+), 205 deletions(-) diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 6a6e146cf1..f20b1c6f9b 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -68,41 +68,26 @@ abstract class DrydockBlueprintImplementation extends Phobject { /* -( Lease Acquisition )-------------------------------------------------- */ - /** - * @task lease - */ - final public function filterResource( - DrydockResource $resource, - DrydockLease $lease) { - - $scope = $this->pushActiveScope($resource, $lease); - - return $this->canAllocateLease($resource, $lease); - } - - /** * Enforce basic checks on lease/resource compatibility. Allows resources to * reject leases if they are incompatible, even if the resource types match. * * For example, if a resource represents a 32-bit host, this method might - * reject leases that need a 64-bit host. If a resource represents a working - * copy of repository "X", this method might reject leases which need a - * working copy of repository "Y". Generally, although the main types of - * a lease and resource may match (e.g., both "host"), it may not actually be - * possible to satisfy the lease with a specific resource. + * reject leases that need a 64-bit host. The blueprint might also reject + * a resource if the lease needs 8GB of RAM and the resource only has 6GB + * free. * - * This method generally should not enforce limits or perform capacity - * checks. Perform those in @{method:shouldAllocateLease} instead. It also - * should not perform actual acquisition of the lease; perform that in - * @{method:executeAcquireLease} instead. + * This method should not acquire locks or expect anything to be locked. This + * is a coarse compatibility check between a lease and a resource. * - * @param DrydockResource Candidiate resource to allocate the lease on. - * @param DrydockLease Pending lease that wants to allocate here. - * @return bool True if the resource and lease are compatible. + * @param DrydockBlueprint Concrete blueprint to allocate for. + * @param DrydockResource Candidiate resource to allocate the lease on. + * @param DrydockLease Pending lease that wants to allocate here. + * @return bool True if the resource and lease are compatible. * @task lease */ - abstract protected function canAllocateLease( + abstract public function canAllocateLeaseOnResource( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease); @@ -297,14 +282,102 @@ abstract class DrydockBlueprintImplementation extends Phobject { /* -( Resource Allocation )------------------------------------------------ */ - public function canAllocateMoreResources(array $pool) { - return true; - } - - abstract protected function executeAllocateResource(DrydockLease $lease); + /** + * Enforce fundamental implementation/lease checks. Allows implementations to + * reject a lease which no concrete blueprint can ever satisfy. + * + * For example, if a lease only builds ARM hosts and the lease needs a + * PowerPC host, it may be rejected here. + * + * This is the earliest rejection phase, and followed by + * @{method:canEverAllocateResourceForLease}. + * + * This method should not actually check if a resource can be allocated + * right now, or even if a blueprint which can allocate a suitable resource + * really exists, only if some blueprint may conceivably exist which could + * plausibly be able to build a suitable resource. + * + * @param DrydockLease Requested lease. + * @return bool True if some concrete blueprint of this implementation's + * type might ever be able to build a resource for the lease. + * @task resource + */ + abstract public function canAnyBlueprintEverAllocateResourceForLease( + DrydockLease $lease); - final public function allocateResource(DrydockLease $lease) { + /** + * Enforce basic blueprint/lease checks. Allows blueprints to reject a lease + * which they can not build a resource for. + * + * This is the second rejection phase. It follows + * @{method:canAnyBlueprintEverAllocateResourceForLease} and is followed by + * @{method:canAllocateResourceForLease}. + * + * This method should not check if a resource can be built right now, only + * if the blueprint as configured may, at some time, be able to build a + * suitable resource. + * + * @param DrydockBlueprint Blueprint which may be asked to allocate a + * resource. + * @param DrydockLease Requested lease. + * @return bool True if this blueprint can eventually build a suitable + * resource for the lease, as currently configured. + * @task resource + */ + abstract public function canEverAllocateResourceForLease( + DrydockBlueprint $blueprint, + DrydockLease $lease); + + + /** + * Enforce basic availability limits. Allows blueprints to reject resource + * allocation if they are currently overallocated. + * + * This method should perform basic capacity/limit checks. For example, if + * it has a limit of 6 resources and currently has 6 resources allocated, + * it might reject new leases. + * + * This method should not acquire locks or expect locks to be acquired. This + * is a coarse check to determine if the operation is likely to succeed + * right now without needing to acquire locks. + * + * It is expected that this method will sometimes return `true` (indicating + * that a resource can be allocated) but find that another allocator has + * eaten up free capacity by the time it actually tries to build a resource. + * This is normal and the allocator will recover from it. + * + * @param DrydockBlueprint The blueprint which may be asked to allocate a + * resource. + * @param DrydockLease Requested lease. + * @return bool True if this blueprint appears likely to be able to allocate + * a suitable resource. + */ + abstract public function canAllocateResourceForLease( + DrydockBlueprint $blueprint, + DrydockLease $lease); + + + /** + * Allocate a suitable resource for a lease. + * + * This method MUST acquire, hold, and manage locks to prevent multiple + * allocations from racing. World state is not locked before this method is + * called. Blueprints are entirely responsible for any lock handling they + * need to perform. + * + * @param DrydockBlueprint The blueprint which should allocate a resource. + * @param DrydockLease Requested lease. + * @return DrydockResource Allocated resource. + */ + abstract protected function executeAllocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease); + + final public function allocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + $scope = $this->pushActiveScope(null, $lease); $this->log( @@ -314,7 +387,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { $lease->getLeaseName())); try { - $resource = $this->executeAllocateResource($lease); + $resource = $this->executeAllocateResource($blueprint, $lease); $this->validateAllocatedResource($resource); } catch (Exception $ex) { $this->logException($ex); @@ -377,14 +450,6 @@ abstract class DrydockBlueprintImplementation extends Phobject { ->execute(); } - public static function getAllBlueprintImplementationsForResource($type) { - static $groups = null; - if ($groups === null) { - $groups = mgroup(self::getAllBlueprintImplementations(), 'getType'); - } - return idx($groups, $type, array()); - } - public static function getNamedImplementation($class) { return idx(self::getAllBlueprintImplementations(), $class); } diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 264394f8ac..b85a5dde9f 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -15,9 +15,31 @@ final class DrydockWorkingCopyBlueprintImplementation return pht('Allows Drydock to check out working copies of repositories.'); } - protected function canAllocateLease( + public function canAnyBlueprintEverAllocateResourceForLease( + DrydockLease $lease) { + // TODO: These checks are out of date. + return true; + } + + public function canEverAllocateResourceForLease( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + // TODO: These checks are out of date. + return true; + } + + public function canAllocateResourceForLease( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + // TODO: These checks are out of date. + return true; + } + + public function canAllocateLeaseOnResource( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { + // TODO: These checks are out of date. $resource_repo = $resource->getAttribute('repositoryID'); $lease_repo = $lease->getAttribute('repositoryID'); @@ -29,11 +51,14 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockResource $resource, DrydockLease $lease, array $other_leases) { - + // TODO: These checks are out of date. return !$other_leases; } - protected function executeAllocateResource(DrydockLease $lease) { + protected function executeAllocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + $repository_id = $lease->getAttribute('repositoryID'); if (!$repository_id) { throw new Exception( diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 8185d35388..132fb7fdc0 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -51,16 +51,7 @@ final class DrydockBlueprint extends DrydockDAO } public function getImplementation() { - $class = $this->className; - $implementations = - DrydockBlueprintImplementation::getAllBlueprintImplementations(); - if (!isset($implementations[$class])) { - throw new Exception( - pht( - "Invalid class name for blueprint (got '%s')", - $class)); - } - return id(new $class())->attachInstance($this); + return $this->assertAttached($this->implementation); } public function attachImplementation(DrydockBlueprintImplementation $impl) { @@ -77,6 +68,26 @@ final class DrydockBlueprint extends DrydockDAO return $this; } + public function canEverAllocateResourceForLease(DrydockLease $lease) { + return $this->getImplementation()->canEverAllocateResourceForLease( + $this, + $lease); + } + + public function canAllocateResourceForLease(DrydockLease $lease) { + return $this->getImplementation()->canAllocateResourceForLease( + $this, + $lease); + } + + public function canAllocateLeaseOnResource( + DrydockResource $resource, + DrydockLease $lease) { + return $this->getImplementation()->canAllocateLeaseOnResource( + $this, + $resource, + $lease); + } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index f9a647a3a8..1898bcaf66 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -2,186 +2,310 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { - private $lease; - - public function getRequiredLeaseTime() { - return 3600 * 24; - } - - public function getMaximumRetryCount() { - // TODO: Allow Drydock allocations to retry. For now, every failure is - // permanent and most of them are because I am bad at programming, so fail - // fast rather than ending up in limbo. - return 0; + private function getViewer() { + return PhabricatorUser::getOmnipotentUser(); } private function loadLease() { - if (empty($this->lease)) { - $lease = id(new DrydockLeaseQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($this->getTaskData())) - ->executeOne(); - if (!$lease) { - throw new PhabricatorWorkerPermanentFailureException( - pht('No such lease %d!', $this->getTaskData())); - } - $this->lease = $lease; - } - return $this->lease; - } + $viewer = $this->getViewer(); - private function logToDrydock($message) { - DrydockBlueprintImplementation::writeLog( - null, - $this->loadLease(), - $message); + // TODO: Make the task data a dictionary like every other worker, and + // probably make this a PHID. + $lease_id = $this->getTaskData(); + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withIDs(array($lease_id)) + ->executeOne(); + if (!$lease) { + throw new PhabricatorWorkerPermanentFailureException( + pht('No such lease "%s"!', $lease_id)); + } + + return $lease; } protected function doWork() { $lease = $this->loadLease(); - $this->logToDrydock(pht('Allocating Lease')); - - try { - $this->allocateLease($lease); - } catch (Exception $ex) { - - // TODO: We should really do this when archiving the task, if we've - // suffered a permanent failure. But we don't have hooks for that yet - // and always fail after the first retry right now, so this is - // functionally equivalent. - $lease->reload(); - if ($lease->getStatus() == DrydockLeaseStatus::STATUS_PENDING) { - $lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN); - $lease->save(); - } - - throw $ex; - } - } - - private function loadAllBlueprints() { - $viewer = PhabricatorUser::getOmnipotentUser(); - $instances = id(new DrydockBlueprintQuery()) - ->setViewer($viewer) - ->execute(); - $blueprints = array(); - foreach ($instances as $instance) { - $blueprints[$instance->getPHID()] = $instance; - } - return $blueprints; + $this->allocateLease($lease); } private function allocateLease(DrydockLease $lease) { - $type = $lease->getResourceType(); + $blueprints = $this->loadBlueprintsForAllocatingLease($lease); - $blueprints = $this->loadAllBlueprints(); + // If we get nothing back, that means no blueprint is defined which can + // ever build the requested resource. This is a permanent failure, since + // we don't expect to succeed no matter how many times we try. + if (!$blueprints) { + $lease + ->setStatus(DrydockLeaseStatus::STATUS_BROKEN) + ->save(); + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'No active Drydock blueprint exists which can ever allocate a '. + 'resource for lease "%s".', + $lease->getPHID())); + } - // TODO: Policy stuff. - $pool = id(new DrydockResource())->loadAllWhere( - 'type = %s AND status = %s', - $lease->getResourceType(), - DrydockResourceStatus::STATUS_OPEN); + // First, try to find a suitable open resource which we can acquire a new + // lease on. + $resources = $this->loadResourcesForAllocatingLease($blueprints, $lease); - $this->logToDrydock( - pht('Found %d Open Resource(s)', count($pool))); + // If no resources exist yet, see if we can build one. + if (!$resources) { + $usable_blueprints = $this->removeOverallocatedBlueprints( + $blueprints, + $lease); - $candidates = array(); - foreach ($pool as $key => $candidate) { - if (!isset($blueprints[$candidate->getBlueprintPHID()])) { - unset($pool[$key]); + // If we get nothing back here, some blueprint claims it can eventually + // satisfy the lease, just not right now. This is a temporary failure, + // and we expect allocation to succeed eventually. + if (!$blueprints) { + // TODO: More formal temporary failure here. We should retry this + // "soon" but not "immediately". + throw new Exception( + pht('No blueprints have space to allocate a resource right now.')); + } + + $usable_blueprints = $this->rankBlueprints($blueprints, $lease); + + $exceptions = array(); + foreach ($usable_blueprints as $blueprint) { + try { + $resources[] = $blueprint->allocateResource($lease); + // Bail after allocating one resource, we don't need any more than + // this. + break; + } catch (Exception $ex) { + $exceptions[] = $ex; + } + } + + if (!$resources) { + // TODO: We should distinguish between temporary and permament failures + // here. If any blueprint failed temporarily, retry "soon". If none + // of these failures were temporary, maybe this should be a permanent + // failure? + throw new PhutilAggregateException( + pht( + 'All blueprints failed to allocate a suitable new resource when '. + 'trying to allocate lease "%s".', + $lease->getPHID()), + $exceptions); + } + + // NOTE: We have not acquired the lease yet, so it is possible that the + // resource we just built will be snatched up by some other lease before + // we can. This is not problematic: we'll retry a little later and should + // suceed eventually. + } + + $resources = $this->rankResources($resources, $lease); + + $exceptions = array(); + $allocated = false; + foreach ($resources as $resource) { + try { + $blueprint->allocateLease($resource, $lease); + $allocated = true; + break; + } catch (Exception $ex) { + $exceptions[] = $ex; + } + } + + if (!$allocated) { + // TODO: We should distinguish between temporary and permanent failures + // here. If any failures were temporary (specifically, failed to acquire + // locks) + + throw new PhutilAggregateException( + pht( + 'Unable to acquire lease "%s" on any resouce.', + $lease->getPHID()), + $exceptions); + } + } + + + /** + * Load a list of all resources which a given lease can possibly be + * allocated against. + * + * @param list Blueprints which may produce suitable + * resources. + * @param DrydockLease Requested lease. + * @return list Resources which may be able to allocate + * the lease. + */ + private function loadResourcesForAllocatingLease( + array $blueprints, + DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + $viewer = $this->getViewer(); + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withBlueprintPHIDs(mpull($blueprints, 'getPHID')) + ->withTypes(array($lease->getResourceType())) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_PENDING, + DrydockResourceStatus::STATUS_OPEN, + )) + ->execute(); + + $keep = array(); + foreach ($resources as $key => $resource) { + if (!$resource->canAllocateLease($lease)) { continue; } - $blueprint = $blueprints[$candidate->getBlueprintPHID()]; - $implementation = $blueprint->getImplementation(); - - if ($implementation->filterResource($candidate, $lease)) { - $candidates[] = $candidate; - } + $keep[$key] = $resource; } - $this->logToDrydock(pht('%d Open Resource(s) Remain', count($candidates))); + return $keep; + } - $resource = null; - if ($candidates) { - shuffle($candidates); - foreach ($candidates as $candidate_resource) { - $blueprint = $blueprints[$candidate_resource->getBlueprintPHID()] - ->getImplementation(); - if ($blueprint->allocateLease($candidate_resource, $lease)) { - $resource = $candidate_resource; - break; - } - } + + /** + * Rank blueprints by suitability for building a new resource for a + * particular lease. + * + * @param list List of blueprints. + * @param DrydockLease Requested lease. + * @return list Ranked list of blueprints. + */ + private function rankBlueprints(array $blueprints, DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + + // TODO: Implement improvements to this ranking algorithm if they become + // available. + shuffle($blueprints); + + return $blueprints; + } + + + /** + * Rank resources by suitability for allocating a particular lease. + * + * @param list List of resources. + * @param DrydockLease Requested lease. + * @return list Ranked list of resources. + */ + private function rankResources(array $resources, DrydockLease $lease) { + assert_instances_of($resources, 'DrydockResource'); + + // TODO: Implement improvements to this ranking algorithm if they become + // available. + shuffle($resources); + + return $resources; + } + + + /** + * Get all the concrete @{class:DrydockBlueprint}s which can possibly + * build a resource to satisfy a lease. + * + * @param DrydockLease Requested lease. + * @return list List of qualifying blueprints. + */ + private function loadBlueprintsForAllocatingLease( + DrydockLease $lease) { + $viewer = $this->getViewer(); + + $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease); + if (!$impls) { + return array(); } - if (!$resource) { - $blueprints = DrydockBlueprintImplementation - ::getAllBlueprintImplementationsForResource($type); + // TODO: When blueprints can be disabled, this query should ignore disabled + // blueprints. - $this->logToDrydock( - pht('Found %d Blueprints', count($blueprints))); + $blueprints = id(new DrydockBlueprintQuery()) + ->setViewer($viewer) + ->withBlueprintClasses(array_keys($impls)) + ->execute(); - foreach ($blueprints as $key => $candidate_blueprint) { - if (!$candidate_blueprint->isEnabled()) { - unset($blueprints[$key]); - continue; - } + $keep = array(); + foreach ($blueprints as $key => $blueprint) { + if (!$blueprint->canEverAllocateResourceForLease($lease)) { + continue; } - $this->logToDrydock( - pht('%d Blueprints Enabled', count($blueprints))); - - foreach ($blueprints as $key => $candidate_blueprint) { - if (!$candidate_blueprint->canAllocateMoreResources($pool)) { - unset($blueprints[$key]); - continue; - } - } - - $this->logToDrydock( - pht('%d Blueprints Can Allocate', count($blueprints))); - - if (!$blueprints) { - $lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN); - $lease->save(); - - $this->logToDrydock( - pht( - "There are no resources of type '%s' available, and no ". - "blueprints which can allocate new ones.", - $type)); - - return; - } - - // TODO: Rank intelligently. - shuffle($blueprints); - - $blueprint = head($blueprints); - $resource = $blueprint->allocateResource($lease); - - if (!$blueprint->allocateLease($resource, $lease)) { - // TODO: This "should" happen only if we lost a race with another lease, - // which happened to acquire this resource immediately after we - // allocated it. In this case, the right behavior is to retry - // immediately. However, other things like a blueprint allocating a - // resource it can't actually allocate the lease on might be happening - // too, in which case we'd just allocate infinite resources. Probably - // what we should do is test for an active or allocated lease and retry - // if we find one (although it might have already been released by now) - // and fail really hard ("your configuration is a huge broken mess") - // otherwise. But just throw for now since this stuff is all edge-casey. - // Alternatively we could bring resources up in a "BESPOKE" status - // and then switch them to "OPEN" only after the allocating lease gets - // its grubby mitts on the resource. This might make more sense but - // is a bit messy. - throw new Exception(pht('Lost an allocation race?')); - } + $keep[$key] = $blueprint; } - $blueprint = $resource->getBlueprint(); - $blueprint->acquireLease($resource, $lease); + return $keep; + } + + + /** + * Get all the @{class:DrydockBlueprintImplementation}s which can possibly + * build a resource to satisfy a lease. + * + * This method returns blueprints which might, at some time, be able to + * build a resource which can satisfy the lease. They may not be able to + * build that resource right now. + * + * @param DrydockLease Requested lease. + * @return list List of qualifying blueprint + * implementations. + */ + private function loadBlueprintImplementationsForAllocatingLease( + DrydockLease $lease) { + + $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); + + $keep = array(); + foreach ($impls as $key => $impl) { + // Don't use disabled blueprint types. + if (!$impl->isEnabled()) { + continue; + } + + // Don't use blueprint types which can't allocate the correct kind of + // resource. + if ($impl->getType() != $lease->getResourceType()) { + continue; + } + + if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { + continue; + } + + $keep[$key] = $impl; + } + + return $keep; + } + + + /** + * Remove blueprints which are too heavily allocated to build a resource for + * a lease from a list of blueprints. + * + * @param list List of blueprints. + * @param list List with fully allocated blueprints + * removed. + */ + private function removeOverallocatedBlueprints( + array $blueprints, + DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + + $keep = array(); + foreach ($blueprints as $key => $blueprint) { + if (!$blueprint->canAllocateResourceForLease($lease)) { + continue; + } + + $keep[$key] = $blueprint; + } + + return $keep; } } From 6e03419593a6726e61c0912f78278508133db5e6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:43:53 -0700 Subject: [PATCH 09/43] Implement a rough AlmanacService blueprint in Drydock Summary: Ref T9253. Broadly, this realigns Allocator behavior to be more consistent and straightforward and amenable to intended future changes. This attempts to make language more consistent: resources are "allocated" and leases are "acquired". This prepares for (but does not implement) optimistic "slot locking", as discussed in D10304. Although I suspect some blueprints will need to perform other locking eventually, this does feel like a good fit for most of the locking blueprints need to do. In particular, I've made the blueprint operations on `$resource` and `$lease` objects more purposeful: they need to invoke an activator on the appropriate object to be implemented correctly. Before they invoke this activator method, they configure the object. In a future diff, this configuration will include specifying slot locks that the lease or resource must acquire. So the API will be something like: $lease ->setActivateWhenAcquired(true) ->needSlotLock('x') ->needSlotLock('y') ->acquireOnResource($resource); In the common case where slot locks are a good fit, I think this should make correct blueprint implementation very straightforward. This prepares for (but does not implement) resources and leases which need significant setup steps. I've basically carved out two modes: - The "activate immediately" mode, as here, immediately opens the resource or activates the lease. This is appropriate if little or no setup is required. I expect many leases to operate in this mode, although I expect many resources will operate in the other mode. - The "allocate now, activate later" mode, which is not fully implemented yet. This will queue setup workers when the allocator exits. Overall, this will work very similarly to Harbormaster. - This new structure makes it acceptable for blueprints to sleep as long as they want during resource allocation and lease acquisition, so long as they are not waiting on anything which needs to be completed by the queue. Putting a `sleep(15 * 60)` in your EC2Blueprint to wait for EC2 to bring a machine up will perform worse than using delayed activation, but won't deadlock the queue or block any locks. Overall, this flow is more similar to Harbormaster's flow. Having consistency between Harbormaster's model and Drydock's model is good, and I think Harbormaster's model is also simply much better than Drydock's (what exists today in Drydock was implemented a long time ago, and we had more support and infrastructure by the time Harbormaster was implemented, as well as a more clearly defined problem). The particular strength of Harbormaster is that objects always (or almost always, at least) have a single, clearly defined writer. Ensuring objects have only one writer prevents races and makes reasoning about everything easier. Drydock does not currently have a clearly defined single writer, but this moves us in that direction. We'll probably need more primitives eventually to flesh this out, like Harbormaster's command queue for messaging objects which you can't write to. This blueprint was originally implemented in D13843. This makes a few changes to the blueprint itself: - A bunch of code from that (e.g., interfaces) doesn't exist yet. - I let the blueprint have multiple services. This simplifies the code a little and seems like it costs us nothing. This also removes `bin/drydock create-resource`, which no longer makes sense to expose. It won't get locking, leasing, etc., correct, and can not be made correct. NOTE: This technically works but doesn't do anything useful yet. Test Plan: Used `bin/drydock lease --type host` to acquire leases against these blueprints. Reviewers: hach-que, chad Reviewed By: hach-que, chad Subscribers: Mnkras Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14117 --- src/__phutil_library_map__.php | 4 +- ...anacServiceHostBlueprintImplementation.php | 234 ++++++++++++ .../DrydockBlueprintImplementation.php | 237 +----------- ...dockWorkingCopyBlueprintImplementation.php | 16 +- .../drydock/constants/DrydockLeaseStatus.php | 6 +- .../DrydockBlueprintCoreCustomField.php | 4 + .../DrydockBlueprintCustomField.php | 6 +- ...rydockManagementCreateResourceWorkflow.php | 81 ---- .../query/DrydockLeaseSearchEngine.php | 2 +- .../drydock/storage/DrydockBlueprint.php | 78 +++- .../drydock/storage/DrydockLease.php | 53 ++- .../drydock/storage/DrydockResource.php | 47 ++- .../drydock/worker/DrydockAllocatorWorker.php | 345 +++++++++++++----- .../PassphraseCredentialEditController.php | 2 +- 14 files changed, 699 insertions(+), 416 deletions(-) create mode 100644 src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php delete mode 100644 src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 15395094dd..e43291b393 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -797,6 +797,7 @@ phutil_register_library_map(array( 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', 'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php', + 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', 'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php', @@ -845,7 +846,6 @@ phutil_register_library_map(array( 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', - 'DrydockManagementCreateResourceWorkflow' => 'applications/drydock/management/DrydockManagementCreateResourceWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', @@ -4502,6 +4502,7 @@ phutil_register_library_map(array( 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', 'DrydockAllocatorWorker' => 'PhabricatorWorker', + 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 'DrydockBlueprint' => array( 'DrydockDAO', @@ -4564,7 +4565,6 @@ phutil_register_library_map(array( 'DrydockLogQuery' => 'DrydockQuery', 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', - 'DrydockManagementCreateResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php new file mode 100644 index 0000000000..102f8f07d2 --- /dev/null +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -0,0 +1,234 @@ +loadServices($blueprint); + $bindings = $this->loadAllBindings($services); + + if (!$bindings) { + // If there are no devices bound to the services for this blueprint, + // we can not allocate resources. + return false; + } + + return true; + } + + public function canAllocateResourceForLease( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + + // We will only allocate one resource per unique device bound to the + // services for this blueprint. Make sure we have a free device somewhere. + $free_bindings = $this->loadFreeBindings($blueprint); + if (!$free_bindings) { + return false; + } + + return true; + } + + public function allocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + + $free_bindings = $this->loadFreeBindings($blueprint); + shuffle($free_bindings); + + $exceptions = array(); + foreach ($free_bindings as $binding) { + $device = $binding->getDevice(); + $device_name = $device->getName(); + + $resource = $this->newResourceTemplate($blueprint, $device_name) + ->setActivateWhenAllocated(true) + ->setAttribute('almanacServicePHID', $binding->getServicePHID()) + ->setAttribute('almanacBindingPHID', $binding->getPHID()); + + // TODO: This algorithm can race, and the "free" binding may not be + // free by the time we acquire it. Do slot-locking here if that works + // out, or some other kind of locking if it does not. + + try { + return $resource->allocateResource(DrydockResourceStatus::STATUS_OPEN); + } catch (Exception $ex) { + $exceptions[] = $ex; + } + } + + throw new PhutilAggregateException( + pht('Unable to allocate any binding as a resource.'), + $exceptions); + } + + public function canAcquireLeaseOnResource( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + // TODO: We'll currently lease each resource an unlimited number of times, + // but should stop doing that. + + return true; + } + + public function acquireLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + // TODO: Once we have limit rules, we should perform slot locking (or other + // kinds of locking) here. + + $lease + ->setActivateWhenAcquired(true) + ->acquireOnResource($resource); + } + + public function getType() { + return 'host'; + } + + public function getInterface( + DrydockResource $resource, + DrydockLease $lease, + $type) { + // TODO: Actually do stuff here, this needs work and currently makes this + // entire exercise pointless. + } + + public function getFieldSpecifications() { + return array( + 'almanacServicePHIDs' => array( + 'name' => pht('Almanac Services'), + 'type' => 'datasource', + 'datasource.class' => 'AlmanacServiceDatasource', + 'datasource.parameters' => array( + 'serviceClasses' => $this->getAlmanacServiceClasses(), + ), + 'required' => true, + ), + 'credentialPHID' => array( + 'name' => pht('Credentials'), + 'type' => 'credential', + 'credential.provides' => + PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE, + 'credential.type' => + PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE, + ), + ) + parent::getFieldSpecifications(); + } + + private function loadServices(DrydockBlueprint $blueprint) { + if (!$this->services) { + $service_phids = $blueprint->getFieldValue('almanacServicePHIDs'); + if (!$service_phids) { + throw new Exception( + pht( + 'This blueprint ("%s") does not define any Almanac Service PHIDs.', + $blueprint->getBlueprintName())); + } + + $viewer = PhabricatorUser::getOmnipotentUser(); + $services = id(new AlmanacServiceQuery()) + ->setViewer($viewer) + ->withPHIDs($service_phids) + ->withServiceClasses($this->getAlmanacServiceClasses()) + ->needBindings(true) + ->execute(); + $services = mpull($services, null, 'getPHID'); + + if (count($services) != count($service_phids)) { + $missing_phids = array_diff($service_phids, array_keys($services)); + throw new Exception( + pht( + 'Some of the Almanac Services defined by this blueprint '. + 'could not be loaded. They may be invalid, no longer exist, '. + 'or be of the wrong type: %s.', + implode(', ', $missing_phids))); + } + + $this->services = $services; + } + + return $this->services; + } + + private function loadAllBindings(array $services) { + assert_instances_of($services, 'AlmanacService'); + $bindings = array_mergev(mpull($services, 'getBindings')); + return mpull($bindings, null, 'getPHID'); + } + + private function loadFreeBindings(DrydockBlueprint $blueprint) { + if ($this->freeBindings === null) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $pool = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withBlueprintPHIDs(array($blueprint->getPHID())) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_PENDING, + DrydockResourceStatus::STATUS_OPEN, + DrydockResourceStatus::STATUS_CLOSED, + )) + ->execute(); + + $allocated_phids = array(); + foreach ($pool as $resource) { + $allocated_phids[] = $resource->getAttribute('almanacDevicePHID'); + } + $allocated_phids = array_fuse($allocated_phids); + + $services = $this->loadServices($blueprint); + $bindings = $this->loadAllBindings($services); + + $free = array(); + foreach ($bindings as $binding) { + if (empty($allocated_phids[$binding->getPHID()])) { + $free[] = $binding; + } + } + + $this->freeBindings = $free; + } + + return $this->freeBindings; + } + + private function getAlmanacServiceClasses() { + return array( + 'AlmanacDrydockPoolServiceType', + ); + } + + +} diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index f20b1c6f9b..5f769d3f02 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -60,10 +60,6 @@ abstract class DrydockBlueprintImplementation extends Phobject { return array(); } - public function getDetail($key, $default = null) { - return $this->getInstance()->getDetail($key, $default); - } - /* -( Lease Acquisition )-------------------------------------------------- */ @@ -86,171 +82,29 @@ abstract class DrydockBlueprintImplementation extends Phobject { * @return bool True if the resource and lease are compatible. * @task lease */ - abstract public function canAllocateLeaseOnResource( + abstract public function canAcquireLeaseOnResource( DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease); /** - * @task lease - */ - final public function allocateLease( - DrydockResource $resource, - DrydockLease $lease) { - - $scope = $this->pushActiveScope($resource, $lease); - - $this->log(pht('Trying to Allocate Lease')); - - $lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING); - $lease->setResourceID($resource->getID()); - $lease->attachResource($resource); - - $ephemeral_lease = id(clone $lease)->makeEphemeral(); - - $allocated = false; - $allocation_exception = null; - - $resource->openTransaction(); - $resource->beginReadLocking(); - $resource->reload(); - - // TODO: Policy stuff. - $other_leases = id(new DrydockLease())->loadAllWhere( - 'status IN (%Ld) AND resourceID = %d', - array( - DrydockLeaseStatus::STATUS_ACQUIRING, - DrydockLeaseStatus::STATUS_ACTIVE, - ), - $resource->getID()); - - try { - $allocated = $this->shouldAllocateLease( - $resource, - $ephemeral_lease, - $other_leases); - } catch (Exception $ex) { - $allocation_exception = $ex; - } - - if ($allocated) { - $lease->save(); - } - $resource->endReadLocking(); - if ($allocated) { - $resource->saveTransaction(); - $this->log(pht('Allocated Lease')); - } else { - $resource->killTransaction(); - $this->log(pht('Failed to Allocate Lease')); - } - - if ($allocation_exception) { - $this->logException($allocation_exception); - } - - return $allocated; - } - - - /** - * Enforce lease limits on resources. Allows resources to reject leases if - * they would become over-allocated by accepting them. - * - * For example, if a resource represents disk space, this method might check - * how much space the lease is asking for (say, 200MB) and how much space is - * left unallocated on the resource. It could grant the lease (return true) - * if it has enough remaining space (more than 200MB), and reject the lease - * (return false) if it does not (less than 200MB). - * - * A resource might also allow only exclusive leases. In this case it could - * accept a new lease (return true) if there are no active leases, or reject - * the new lease (return false) if there any other leases. - * - * A lock is held on the resource while this method executes to prevent - * multiple processes from allocating leases on the resource simultaneously. - * However, this means you should implement the method as cheaply as possible. - * In particular, do not perform any actual acquisition or setup in this - * method. - * - * If allocation is permitted, the lease will be moved to `ACQUIRING` status - * and @{method:executeAcquireLease} will be called to actually perform - * acquisition. - * - * General compatibility checks unrelated to resource limits and capacity are - * better implemented in @{method:canAllocateLease}, which serves as a - * cheap filter before lock acquisition. - * - * @param DrydockResource Candidate resource to allocate the lease on. - * @param DrydockLease Pending lease that wants to allocate here. - * @param list Other allocated and acquired leases on the - * resource. The implementation can inspect them - * to verify it can safely add the new lease. - * @return bool True to allocate the lease on the resource; - * false to reject it. - * @task lease - */ - abstract protected function shouldAllocateLease( - DrydockResource $resource, - DrydockLease $lease, - array $other_leases); - - - /** - * @task lease - */ - final public function acquireLease( - DrydockResource $resource, - DrydockLease $lease) { - - $scope = $this->pushActiveScope($resource, $lease); - - $this->log(pht('Acquiring Lease')); - $lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE); - $lease->setResourceID($resource->getID()); - $lease->attachResource($resource); - - $ephemeral_lease = id(clone $lease)->makeEphemeral(); - - try { - $this->executeAcquireLease($resource, $ephemeral_lease); - } catch (Exception $ex) { - $this->logException($ex); - throw $ex; - } - - $lease->setAttributes($ephemeral_lease->getAttributes()); - $lease->save(); - $this->log(pht('Acquired Lease')); - } - - - /** - * Acquire and activate an allocated lease. Allows resources to peform setup - * as leases are brought online. - * - * Following a successful call to @{method:canAllocateLease}, a lease is moved - * to `ACQUIRING` status and this method is called after resource locks are - * released. Nothing is locked while this method executes; the implementation - * is free to perform expensive operations like writing files and directories, - * executing commands, etc. - * - * After this method executes, the lease status is moved to `ACTIVE` and the - * original leasee may access it. + * Acquire a lease. Allows resources to peform setup as leases are brought + * online. * * If acquisition fails, throw an exception. * - * @param DrydockResource Resource to acquire a lease on. - * @param DrydockLease Lease to acquire. - * @return void + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource to acquire a lease on. + * @param DrydockLease Requested lease. + * @return void + * @task lease */ - abstract protected function executeAcquireLease( + abstract public function acquireLease( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease); - - final public function releaseLease( DrydockResource $resource, DrydockLease $lease) { @@ -352,6 +206,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { * @param DrydockLease Requested lease. * @return bool True if this blueprint appears likely to be able to allocate * a suitable resource. + * @task resource */ abstract public function canAllocateResourceForLease( DrydockBlueprint $blueprint, @@ -369,34 +224,12 @@ abstract class DrydockBlueprintImplementation extends Phobject { * @param DrydockBlueprint The blueprint which should allocate a resource. * @param DrydockLease Requested lease. * @return DrydockResource Allocated resource. + * @task resource */ - abstract protected function executeAllocateResource( + abstract public function allocateResource( DrydockBlueprint $blueprint, DrydockLease $lease); - final public function allocateResource( - DrydockBlueprint $blueprint, - DrydockLease $lease) { - - $scope = $this->pushActiveScope(null, $lease); - - $this->log( - pht( - "Blueprint '%s': Allocating Resource for '%s'", - $this->getBlueprintClass(), - $lease->getLeaseName())); - - try { - $resource = $this->executeAllocateResource($blueprint, $lease); - $this->validateAllocatedResource($resource); - } catch (Exception $ex) { - $this->logException($ex); - throw $ex; - } - - return $resource; - } - /* -( Logging )------------------------------------------------------------ */ @@ -454,14 +287,15 @@ abstract class DrydockBlueprintImplementation extends Phobject { return idx(self::getAllBlueprintImplementations(), $class); } - protected function newResourceTemplate($name) { + protected function newResourceTemplate( + DrydockBlueprint $blueprint, + $name) { + $resource = id(new DrydockResource()) - ->setBlueprintPHID($this->getInstance()->getPHID()) - ->setBlueprintClass($this->getBlueprintClass()) + ->setBlueprintPHID($blueprint->getPHID()) ->setType($this->getType()) ->setStatus(DrydockResourceStatus::STATUS_PENDING) - ->setName($name) - ->save(); + ->setName($name); $this->activeResource = $resource; @@ -473,39 +307,6 @@ abstract class DrydockBlueprintImplementation extends Phobject { return $resource; } - /** - * Sanity checks that the blueprint is implemented properly. - */ - private function validateAllocatedResource($resource) { - $blueprint = $this->getBlueprintClass(); - - if (!($resource instanceof DrydockResource)) { - throw new Exception( - pht( - "Blueprint '%s' is not properly implemented: %s must return an ". - "object of type %s or throw, but returned something else.", - $blueprint, - 'executeAllocateResource()', - 'DrydockResource')); - } - - $current_status = $resource->getStatus(); - $req_status = DrydockResourceStatus::STATUS_OPEN; - if ($current_status != $req_status) { - $current_name = DrydockResourceStatus::getNameForStatus($current_status); - $req_name = DrydockResourceStatus::getNameForStatus($req_status); - throw new Exception( - pht( - "Blueprint '%s' is not properly implemented: %s must return a %s ". - "with status '%s', but returned one with status '%s'.", - $blueprint, - 'executeAllocateResource()', - 'DrydockResource', - $req_name, - $current_name)); - } - } - private function pushActiveScope( DrydockResource $resource = null, DrydockLease $lease = null) { diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index b85a5dde9f..86207bb73f 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -35,7 +35,7 @@ final class DrydockWorkingCopyBlueprintImplementation return true; } - public function canAllocateLeaseOnResource( + public function canAcquireLeaseOnResource( DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { @@ -47,15 +47,7 @@ final class DrydockWorkingCopyBlueprintImplementation return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo)); } - protected function shouldAllocateLease( - DrydockResource $resource, - DrydockLease $lease, - array $other_leases) { - // TODO: These checks are out of date. - return !$other_leases; - } - - protected function executeAllocateResource( + public function allocateResource( DrydockBlueprint $blueprint, DrydockLease $lease) { @@ -105,6 +97,7 @@ final class DrydockWorkingCopyBlueprintImplementation $this->log(pht('Complete.')); $resource = $this->newResourceTemplate( + $blueprint, pht( 'Working Copy (%s)', $repository->getCallsign())); @@ -117,7 +110,8 @@ final class DrydockWorkingCopyBlueprintImplementation return $resource; } - protected function executeAcquireLease( + public function acquireLease( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { return; diff --git a/src/applications/drydock/constants/DrydockLeaseStatus.php b/src/applications/drydock/constants/DrydockLeaseStatus.php index 847db52cbb..645c1c1708 100644 --- a/src/applications/drydock/constants/DrydockLeaseStatus.php +++ b/src/applications/drydock/constants/DrydockLeaseStatus.php @@ -3,7 +3,7 @@ final class DrydockLeaseStatus extends DrydockConstants { const STATUS_PENDING = 0; - const STATUS_ACQUIRING = 5; + const STATUS_ACQUIRED = 5; const STATUS_ACTIVE = 1; const STATUS_RELEASED = 2; const STATUS_BROKEN = 3; @@ -12,7 +12,7 @@ final class DrydockLeaseStatus extends DrydockConstants { public static function getNameForStatus($status) { $map = array( self::STATUS_PENDING => pht('Pending'), - self::STATUS_ACQUIRING => pht('Acquiring'), + self::STATUS_ACQUIRED => pht('Acquired'), self::STATUS_ACTIVE => pht('Active'), self::STATUS_RELEASED => pht('Released'), self::STATUS_BROKEN => pht('Broken'), @@ -25,7 +25,7 @@ final class DrydockLeaseStatus extends DrydockConstants { public static function getAllStatuses() { return array( self::STATUS_PENDING, - self::STATUS_ACQUIRING, + self::STATUS_ACQUIRED, self::STATUS_ACTIVE, self::STATUS_RELEASED, self::STATUS_BROKEN, diff --git a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php index 3f4a2acc67..4317242c10 100644 --- a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php +++ b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php @@ -40,4 +40,8 @@ final class DrydockBlueprintCoreCustomField return; } + public function getBlueprintFieldValue() { + return $this->getProxy()->getFieldValue(); + } + } diff --git a/src/applications/drydock/customfield/DrydockBlueprintCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCustomField.php index 37d27ccdd6..700131e753 100644 --- a/src/applications/drydock/customfield/DrydockBlueprintCustomField.php +++ b/src/applications/drydock/customfield/DrydockBlueprintCustomField.php @@ -1,4 +1,8 @@ setName('create-resource') - ->setSynopsis(pht('Create a resource manually.')) - ->setArguments( - array( - array( - 'name' => 'name', - 'param' => 'resource_name', - 'help' => pht('Resource name.'), - ), - array( - 'name' => 'blueprint', - 'param' => 'blueprint_id', - 'help' => pht('Blueprint ID.'), - ), - array( - 'name' => 'attributes', - 'param' => 'name=value,...', - 'help' => pht('Resource attributes.'), - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $resource_name = $args->getArg('name'); - if (!$resource_name) { - throw new PhutilArgumentUsageException( - pht( - 'Specify a resource name with `%s`.', - '--name')); - } - - $blueprint_id = $args->getArg('blueprint'); - if (!$blueprint_id) { - throw new PhutilArgumentUsageException( - pht( - 'Specify a blueprint ID with `%s`.', - '--blueprint')); - } - - $attributes = $args->getArg('attributes'); - if ($attributes) { - $options = new PhutilSimpleOptions(); - $options->setCaseSensitive(true); - $attributes = $options->parse($attributes); - } - - $viewer = $this->getViewer(); - - $blueprint = id(new DrydockBlueprintQuery()) - ->setViewer($viewer) - ->withIDs(array($blueprint_id)) - ->executeOne(); - if (!$blueprint) { - throw new PhutilArgumentUsageException( - pht('Specified blueprint does not exist.')); - } - - $resource = id(new DrydockResource()) - ->setBlueprintPHID($blueprint->getPHID()) - ->setType($blueprint->getImplementation()->getType()) - ->setName($resource_name) - ->setStatus(DrydockResourceStatus::STATUS_OPEN); - if ($attributes) { - $resource->setAttributes($attributes); - } - $resource->save(); - - $console->writeOut("%s\n", pht('Created Resource %s', $resource->getID())); - return 0; - } - -} diff --git a/src/applications/drydock/query/DrydockLeaseSearchEngine.php b/src/applications/drydock/query/DrydockLeaseSearchEngine.php index 66f783875e..eb5338b2cb 100644 --- a/src/applications/drydock/query/DrydockLeaseSearchEngine.php +++ b/src/applications/drydock/query/DrydockLeaseSearchEngine.php @@ -74,7 +74,7 @@ final class DrydockLeaseSearchEngine 'statuses', array( DrydockLeaseStatus::STATUS_PENDING, - DrydockLeaseStatus::STATUS_ACQUIRING, + DrydockLeaseStatus::STATUS_ACQUIRED, DrydockLeaseStatus::STATUS_ACTIVE, )); case 'all': diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 132fb7fdc0..cb039bd7f9 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -1,5 +1,9 @@ loadCustomFields(); + + $field = idx($fields, $key); + if (!$field) { + throw new Exception( + pht( + 'Unknown blueprint field "%s"!', + $key)); + } + + return $field->getBlueprintFieldValue(); + } + + private function loadCustomFields() { + if ($this->fields === null) { + $field_list = PhabricatorCustomField::getObjectFields( + $this, + PhabricatorCustomField::ROLE_VIEW); + $field_list->readFieldsFromStorage($this); + + $this->fields = $field_list->getFields(); + } + return $this->fields; + } + + +/* -( Allocating Resources )----------------------------------------------- */ + + + /** + * @task resource + */ public function canEverAllocateResourceForLease(DrydockLease $lease) { return $this->getImplementation()->canEverAllocateResourceForLease( $this, $lease); } + + /** + * @task resource + */ public function canAllocateResourceForLease(DrydockLease $lease) { return $this->getImplementation()->canAllocateResourceForLease( $this, $lease); } - public function canAllocateLeaseOnResource( + + /** + * @task resource + */ + public function allocateResource(DrydockLease $lease) { + return $this->getImplementation()->allocateResource( + $this, + $lease); + } + + +/* -( Acquiring Leases )--------------------------------------------------- */ + + + /** + * @task lease + */ + public function canAcquireLeaseOnResource( DrydockResource $resource, DrydockLease $lease) { - return $this->getImplementation()->canAllocateLeaseOnResource( + return $this->getImplementation()->canAcquireLeaseOnResource( $this, $resource, $lease); } + + /** + * @task lease + */ + public function acquireLease( + DrydockResource $resource, + DrydockLease $lease) { + return $this->getImplementation()->acquireLease( + $this, + $resource, + $lease); + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 39fa59330c..1401b52287 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -13,6 +13,8 @@ final class DrydockLease extends DrydockDAO private $resource = self::ATTACHABLE; private $releaseOnDestruction; + private $isAcquired = false; + private $activateWhenAcquired = false; /** * Flag this lease to be released when its destructor is called. This is @@ -133,8 +135,8 @@ final class DrydockLease extends DrydockDAO public function isActive() { switch ($this->status) { + case DrydockLeaseStatus::STATUS_ACQUIRED: case DrydockLeaseStatus::STATUS_ACTIVE: - case DrydockLeaseStatus::STATUS_ACQUIRING: return true; } return false; @@ -171,7 +173,7 @@ final class DrydockLease extends DrydockDAO case DrydockLeaseStatus::STATUS_BROKEN: throw new Exception(pht('Lease has been broken!')); case DrydockLeaseStatus::STATUS_PENDING: - case DrydockLeaseStatus::STATUS_ACQUIRING: + case DrydockLeaseStatus::STATUS_ACQUIRED: break; default: throw new Exception(pht('Unknown status??')); @@ -199,6 +201,53 @@ final class DrydockLease extends DrydockDAO return $this; } + public function setActivateWhenAcquired($activate) { + $this->activateWhenAcquired = true; + return $this; + } + + public function acquireOnResource(DrydockResource $resource) { + $expect_status = DrydockLeaseStatus::STATUS_PENDING; + $actual_status = $this->getStatus(); + if ($actual_status != $expect_status) { + throw new Exception( + pht( + 'Trying to acquire a lease on a resource which is in the wrong '. + 'state: status must be "%s", actually "%s".', + $expect_status, + $actual_status)); + } + + if ($this->activateWhenAcquired) { + $new_status = DrydockLeaseStatus::STATUS_ACTIVE; + } else { + $new_status = DrydockLeaseStatus::STATUS_PENDING; + } + + if ($new_status === DrydockLeaseStatus::STATUS_ACTIVE) { + if ($resource->getStatus() === DrydockResourceStatus::STATUS_PENDING) { + throw new Exception( + pht( + 'Trying to acquire an active lease on a pending resource. '. + 'You can not immediately activate leases on resources which '. + 'need time to start up.')); + } + } + + $this + ->setResourceID($resource->getID()) + ->setStatus($new_status) + ->save(); + + $this->isAcquired = true; + + return $this; + } + + public function isAcquiredLease() { + return $this->isAcquired; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 87638d1ae0..8d99cba154 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -15,6 +15,8 @@ final class DrydockResource extends DrydockDAO protected $ownerPHID; private $blueprint = self::ATTACHABLE; + private $isAllocated = false; + private $activateWhenAllocated = false; protected function getConfiguration() { return array( @@ -73,10 +75,47 @@ final class DrydockResource extends DrydockDAO return $this; } - public function canAllocateLease(DrydockLease $lease) { - return $this->getBlueprint()->canAllocateLeaseOnResource( - $this, - $lease); + public function setActivateWhenAllocated($activate) { + $this->activateWhenAllocated = $activate; + return $this; + } + + public function allocateResource($status) { + if ($this->getID()) { + throw new Exception( + pht( + 'Trying to allocate a resource which has already been persisted. '. + 'Only new resources may be allocated.')); + } + + $expect_status = DrydockResourceStatus::STATUS_PENDING; + $actual_status = $this->getStatus(); + if ($actual_status != $expect_status) { + throw new Exception( + pht( + 'Trying to allocate a resource from the wrong status. Status must '. + 'be "%s", actually "%s".', + $expect_status, + $actual_status)); + } + + if ($this->activateWhenAllocated) { + $new_status = DrydockResourceStatus::STATUS_OPEN; + } else { + $new_status = DrydockResourceStatus::STATUS_PENDING; + } + + $this + ->setStatus($new_status) + ->save(); + + $this->didAllocate = true; + + return $this; + } + + public function isAllocatedResource() { + return $this->isAllocated; } public function closeResource() { diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index 1898bcaf66..8b26ef3571 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -1,5 +1,10 @@ loadLease(); - $this->allocateLease($lease); + $this->allocateAndAcquireLease($lease); } - private function allocateLease(DrydockLease $lease) { + +/* -( Allocator )---------------------------------------------------------- */ + + + /** + * Find or build a resource which can satisfy a given lease request, then + * acquire the lease. + * + * @param DrydockLease Requested lease. + * @return void + * @task allocator + */ + private function allocateAndAcquireLease(DrydockLease $lease) { $blueprints = $this->loadBlueprintsForAllocatingLease($lease); // If we get nothing back, that means no blueprint is defined which can @@ -72,7 +89,8 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { $exceptions = array(); foreach ($usable_blueprints as $blueprint) { try { - $resources[] = $blueprint->allocateResource($lease); + $resources[] = $this->allocateResource($blueprint, $lease); + // Bail after allocating one resource, we don't need any more than // this. break; @@ -106,7 +124,7 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { $allocated = false; foreach ($resources as $resource) { try { - $blueprint->allocateLease($resource, $lease); + $this->acquireLease($resource, $lease); $allocated = true; break; } catch (Exception $ex) { @@ -129,88 +147,54 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { /** - * Load a list of all resources which a given lease can possibly be - * allocated against. + * Get all the @{class:DrydockBlueprintImplementation}s which can possibly + * build a resource to satisfy a lease. + * + * This method returns blueprints which might, at some time, be able to + * build a resource which can satisfy the lease. They may not be able to + * build that resource right now. * - * @param list Blueprints which may produce suitable - * resources. * @param DrydockLease Requested lease. - * @return list Resources which may be able to allocate - * the lease. + * @return list List of qualifying blueprint + * implementations. + * @task allocator */ - private function loadResourcesForAllocatingLease( - array $blueprints, + private function loadBlueprintImplementationsForAllocatingLease( DrydockLease $lease) { - assert_instances_of($blueprints, 'DrydockBlueprint'); - $viewer = $this->getViewer(); - $resources = id(new DrydockResourceQuery()) - ->setViewer($viewer) - ->withBlueprintPHIDs(mpull($blueprints, 'getPHID')) - ->withTypes(array($lease->getResourceType())) - ->withStatuses( - array( - DrydockResourceStatus::STATUS_PENDING, - DrydockResourceStatus::STATUS_OPEN, - )) - ->execute(); + $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); $keep = array(); - foreach ($resources as $key => $resource) { - if (!$resource->canAllocateLease($lease)) { + foreach ($impls as $key => $impl) { + // Don't use disabled blueprint types. + if (!$impl->isEnabled()) { continue; } - $keep[$key] = $resource; + // Don't use blueprint types which can't allocate the correct kind of + // resource. + if ($impl->getType() != $lease->getResourceType()) { + continue; + } + + if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { + continue; + } + + $keep[$key] = $impl; } return $keep; } - /** - * Rank blueprints by suitability for building a new resource for a - * particular lease. - * - * @param list List of blueprints. - * @param DrydockLease Requested lease. - * @return list Ranked list of blueprints. - */ - private function rankBlueprints(array $blueprints, DrydockLease $lease) { - assert_instances_of($blueprints, 'DrydockBlueprint'); - - // TODO: Implement improvements to this ranking algorithm if they become - // available. - shuffle($blueprints); - - return $blueprints; - } - - - /** - * Rank resources by suitability for allocating a particular lease. - * - * @param list List of resources. - * @param DrydockLease Requested lease. - * @return list Ranked list of resources. - */ - private function rankResources(array $resources, DrydockLease $lease) { - assert_instances_of($resources, 'DrydockResource'); - - // TODO: Implement improvements to this ranking algorithm if they become - // available. - shuffle($resources); - - return $resources; - } - - /** * Get all the concrete @{class:DrydockBlueprint}s which can possibly * build a resource to satisfy a lease. * * @param DrydockLease Requested lease. * @return list List of qualifying blueprints. + * @task allocator */ private function loadBlueprintsForAllocatingLease( DrydockLease $lease) { @@ -243,40 +227,42 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { /** - * Get all the @{class:DrydockBlueprintImplementation}s which can possibly - * build a resource to satisfy a lease. - * - * This method returns blueprints which might, at some time, be able to - * build a resource which can satisfy the lease. They may not be able to - * build that resource right now. + * Load a list of all resources which a given lease can possibly be + * allocated against. * + * @param list Blueprints which may produce suitable + * resources. * @param DrydockLease Requested lease. - * @return list List of qualifying blueprint - * implementations. + * @return list Resources which may be able to allocate + * the lease. + * @task allocator */ - private function loadBlueprintImplementationsForAllocatingLease( + private function loadResourcesForAllocatingLease( + array $blueprints, DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + $viewer = $this->getViewer(); - $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withBlueprintPHIDs(mpull($blueprints, 'getPHID')) + ->withTypes(array($lease->getResourceType())) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_PENDING, + DrydockResourceStatus::STATUS_OPEN, + )) + ->execute(); $keep = array(); - foreach ($impls as $key => $impl) { - // Don't use disabled blueprint types. - if (!$impl->isEnabled()) { + foreach ($resources as $key => $resource) { + $blueprint = $resource->getBlueprint(); + + if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) { continue; } - // Don't use blueprint types which can't allocate the correct kind of - // resource. - if ($impl->getType() != $lease->getResourceType()) { - continue; - } - - if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { - continue; - } - - $keep[$key] = $impl; + $keep[$key] = $resource; } return $keep; @@ -288,8 +274,9 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { * a lease from a list of blueprints. * * @param list List of blueprints. - * @param list List with fully allocated blueprints - * removed. + * @return list List with blueprints that can not allocate + * a resource for the lease right now removed. + * @task allocator */ private function removeOverallocatedBlueprints( array $blueprints, @@ -308,4 +295,182 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { return $keep; } + + /** + * Rank blueprints by suitability for building a new resource for a + * particular lease. + * + * @param list List of blueprints. + * @param DrydockLease Requested lease. + * @return list Ranked list of blueprints. + * @task allocator + */ + private function rankBlueprints(array $blueprints, DrydockLease $lease) { + assert_instances_of($blueprints, 'DrydockBlueprint'); + + // TODO: Implement improvements to this ranking algorithm if they become + // available. + shuffle($blueprints); + + return $blueprints; + } + + + /** + * Rank resources by suitability for allocating a particular lease. + * + * @param list List of resources. + * @param DrydockLease Requested lease. + * @return list Ranked list of resources. + * @task allocator + */ + private function rankResources(array $resources, DrydockLease $lease) { + assert_instances_of($resources, 'DrydockResource'); + + // TODO: Implement improvements to this ranking algorithm if they become + // available. + shuffle($resources); + + return $resources; + } + + +/* -( Managing Resources )------------------------------------------------- */ + + + /** + * Perform an actual resource allocation with a particular blueprint. + * + * @param DrydockBlueprint The blueprint to allocate a resource from. + * @param DrydockLease Requested lease. + * @return DrydockResource Allocated resource. + * @task resource + */ + private function allocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + $resource = $blueprint->allocateResource($lease); + $this->validateAllocatedResource($resource); + return $resource; + } + + + /** + * Check that the resource a blueprint allocated is roughly the sort of + * object we expect. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param wild Thing which the blueprint claims is a valid resource. + * @param DrydockLease Lease the resource was allocated for. + * @return void + * @task resource + */ + private function validateAllocatedResource( + DrydockBlueprint $blueprint, + $resource, + DrydockLease $lease) { + $blueprint = $this->getBlueprintClass(); + + if (!($resource instanceof DrydockResource)) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: %s must '. + 'return an object of type %s or throw, but returned something else.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'allocateResource()', + 'DrydockResource')); + } + + if (!$resource->isAllocatedResource()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. + 'must actually allocate the resource it returns.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'allocateResource()')); + } + + $resource_type = $resource->getType(); + $lease_type = $lease->getResourceType(); + + if ($resource_type !== $lease_type) { + // TODO: Destroy the resource here? + + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'built a resource of type "%s" to satisfy a lease requesting a '. + 'resource of type "%s".', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + $resource_type, + $lease_type)); + } + } + + +/* -( Managing Leases )---------------------------------------------------- */ + + + /** + * Perform an actual lease acquisition on a particular resource. + * + * @param DrydockResource Resource to acquire a lease on. + * @param DrydockLease Lease to acquire. + * @return void + * @task lease + */ + private function acquireLease( + DrydockResource $resource, + DrydockLease $lease) { + + $blueprint = $resource->getBlueprint(); + $blueprint->acquireLease($resource, $lease); + + $this->validateAcquiredLease($blueprint, $resource, $lease); + } + + + /** + * Make sure that a lease was really acquired properly. + * + * @param DrydockBlueprint Blueprint which created the resource. + * @param DrydockResource Resource which was acquired. + * @param DrydockLease The lease which was supposedly acquired. + * @return void + * @task lease + */ + private function validateAcquiredLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + if (!$lease->isAcquiredLease()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'returned from "%s" without acquiring a lease.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'acquireLease()')); + } + + $lease_id = $lease->getResourceID(); + $resource_id = $resource->getID(); + + if ($lease_id !== $resource_id) { + // TODO: Destroy the lease? + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'returned from "%s" with a lease acquired on the wrong resource.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'acquireLease()')); + } + } + + } diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index 8a2a8da70f..89fe783fac 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -31,7 +31,7 @@ final class PassphraseCredentialEditController extends PassphraseController { throw new Exception( pht( 'Credential has noncreateable type "%s"!', - $credential->getCredentialType())); + $type_const)); } $credential = PassphraseCredential::initializeNewCredential($viewer) From 3ac99006bfd5998f916faa3c413fc7676c0bc018 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:45:25 -0700 Subject: [PATCH 10/43] Implement optimistic "slot locks" in Drydock Summary: See discussion in D10304. There's a lot of context there, but the general idea is: - Blueprints should manage locks in a granular way during the actual allocation/acquisition phase. - Optimistic "slot locks" might a pretty good primitive to make that easy to implement and reason about in most cases. The way these locks work is that you just pick some name for the lock (like the PHID of a resource) and say that it needs to be acquired for the allocation/acquisition to work: ``` ... ->needSlotLock("mylock(PHID-XYZQ-...)") ... ``` When you fire off the acquisition or allocation, it fails unless it could acquire the slot with that name. This is really simple (no explicit lock management) and a pretty good fit for most of the locking that blueprints and leases need to do. If you need to do limit-based locks (e.g., maximum of 3 locks) you could acquire a lock like this: ``` mylock(whatever).slot(2) ``` Blueprints generally only contend with themselves, so it's normally OK for them to pick whatever strategy works best for them in naming locks. This may not work as well if you have a huge number of slots (e.g., 100TB you want to give out in 1MB chunks), or other complex needs for locks (like you have to synchronize access to some external resource), but slot locks don't need to be the only mechanism that blueprints use. If they run into a problem that slot locks aren't a good fit for, they can use something else instead. For now, slot locks seem like a good fit for the problems we currently face and most of the problems I anticipate facing. (The release workflows have other race issues which I'm not addressing here. They work fine if nothing races, but aren't race-safe.) Test Plan: To create a race where the same binding is allocated as a resource twice: - Add `sleep(10)` near the beginning of `allocateResource()`, after the free bindings are loaded but before resources are allocated. - (Comment out slot lock acquisition if you have this patch.) - Run `bin/drydock lease ...` in two windows, within 10 seconds of one another. This will reliably double-allocate the binding because both blueprints see a view of the world where the binding is free. To verify the lock works, un-comment it (or apply this patch) and run the same test again. Now, the lock fails in one process and only one resource is allocated. Reviewers: hach-que, chad Reviewed By: hach-que, chad Differential Revision: https://secure.phabricator.com/D14118 --- .../20150916.drydock.slotlocks.1.sql | 8 ++ src/__phutil_library_map__.php | 2 + ...anacServiceHostBlueprintImplementation.php | 20 ++-- .../DrydockBlueprintImplementation.php | 6 + .../drydock/storage/DrydockBlueprint.php | 11 ++ .../drydock/storage/DrydockLease.php | 23 +++- .../drydock/storage/DrydockResource.php | 24 +++- .../drydock/storage/DrydockSlotLock.php | 107 ++++++++++++++++++ .../drydock/worker/DrydockAllocatorWorker.php | 3 +- 9 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 resources/sql/autopatches/20150916.drydock.slotlocks.1.sql create mode 100644 src/applications/drydock/storage/DrydockSlotLock.php diff --git a/resources/sql/autopatches/20150916.drydock.slotlocks.1.sql b/resources/sql/autopatches/20150916.drydock.slotlocks.1.sql new file mode 100644 index 0000000000..837566f1bf --- /dev/null +++ b/resources/sql/autopatches/20150916.drydock.slotlocks.1.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_drydock.drydock_slotlock ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ownerPHID VARBINARY(64) NOT NULL, + lockIndex BINARY(12) NOT NULL, + lockKey LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + UNIQUE KEY `key_lock` (lockIndex), + KEY `key_owner` (ownerPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e43291b393..3a2a76ba6a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -863,6 +863,7 @@ phutil_register_library_map(array( 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', + 'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php', 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', 'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php', @@ -4585,6 +4586,7 @@ phutil_register_library_map(array( 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', + 'DrydockSlotLock' => 'DrydockDAO', 'DrydockWebrootInterface' => 'DrydockInterface', 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', 'FeedConduitAPIMethod' => 'ConduitAPIMethod', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 102f8f07d2..aea46edd71 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -67,14 +67,13 @@ final class DrydockAlmanacServiceHostBlueprintImplementation $device = $binding->getDevice(); $device_name = $device->getName(); + $binding_phid = $binding->getPHID(); + $resource = $this->newResourceTemplate($blueprint, $device_name) ->setActivateWhenAllocated(true) ->setAttribute('almanacServicePHID', $binding->getServicePHID()) - ->setAttribute('almanacBindingPHID', $binding->getPHID()); - - // TODO: This algorithm can race, and the "free" binding may not be - // free by the time we acquire it. Do slot-locking here if that works - // out, or some other kind of locking if it does not. + ->setAttribute('almanacBindingPHID', $binding_phid) + ->needSlotLock("almanac.host.binding({$binding_phid})"); try { return $resource->allocateResource(DrydockResourceStatus::STATUS_OPEN); @@ -93,8 +92,11 @@ final class DrydockAlmanacServiceHostBlueprintImplementation DrydockResource $resource, DrydockLease $lease) { - // TODO: We'll currently lease each resource an unlimited number of times, - // but should stop doing that. + // TODO: The current rule is one lease per resource, and there's no way to + // make that cheaper here than by just trying to acquire the lease below, + // so don't do any special checks for now. When we eventually permit + // multiple leases per host, we'll need to load leases anyway, so we can + // reject fully leased hosts cheaply here. return true; } @@ -104,11 +106,11 @@ final class DrydockAlmanacServiceHostBlueprintImplementation DrydockResource $resource, DrydockLease $lease) { - // TODO: Once we have limit rules, we should perform slot locking (or other - // kinds of locking) here. + $resource_phid = $resource->getPHID(); $lease ->setActivateWhenAcquired(true) + ->needSlotLock("almanac.host.lease({$resource_phid})") ->acquireOnResource($resource); } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 5f769d3f02..3a51f65ad1 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -106,8 +106,12 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockLease $lease); final public function releaseLease( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { + + // TODO: This is all broken nonsense. + $scope = $this->pushActiveScope(null, $lease); $released = false; @@ -117,6 +121,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { $lease->reload(); if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { + $lease->release(); $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); $lease->save(); $released = true; @@ -293,6 +298,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { $resource = id(new DrydockResource()) ->setBlueprintPHID($blueprint->getPHID()) + ->attachBlueprint($blueprint) ->setType($this->getType()) ->setStatus(DrydockResourceStatus::STATUS_PENDING) ->setName($name); diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index cb039bd7f9..21a3933ccc 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -163,6 +163,17 @@ final class DrydockBlueprint extends DrydockDAO } + /** + * @task lease + */ + public function releaseLease( + DrydockResource $resource, + DrydockLease $lease) { + $this->getImplementation()->releaseLease($this, $resource, $lease); + return $this; + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 1401b52287..1b301d93eb 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -15,6 +15,7 @@ final class DrydockLease extends DrydockDAO private $releaseOnDestruction; private $isAcquired = false; private $activateWhenAcquired = false; + private $slotLocks = array(); /** * Flag this lease to be released when its destructor is called. This is @@ -128,6 +129,8 @@ final class DrydockLease extends DrydockDAO $this->setStatus(DrydockLeaseStatus::STATUS_RELEASED); $this->save(); + DrydockSlotLock::releaseLocks($this->getPHID()); + $this->resource = null; return $this; @@ -206,6 +209,11 @@ final class DrydockLease extends DrydockDAO return $this; } + public function needSlotLock($key) { + $this->slotLocks[] = $key; + return $this; + } + public function acquireOnResource(DrydockResource $resource) { $expect_status = DrydockLeaseStatus::STATUS_PENDING; $actual_status = $this->getStatus(); @@ -234,10 +242,17 @@ final class DrydockLease extends DrydockDAO } } - $this - ->setResourceID($resource->getID()) - ->setStatus($new_status) - ->save(); + $this->openTransaction(); + + $this + ->setResourceID($resource->getID()) + ->setStatus($new_status) + ->save(); + + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + + $this->saveTransaction(); $this->isAcquired = true; diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 8d99cba154..4f6f8b83ac 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -17,6 +17,7 @@ final class DrydockResource extends DrydockDAO private $blueprint = self::ATTACHABLE; private $isAllocated = false; private $activateWhenAllocated = false; + private $slotLocks = array(); protected function getConfiguration() { return array( @@ -80,6 +81,11 @@ final class DrydockResource extends DrydockDAO return $this; } + public function needSlotLock($key) { + $this->slotLocks[] = $key; + return $this; + } + public function allocateResource($status) { if ($this->getID()) { throw new Exception( @@ -105,11 +111,18 @@ final class DrydockResource extends DrydockDAO $new_status = DrydockResourceStatus::STATUS_PENDING; } - $this - ->setStatus($new_status) - ->save(); + $this->openTransaction(); - $this->didAllocate = true; + $this + ->setStatus($new_status) + ->save(); + + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + + $this->saveTransaction(); + + $this->isAllocated = true; return $this; } @@ -151,6 +164,9 @@ final class DrydockResource extends DrydockDAO $this->setStatus(DrydockResourceStatus::STATUS_CLOSED); $this->save(); + + DrydockSlotLock::releaseLocks($this->getPHID()); + $this->saveTransaction(); } diff --git a/src/applications/drydock/storage/DrydockSlotLock.php b/src/applications/drydock/storage/DrydockSlotLock.php new file mode 100644 index 0000000000..2d52a81f1e --- /dev/null +++ b/src/applications/drydock/storage/DrydockSlotLock.php @@ -0,0 +1,107 @@ + false, + self::CONFIG_COLUMN_SCHEMA => array( + 'lockIndex' => 'bytes12', + 'lockKey' => 'text', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_lock' => array( + 'columns' => array('lockIndex'), + 'unique' => true, + ), + 'key_owner' => array( + 'columns' => array('ownerPHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public static function loadLocks($owner_phid) { + return id(new DrydockSlotLock())->loadAllWhere( + 'ownerPHID = %s', + $owner_phid); + } + + +/* -( Acquiring and Releasing Locks )-------------------------------------- */ + + + /** + * Acquire a set of slot locks. + * + * This method either acquires all the locks or throws an exception (usually + * because one or more locks are held). + * + * @param phid Lock owner PHID. + * @param list List of locks to acquire. + * @return void + * @task locks + */ + public static function acquireLocks($owner_phid, array $locks) { + if (!$locks) { + return; + } + + $table = new DrydockSlotLock(); + $conn_w = $table->establishConnection('w'); + + $sql = array(); + foreach ($locks as $lock) { + $sql[] = qsprintf( + $conn_w, + '(%s, %s, %s)', + $owner_phid, + PhabricatorHash::digestForIndex($lock), + $lock); + } + + // TODO: These exceptions are pretty tricky to read. It would be good to + // figure out which locks could not be acquired and try to improve the + // exception to make debugging easier. + + queryfx( + $conn_w, + 'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %Q', + $table->getTableName(), + implode(', ', $sql)); + } + + + /** + * Release all locks held by an owner. + * + * @param phid Lock owner PHID. + * @return void + * @task locks + */ + public static function releaseLocks($owner_phid) { + $table = new DrydockSlotLock(); + $conn_w = $table->establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE ownerPHID = %s', + $table->getTableName(), + $owner_phid); + } + +} diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index 8b26ef3571..407910ae1e 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -350,7 +350,7 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { DrydockBlueprint $blueprint, DrydockLease $lease) { $resource = $blueprint->allocateResource($lease); - $this->validateAllocatedResource($resource); + $this->validateAllocatedResource($blueprint, $resource, $lease); return $resource; } @@ -369,7 +369,6 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { DrydockBlueprint $blueprint, $resource, DrydockLease $lease) { - $blueprint = $this->getBlueprintClass(); if (!($resource instanceof DrydockResource)) { throw new Exception( From 9a270efe8a74c72bfd6f033cc9bdfb94b2c658f1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:45:43 -0700 Subject: [PATCH 11/43] Tidy up some Drydock UI Summary: Ref T9253. We had some un-modern use of UI elements, clean that up. Add a tab for showing slot locks so you don't have to fish around in the database. Test Plan: Looked at blueprints, resources and leases. Looked at slot locks. Reviewers: chad, hach-que Reviewed By: chad, hach-que Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14119 --- .../DrydockBlueprintViewController.php | 6 +++- .../drydock/controller/DrydockController.php | 28 +++++++++++++++++++ .../controller/DrydockLeaseViewController.php | 13 +++++++-- .../DrydockResourceViewController.php | 21 ++++++++++---- .../drydock/phid/DrydockBlueprintPHIDType.php | 1 + 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 33a27264b8..5da92539cf 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -61,6 +61,10 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $viewer, $properties); + $resource_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Resources')) + ->setObjectList($resource_list); + $timeline = $this->buildTransactionTimeline( $blueprint, new DrydockBlueprintTransactionQuery()); @@ -70,7 +74,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { array( $crumbs, $object_box, - $resource_list, + $resource_box, $timeline, ), array( diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index c2fa3dae72..3e3d83cc1d 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -8,4 +8,32 @@ abstract class DrydockController extends PhabricatorController { return $this->buildSideNavView()->getMenu(); } + protected function buildLocksTab($owner_phid) { + $locks = DrydockSlotLock::loadLocks($owner_phid); + + $rows = array(); + foreach ($locks as $lock) { + $rows[] = array( + $lock->getID(), + $lock->getLockKey(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No slot locks held.')) + ->setHeaders( + array( + pht('ID'), + pht('Lock Key'), + )) + ->setColumnClasses( + array( + null, + 'wide', + )); + + return id(new PHUIPropertyListView()) + ->addRawContent($table); + } + } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 562e466311..aed1f6416f 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -42,18 +42,25 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $lease_uri); + $locks = $this->buildLocksTab($lease->getPHID()); + $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) - ->addPropertyList($properties); + ->addPropertyList($properties, pht('Properties')) + ->addPropertyList($locks, pht('Slot Locks')); + + $log_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Lease Logs')) + ->setTable($log_table); return $this->buildApplicationPage( array( $crumbs, $object_box, - $log_table, + $log_box, ), array( - 'title' => $title, + 'title' => $title, )); } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index afa0fb49fd..d4a37af33c 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -54,16 +54,27 @@ final class DrydockResourceViewController extends DrydockResourceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); + $locks = $this->buildLocksTab($resource->getPHID()); + $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) - ->addPropertyList($properties); + ->addPropertyList($properties, pht('Properties')) + ->addPropertyList($locks, pht('Slot Locks')); + + $lease_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Leases')) + ->setObjectList($lease_list); + + $log_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Resource Logs')) + ->setTable($log_table); return $this->buildApplicationPage( array( $crumbs, $object_box, - $lease_list, - $log_table, + $lease_box, + $log_box, ), array( 'title' => $title, @@ -95,6 +106,7 @@ final class DrydockResourceViewController extends DrydockResourceController { private function buildPropertyListView( DrydockResource $resource, PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); $view->setActionList($actions); @@ -110,10 +122,9 @@ final class DrydockResourceViewController extends DrydockResourceController { pht('Resource Type'), $resource->getType()); - // TODO: Load handle. $view->addProperty( pht('Blueprint'), - $resource->getBlueprintPHID()); + $viewer->renderHandle($resource->getBlueprintPHID())); $attributes = $resource->getAttributes(); if ($attributes) { diff --git a/src/applications/drydock/phid/DrydockBlueprintPHIDType.php b/src/applications/drydock/phid/DrydockBlueprintPHIDType.php index b61eb396b9..86eeb7f3c5 100644 --- a/src/applications/drydock/phid/DrydockBlueprintPHIDType.php +++ b/src/applications/drydock/phid/DrydockBlueprintPHIDType.php @@ -29,6 +29,7 @@ final class DrydockBlueprintPHIDType extends PhabricatorPHIDType { $blueprint = $objects[$phid]; $id = $blueprint->getID(); + $handle->setName($blueprint->getBlueprintName()); $handle->setURI("/drydock/blueprint/{$id}/"); } } From 6a0eb9d84b5a342d0d959a3369e06d6e6e09f354 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:46:02 -0700 Subject: [PATCH 12/43] Allow AlmanacHost blueprints to build a meaningful CommandInterface Summary: Ref T9253. Provide a meaningful command interface for Almanac hosts. Test Plan: Configued and leased a real host (`sbuild001.phacility.net`) and ran a command on it. ``` $ ./bin/drydock command --lease 90 -- ls / bin boot core dev etc home initrd.img lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var vmlinuz ``` Reviewers: chad, hach-que Reviewed By: chad, hach-que Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14126 --- src/__phutil_library_map__.php | 4 +- ...anacServiceHostBlueprintImplementation.php | 30 +++++++- .../DrydockBlueprintImplementation.php | 59 +++++----------- ...dockWorkingCopyBlueprintImplementation.php | 11 +-- .../drydock/interface/DrydockInterface.php | 6 +- .../command/DrydockCommandInterface.php | 6 +- .../command/DrydockLocalCommandInterface.php | 12 ---- .../command/DrydockSSHCommandInterface.php | 70 ++++++++----------- .../DrydockManagementCommandWorkflow.php | 66 +++++++++++++++++ .../drydock/storage/DrydockBlueprint.php | 18 +++++ 10 files changed, 169 insertions(+), 113 deletions(-) delete mode 100644 src/applications/drydock/interface/command/DrydockLocalCommandInterface.php create mode 100644 src/applications/drydock/management/DrydockManagementCommandWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3a2a76ba6a..a9e43d215e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -838,7 +838,6 @@ phutil_register_library_map(array( 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', - 'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php', @@ -846,6 +845,7 @@ phutil_register_library_map(array( 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', + 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', @@ -4555,7 +4555,6 @@ phutil_register_library_map(array( 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseViewController' => 'DrydockLeaseController', - 'DrydockLocalCommandInterface' => 'DrydockCommandInterface', 'DrydockLog' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', @@ -4566,6 +4565,7 @@ phutil_register_library_map(array( 'DrydockLogQuery' => 'DrydockQuery', 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index aea46edd71..37fe18efab 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -119,11 +119,37 @@ final class DrydockAlmanacServiceHostBlueprintImplementation } public function getInterface( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease, $type) { - // TODO: Actually do stuff here, this needs work and currently makes this - // entire exercise pointless. + + $viewer = PhabricatorUser::getOmnipotentUser(); + + switch ($type) { + case DrydockCommandInterface::INTERFACE_TYPE: + $credential_phid = $blueprint->getFieldValue('credentialPHID'); + $binding_phid = $resource->getAttribute('almanacBindingPHID'); + + $binding = id(new AlmanacBindingQuery()) + ->setViewer($viewer) + ->withPHIDs(array($binding_phid)) + ->executeOne(); + if (!$binding) { + // TODO: This is probably a permanent failure, destroy this resource? + throw new Exception( + pht( + 'Unable to load binding "%s" to create command interface.', + $binding_phid)); + } + + $interface = $binding->getInterface(); + + return id(new DrydockSSHCommandInterface()) + ->setConfig('credentialPHID', $credential_phid) + ->setConfig('host', $interface->getAddress()) + ->setConfig('port', $interface->getPort()); + } } public function getFieldSpecifications() { diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 3a51f65ad1..b3983d250d 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -1,61 +1,23 @@ setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($lease_id)) - ->execute(); - - $lease = idx($query, $lease_id); - - if (!$lease) { - throw new Exception(pht("No such lease '%d'!", $lease_id)); - } - - return $lease; - } - - protected function getInstance() { - if (!$this->instance) { - throw new Exception( - pht('Attach the blueprint instance to the implementation.')); - } - - return $this->instance; - } - - public function attachInstance(DrydockBlueprint $instance) { - $this->instance = $instance; - return $this; - } - public function getFieldSpecifications() { return array(); } @@ -105,6 +67,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockResource $resource, DrydockLease $lease); + final public function releaseLease( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -236,6 +199,16 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockLease $lease); +/* -( Resource Interfaces )------------------------------------------------ */ + + + abstract public function getInterface( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease, + $type); + + /* -( Logging )------------------------------------------------------------ */ @@ -308,7 +281,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { $this->log( pht( "Blueprint '%s': Created New Template", - $this->getBlueprintClass())); + get_class($this))); return $resource; } diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 86207bb73f..34593bba3a 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -122,18 +122,11 @@ final class DrydockWorkingCopyBlueprintImplementation } public function getInterface( + DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease, $type) { - - switch ($type) { - case 'command': - return $this - ->loadLease($resource->getAttribute('lease.host')) - ->getInterface($type); - } - - throw new Exception(pht("No interface of type '%s'.", $type)); + // TODO: This blueprint doesn't work at all. } } diff --git a/src/applications/drydock/interface/DrydockInterface.php b/src/applications/drydock/interface/DrydockInterface.php index 631481f7eb..3b4863e1f4 100644 --- a/src/applications/drydock/interface/DrydockInterface.php +++ b/src/applications/drydock/interface/DrydockInterface.php @@ -2,12 +2,12 @@ abstract class DrydockInterface extends Phobject { - private $config; + private $config = array(); abstract public function getInterfaceType(); - final public function setConfiguration(array $config) { - $this->config = $config; + final public function setConfig($key, $value) { + $this->config[$key] = $value; return $this; } diff --git a/src/applications/drydock/interface/command/DrydockCommandInterface.php b/src/applications/drydock/interface/command/DrydockCommandInterface.php index e2239c1045..8034e0f732 100644 --- a/src/applications/drydock/interface/command/DrydockCommandInterface.php +++ b/src/applications/drydock/interface/command/DrydockCommandInterface.php @@ -2,6 +2,8 @@ abstract class DrydockCommandInterface extends DrydockInterface { + const INTERFACE_TYPE = 'command'; + private $workingDirectory; public function setWorkingDirectory($working_directory) { @@ -14,7 +16,7 @@ abstract class DrydockCommandInterface extends DrydockInterface { } final public function getInterfaceType() { - return 'command'; + return self::INTERFACE_TYPE; } final public function exec($command) { @@ -38,7 +40,7 @@ abstract class DrydockCommandInterface extends DrydockInterface { protected function applyWorkingDirectoryToArgv(array $argv) { if ($this->getWorkingDirectory() !== null) { $cmd = $argv[0]; - $cmd = "(cd %s; {$cmd})"; + $cmd = "(cd %s && {$cmd})"; $argv = array_merge( array($cmd), array($this->getWorkingDirectory()), diff --git a/src/applications/drydock/interface/command/DrydockLocalCommandInterface.php b/src/applications/drydock/interface/command/DrydockLocalCommandInterface.php deleted file mode 100644 index e791214733..0000000000 --- a/src/applications/drydock/interface/command/DrydockLocalCommandInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -applyWorkingDirectoryToArgv($argv); - - return newv('ExecFuture', $argv); - } - -} diff --git a/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php b/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php index d75dcfb780..1aab14b57b 100644 --- a/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php +++ b/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php @@ -2,35 +2,19 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface { - private $passphraseSSHKey; + private $credential; private $connectTimeout; - private function openCredentialsIfNotOpen() { - if ($this->passphraseSSHKey !== null) { - return; + private function loadCredential() { + if ($this->credential === null) { + $credential_phid = $this->getConfig('credentialPHID'); + + $this->credential = PassphraseSSHKey::loadFromPHID( + $credential_phid, + PhabricatorUser::getOmnipotentUser()); } - $credential = id(new PassphraseCredentialQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($this->getConfig('credential'))) - ->needSecrets(true) - ->executeOne(); - - if ($credential === null) { - throw new Exception( - pht( - 'There is no credential with ID %d.', - $this->getConfig('credential'))); - } - - if ($credential->getProvidesType() !== - PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE) { - throw new Exception(pht('Only private key credentials are supported.')); - } - - $this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID( - $credential->getPHID(), - PhabricatorUser::getOmnipotentUser()); + return $this->credential; } public function setConnectTimeout($timeout) { @@ -39,30 +23,36 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface { } public function getExecFuture($command) { - $this->openCredentialsIfNotOpen(); + $credential = $this->loadCredential(); $argv = func_get_args(); $argv = $this->applyWorkingDirectoryToArgv($argv); $full_command = call_user_func_array('csprintf', $argv); - $command_timeout = ''; - if ($this->connectTimeout !== null) { - $command_timeout = csprintf( - '-o %s', - 'ConnectTimeout='.$this->connectTimeout); + $flags = array(); + $flags[] = '-o'; + $flags[] = 'LogLevel=quiet'; + + $flags[] = '-o'; + $flags[] = 'StrictHostKeyChecking=no'; + + $flags[] = '-o'; + $flags[] = 'UserKnownHostsFile=/dev/null'; + + $flags[] = '-o'; + $flags[] = 'BatchMode=yes'; + + if ($this->connectTimeout) { + $flags[] = '-o'; + $flags[] = 'ConnectTimeout='.$this->connectTimeout; } return new ExecFuture( - 'ssh '. - '-o LogLevel=quiet '. - '-o StrictHostKeyChecking=no '. - '-o UserKnownHostsFile=/dev/null '. - '-o BatchMode=yes '. - '%C -p %s -i %P %P@%s -- %s', - $command_timeout, + 'ssh %Ls -l %P -p %s -i %P %s -- %s', + $flags, + $credential->getUsernameEnvelope(), $this->getConfig('port'), - $this->passphraseSSHKey->getKeyfileEnvelope(), - $this->passphraseSSHKey->getUsernameEnvelope(), + $credential->getKeyfileEnvelope(), $this->getConfig('host'), $full_command); } diff --git a/src/applications/drydock/management/DrydockManagementCommandWorkflow.php b/src/applications/drydock/management/DrydockManagementCommandWorkflow.php new file mode 100644 index 0000000000..bc66966f8c --- /dev/null +++ b/src/applications/drydock/management/DrydockManagementCommandWorkflow.php @@ -0,0 +1,66 @@ +setName('command') + ->setSynopsis(pht('Run a command on a leased resource.')) + ->setArguments( + array( + array( + 'name' => 'lease', + 'param' => 'id', + 'help' => pht('Lease ID.'), + ), + array( + 'name' => 'argv', + 'wildcard' => true, + 'help' => pht('Command to execute.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $lease_id = $args->getArg('lease'); + if (!$lease_id) { + throw new PhutilArgumentUsageException( + pht( + 'Use %s to specify a lease.', + '--lease')); + } + + $argv = $args->getArg('argv'); + if (!$argv) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a command to run.')); + } + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($this->getViewer()) + ->withIDs(array($lease_id)) + ->executeOne(); + if (!$lease) { + throw new Exception( + pht( + 'Unable to load lease with ID "%s"!', + $lease_id)); + } + + // TODO: Check lease state, etc. + + $interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE); + + list($stdout, $stderr) = call_user_func_array( + array($interface, 'execx'), + array('%Ls', $argv)); + + fprintf(STDOUT, $stdout); + fprintf(STDERR, $stderr); + + return 0; + } + +} diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 21a3933ccc..9e09ee744c 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -173,6 +173,24 @@ final class DrydockBlueprint extends DrydockDAO return $this; } + public function getInterface( + DrydockResource $resource, + DrydockLease $lease, + $type) { + + $interface = $this->getImplementation() + ->getInterface($this, $resource, $lease, $type); + + if (!$interface) { + throw new Exception( + pht( + 'Unable to build resource interface of type "%s".', + $type)); + } + + return $interface; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From f1119ffcf51d592568281cadb7e31eed9e6f0ad7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 21 Sep 2015 04:46:24 -0700 Subject: [PATCH 13/43] Support working copies and separate allocate + activate steps for resources/leases in Drydock Summary: Ref T9253. For resources and leases that need to do something which takes a lot of time or requires waiting, allow them to allocate/acquire first and then activate later. When we allocate a resource or acquire a lease, the blueprint can either activate it immediately (if all the work can happen quickly/inline) or activate it later. If the blueprint activates it later, we queue a worker to handle activating it. Rebuild the "working copy" blueprint to work with this model: it allocates/acquires and activates in a separate step, once it is able to acquire a host. Test Plan: With some power of imagination, brought up a bunch of working copies with `bin/drydock lease --type working-copy ...` Reviewers: hach-que, chad Reviewed By: hach-que, chad Maniphest Tasks: T9253 Differential Revision: https://secure.phabricator.com/D14127 --- src/__phutil_library_map__.php | 10 +- ...anacServiceHostBlueprintImplementation.php | 19 +- .../DrydockBlueprintImplementation.php | 33 +++ ...dockWorkingCopyBlueprintImplementation.php | 233 +++++++++++++----- .../exception/DrydockSlotLockException.php | 25 ++ .../DrydockManagementLeaseWorkflow.php | 2 - .../drydock/storage/DrydockBlueprint.php | 22 ++ .../drydock/storage/DrydockLease.php | 53 +++- .../drydock/storage/DrydockResource.php | 41 ++- .../drydock/storage/DrydockSlotLock.php | 86 ++++++- .../drydock/worker/DrydockAllocatorWorker.php | 56 +++-- .../drydock/worker/DrydockLeaseWorker.php | 81 ++++++ .../drydock/worker/DrydockResourceWorker.php | 45 ++++ .../drydock/worker/DrydockWorker.php | 39 +++ .../daemon/workers/PhabricatorWorker.php | 33 +-- 15 files changed, 639 insertions(+), 139 deletions(-) create mode 100644 src/applications/drydock/exception/DrydockSlotLockException.php create mode 100644 src/applications/drydock/worker/DrydockLeaseWorker.php create mode 100644 src/applications/drydock/worker/DrydockResourceWorker.php create mode 100644 src/applications/drydock/worker/DrydockWorker.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a9e43d215e..747e085023 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -838,6 +838,7 @@ phutil_register_library_map(array( 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', + 'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php', @@ -861,10 +862,13 @@ phutil_register_library_map(array( 'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php', 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', + 'DrydockResourceWorker' => 'applications/drydock/worker/DrydockResourceWorker.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', 'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php', + 'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php', 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', + 'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php', 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', 'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php', 'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php', @@ -4502,7 +4506,7 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', - 'DrydockAllocatorWorker' => 'PhabricatorWorker', + 'DrydockAllocatorWorker' => 'DrydockWorker', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 'DrydockBlueprint' => array( @@ -4555,6 +4559,7 @@ phutil_register_library_map(array( 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseViewController' => 'DrydockLeaseController', + 'DrydockLeaseWorker' => 'DrydockWorker', 'DrydockLog' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', @@ -4584,10 +4589,13 @@ phutil_register_library_map(array( 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockResourceStatus' => 'DrydockConstants', 'DrydockResourceViewController' => 'DrydockResourceController', + 'DrydockResourceWorker' => 'DrydockWorker', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', 'DrydockSlotLock' => 'DrydockDAO', + 'DrydockSlotLockException' => 'Exception', 'DrydockWebrootInterface' => 'DrydockInterface', + 'DrydockWorker' => 'PhabricatorWorker', 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', 'FeedConduitAPIMethod' => 'ConduitAPIMethod', 'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 37fe18efab..64c002bdb0 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -76,7 +76,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation ->needSlotLock("almanac.host.binding({$binding_phid})"); try { - return $resource->allocateResource(DrydockResourceStatus::STATUS_OPEN); + return $resource->allocateResource(); } catch (Exception $ex) { $exceptions[] = $ex; } @@ -92,11 +92,9 @@ final class DrydockAlmanacServiceHostBlueprintImplementation DrydockResource $resource, DrydockLease $lease) { - // TODO: The current rule is one lease per resource, and there's no way to - // make that cheaper here than by just trying to acquire the lease below, - // so don't do any special checks for now. When we eventually permit - // multiple leases per host, we'll need to load leases anyway, so we can - // reject fully leased hosts cheaply here. + if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) { + return false; + } return true; } @@ -106,14 +104,17 @@ final class DrydockAlmanacServiceHostBlueprintImplementation DrydockResource $resource, DrydockLease $lease) { - $resource_phid = $resource->getPHID(); - $lease ->setActivateWhenAcquired(true) - ->needSlotLock("almanac.host.lease({$resource_phid})") + ->needSlotLock($this->getLeaseSlotLock($resource)) ->acquireOnResource($resource); } + private function getLeaseSlotLock(DrydockResource $resource) { + $resource_phid = $resource->getPHID(); + return "almanac.host.lease({$resource_phid})"; + } + public function getType() { return 'host'; } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index b3983d250d..d5085002eb 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -67,6 +67,12 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockResource $resource, DrydockLease $lease); + public function activateLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + throw new PhutilMethodNotImplementedException(); + } final public function releaseLease( DrydockBlueprint $blueprint, @@ -198,6 +204,11 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockBlueprint $blueprint, DrydockLease $lease); + public function activateResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + throw new PhutilMethodNotImplementedException(); + } /* -( Resource Interfaces )------------------------------------------------ */ @@ -276,6 +287,9 @@ abstract class DrydockBlueprintImplementation extends Phobject { ->setStatus(DrydockResourceStatus::STATUS_PENDING) ->setName($name); + // Pre-allocate the resource PHID. + $resource->setPHID($resource->generatePHID()); + $this->activeResource = $resource; $this->log( @@ -286,6 +300,25 @@ abstract class DrydockBlueprintImplementation extends Phobject { return $resource; } + protected function newLease(DrydockBlueprint $blueprint) { + return id(new DrydockLease()); + } + + protected function requireActiveLease(DrydockLease $lease) { + $lease_status = $lease->getStatus(); + + switch ($lease_status) { + case DrydockLeaseStatus::STATUS_ACQUIRED: + // TODO: Temporary failure. + throw new Exception(pht('Lease still activating.')); + case DrydockLeaseStatus::STATUS_ACTIVE: + return; + default: + // TODO: Permanent failure. + throw new Exception(pht('Lease in bad state.')); + } + } + private function pushActiveScope( DrydockResource $resource = null, DrydockLease $lease = null) { diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 34593bba3a..6fdc787274 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -17,21 +17,18 @@ final class DrydockWorkingCopyBlueprintImplementation public function canAnyBlueprintEverAllocateResourceForLease( DrydockLease $lease) { - // TODO: These checks are out of date. return true; } public function canEverAllocateResourceForLease( DrydockBlueprint $blueprint, DrydockLease $lease) { - // TODO: These checks are out of date. return true; } public function canAllocateResourceForLease( DrydockBlueprint $blueprint, DrydockLease $lease) { - // TODO: These checks are out of date. return true; } @@ -39,82 +36,130 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { - // TODO: These checks are out of date. - $resource_repo = $resource->getAttribute('repositoryID'); - $lease_repo = $lease->getAttribute('repositoryID'); + $have_phid = $resource->getAttribute('repositoryPHID'); + $need_phid = $lease->getAttribute('repositoryPHID'); - return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo)); - } - - public function allocateResource( - DrydockBlueprint $blueprint, - DrydockLease $lease) { - - $repository_id = $lease->getAttribute('repositoryID'); - if (!$repository_id) { - throw new Exception( - pht( - "Lease is missing required '%s' attribute.", - 'repositoryID')); + if ($need_phid !== $have_phid) { + return false; } - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($repository_id)) - ->executeOne(); - - if (!$repository) { - throw new Exception( - pht( - "Repository '%s' does not exist!", - $repository_id)); + if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) { + return false; } - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - break; - default: - throw new Exception(pht('Unsupported VCS!')); - } - - // TODO: Policy stuff here too. - $host_lease = id(new DrydockLease()) - ->setResourceType('host') - ->waitUntilActive(); - - $path = $host_lease->getAttribute('path').$repository->getCallsign(); - - $this->log( - pht('Cloning %s into %s....', $repository->getCallsign(), $path)); - - $cmd = $host_lease->getInterface('command'); - $cmd->execx( - 'git clone --origin origin %P %s', - $repository->getRemoteURIEnvelope(), - $path); - - $this->log(pht('Complete.')); - - $resource = $this->newResourceTemplate( - $blueprint, - pht( - 'Working Copy (%s)', - $repository->getCallsign())); - $resource->setStatus(DrydockResourceStatus::STATUS_OPEN); - $resource->setAttribute('lease.host', $host_lease->getID()); - $resource->setAttribute('path', $path); - $resource->setAttribute('repositoryID', $repository->getID()); - $resource->save(); - - return $resource; + return true; } public function acquireLease( DrydockBlueprint $blueprint, DrydockResource $resource, DrydockLease $lease) { - return; + + $lease + ->needSlotLock($this->getLeaseSlotLock($resource)) + ->acquireOnResource($resource); + } + + private function getLeaseSlotLock(DrydockResource $resource) { + $resource_phid = $resource->getPHID(); + return "workingcopy.lease({$resource_phid})"; + } + + public function allocateResource( + DrydockBlueprint $blueprint, + DrydockLease $lease) { + + $repository_phid = $lease->getAttribute('repositoryPHID'); + $repository = $this->loadRepository($repository_phid); + + $resource = $this->newResourceTemplate( + $blueprint, + pht( + 'Working Copy (%s)', + $repository->getCallsign())); + + $resource_phid = $resource->getPHID(); + + $host_lease = $this->newLease($blueprint) + ->setResourceType('host') + ->setOwnerPHID($resource_phid) + ->setAttribute('workingcopy.resourcePHID', $resource_phid) + ->queueForActivation(); + + // TODO: Add some limits to the number of working copies we can have at + // once? + + return $resource + ->setAttribute('repositoryPHID', $repository->getPHID()) + ->setAttribute('host.leasePHID', $host_lease->getPHID()) + ->allocateResource(); + } + + public function activateResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + + $lease = $this->loadHostLease($resource); + $this->requireActiveLease($lease); + + $repository_phid = $resource->getAttribute('repositoryPHID'); + $repository = $this->loadRepository($repository_phid); + $repository_id = $repository->getID(); + + $command_type = DrydockCommandInterface::INTERFACE_TYPE; + $interface = $lease->getInterface($command_type); + + // TODO: Make this configurable. + $resource_id = $resource->getID(); + $root = "/var/drydock/workingcopy-{$resource_id}"; + $path = "{$root}/repo/{$repository_id}/"; + + $interface->execx( + 'git clone -- %s %s', + (string)$repository->getCloneURIObject(), + $path); + + $resource + ->setAttribute('workingcopy.root', $root) + ->setAttribute('workingcopy.path', $path) + ->activateResource(); + } + + public function activateLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + $command_type = DrydockCommandInterface::INTERFACE_TYPE; + $interface = $lease->getInterface($command_type); + + $cmd = array(); + $arg = array(); + + $cmd[] = 'git clean -d --force'; + $cmd[] = 'git reset --hard HEAD'; + $cmd[] = 'git fetch'; + + $commit = $lease->getAttribute('commit'); + $branch = $lease->getAttribute('branch'); + + if ($commit !== null) { + $cmd[] = 'git reset --hard %s'; + $arg[] = $commit; + } else if ($branch !== null) { + $cmd[] = 'git reset --hard %s'; + $arg[] = $branch; + } + + $cmd = implode(' && ', $cmd); + $argv = array_merge(array($cmd), $arg); + + $result = call_user_func_array( + array($interface, 'execx'), + $argv); + + $lease->activateOnResource($resource); } public function getType() { @@ -126,7 +171,59 @@ final class DrydockWorkingCopyBlueprintImplementation DrydockResource $resource, DrydockLease $lease, $type) { - // TODO: This blueprint doesn't work at all. + + switch ($type) { + case DrydockCommandInterface::INTERFACE_TYPE: + $host_lease = $this->loadHostLease($resource); + $command_interface = $host_lease->getInterface($type); + + $path = $resource->getAttribute('workingcopy.path'); + $command_interface->setWorkingDirectory($path); + + return $command_interface; + } } + private function loadRepository($repository_phid) { + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($repository_phid)) + ->executeOne(); + if (!$repository) { + // TODO: Permanent failure. + throw new Exception( + pht( + 'Repository PHID "%s" does not exist.', + $repository_phid)); + } + + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + break; + default: + // TODO: Permanent failure. + throw new Exception(pht('Unsupported VCS!')); + } + + return $repository; + } + + private function loadHostLease(DrydockResource $resource) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $lease_phid = $resource->getAttribute('host.leasePHID'); + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withPHIDs(array($lease_phid)) + ->executeOne(); + if (!$lease) { + // TODO: Permanent failure. + throw new Exception(pht('Unable to load lease "%s".', $lease_phid)); + } + + return $lease; + } + + } diff --git a/src/applications/drydock/exception/DrydockSlotLockException.php b/src/applications/drydock/exception/DrydockSlotLockException.php new file mode 100644 index 0000000000..2bcf5f8cd7 --- /dev/null +++ b/src/applications/drydock/exception/DrydockSlotLockException.php @@ -0,0 +1,25 @@ +lockMap = $locks; + + if ($locks) { + $lock_list = array(); + foreach ($locks as $lock => $owner_phid) { + $lock_list[] = pht('"%s" (owned by "%s")', $lock, $owner_phid); + } + $message = pht( + 'Unable to acquire slot locks: %s.', + implode(', ', $lock_list)); + } else { + $message = pht('Unable to acquire slot locks.'); + } + + parent::__construct($message); + } + +} diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 238177ae62..f3617f9b44 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -40,8 +40,6 @@ final class DrydockManagementLeaseWorkflow $attributes = $options->parse($attributes); } - PhabricatorWorker::setRunAllTasksInProcess(true); - $lease = id(new DrydockLease()) ->setResourceType($resource_type); if ($attributes) { diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 9e09ee744c..0907948d14 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -134,6 +134,15 @@ final class DrydockBlueprint extends DrydockDAO } + /** + * @task resource + */ + public function activateResource(DrydockResource $resource) { + return $this->getImplementation()->activateResource( + $this, + $resource); + } + /* -( Acquiring Leases )--------------------------------------------------- */ @@ -163,6 +172,19 @@ final class DrydockBlueprint extends DrydockDAO } + /** + * @task lease + */ + public function activateLease( + DrydockResource $resource, + DrydockLease $lease) { + return $this->getImplementation()->activateLease( + $this, + $resource, + $lease); + } + + /** * @task lease */ diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 1b301d93eb..475d209ea0 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -14,6 +14,7 @@ final class DrydockLease extends DrydockDAO private $resource = self::ATTACHABLE; private $releaseOnDestruction; private $isAcquired = false; + private $isActivated = false; private $activateWhenAcquired = false; private $slotLocks = array(); @@ -111,7 +112,12 @@ final class DrydockLease extends DrydockDAO $task = PhabricatorWorker::scheduleTask( 'DrydockAllocatorWorker', - $this->getID()); + array( + 'leasePHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); // NOTE: Scheduling the task might execute it in-process, if we're running // from a CLI script. Reload the lease to make sure we have the most @@ -229,11 +235,11 @@ final class DrydockLease extends DrydockDAO if ($this->activateWhenAcquired) { $new_status = DrydockLeaseStatus::STATUS_ACTIVE; } else { - $new_status = DrydockLeaseStatus::STATUS_PENDING; + $new_status = DrydockLeaseStatus::STATUS_ACQUIRED; } - if ($new_status === DrydockLeaseStatus::STATUS_ACTIVE) { - if ($resource->getStatus() === DrydockResourceStatus::STATUS_PENDING) { + if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) { + if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { throw new Exception( pht( 'Trying to acquire an active lease on a pending resource. '. @@ -263,6 +269,45 @@ final class DrydockLease extends DrydockDAO return $this->isAcquired; } + public function activateOnResource(DrydockResource $resource) { + $expect_status = DrydockLeaseStatus::STATUS_ACQUIRED; + $actual_status = $this->getStatus(); + if ($actual_status != $expect_status) { + throw new Exception( + pht( + 'Trying to activate a lease which has the wrong status: status '. + 'must be "%s", actually "%s".', + $expect_status, + $actual_status)); + } + + if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { + // TODO: Be stricter about this? + throw new Exception( + pht( + 'Trying to activate a lease on a pending resource.')); + } + + $this->openTransaction(); + + $this + ->setStatus(DrydockLeaseStatus::STATUS_ACTIVE) + ->save(); + + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + + $this->saveTransaction(); + + $this->isActivated = true; + + return $this; + } + + public function isActivatedLease() { + return $this->isActivated; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 4f6f8b83ac..6affb7863c 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -16,6 +16,7 @@ final class DrydockResource extends DrydockDAO private $blueprint = self::ATTACHABLE; private $isAllocated = false; + private $isActivated = false; private $activateWhenAllocated = false; private $slotLocks = array(); @@ -86,7 +87,7 @@ final class DrydockResource extends DrydockDAO return $this; } - public function allocateResource($status) { + public function allocateResource() { if ($this->getID()) { throw new Exception( pht( @@ -131,6 +132,44 @@ final class DrydockResource extends DrydockDAO return $this->isAllocated; } + public function activateResource() { + if (!$this->getID()) { + throw new Exception( + pht( + 'Trying to activate a resource which has not yet been persisted.')); + } + + $expect_status = DrydockResourceStatus::STATUS_PENDING; + $actual_status = $this->getStatus(); + if ($actual_status != $expect_status) { + throw new Exception( + pht( + 'Trying to activate a resource from the wrong status. Status must '. + 'be "%s", actually "%s".', + $expect_status, + $actual_status)); + } + + $this->openTransaction(); + + $this + ->setStatus(DrydockResourceStatus::STATUS_OPEN) + ->save(); + + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + + $this->saveTransaction(); + + $this->isActivated = true; + + return $this; + } + + public function isActivatedResource() { + return $this->isActivated; + } + public function closeResource() { // TODO: This is super broken and will race other lease writers! diff --git a/src/applications/drydock/storage/DrydockSlotLock.php b/src/applications/drydock/storage/DrydockSlotLock.php index 2d52a81f1e..2f980660aa 100644 --- a/src/applications/drydock/storage/DrydockSlotLock.php +++ b/src/applications/drydock/storage/DrydockSlotLock.php @@ -8,6 +8,7 @@ * machine. These optimistic "slot locks" provide a flexible way to do this * sort of simple locking. * + * @task info Getting Lock Information * @task lock Acquiring and Releasing Locks */ final class DrydockSlotLock extends DrydockDAO { @@ -35,6 +36,17 @@ final class DrydockSlotLock extends DrydockDAO { ) + parent::getConfiguration(); } + +/* -( Getting Lock Information )------------------------------------------- */ + + + /** + * Load all locks held by a particular owner. + * + * @param phid Owner PHID. + * @return list All held locks. + * @task info + */ public static function loadLocks($owner_phid) { return id(new DrydockSlotLock())->loadAllWhere( 'ownerPHID = %s', @@ -42,6 +54,57 @@ final class DrydockSlotLock extends DrydockDAO { } + /** + * Test if a lock is currently free. + * + * @param string Lock key to test. + * @return bool True if the lock is currently free. + * @task info + */ + public static function isLockFree($lock) { + return self::areLocksFree(array($lock)); + } + + + /** + * Test if a list of locks are all currently free. + * + * @param list List of lock keys to test. + * @return bool True if all locks are currently free. + * @task info + */ + public static function areLocksFree(array $locks) { + $lock_map = self::loadHeldLocks($locks); + return !$lock_map; + } + + + /** + * Load named locks. + * + * @param list List of lock keys to load. + * @return list List of held locks. + * @task info + */ + public static function loadHeldLocks(array $locks) { + if (!$locks) { + return array(); + } + + $table = new DrydockSlotLock(); + $conn_r = $table->establishConnection('r'); + + $indexes = array(); + foreach ($locks as $lock) { + $indexes[] = PhabricatorHash::digestForIndex($lock); + } + + return id(new DrydockSlotLock())->loadAllWhere( + 'lockIndex IN (%Ls)', + $indexes); + } + + /* -( Acquiring and Releasing Locks )-------------------------------------- */ @@ -74,15 +137,20 @@ final class DrydockSlotLock extends DrydockDAO { $lock); } - // TODO: These exceptions are pretty tricky to read. It would be good to - // figure out which locks could not be acquired and try to improve the - // exception to make debugging easier. - - queryfx( - $conn_w, - 'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %Q', - $table->getTableName(), - implode(', ', $sql)); + try { + queryfx( + $conn_w, + 'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %Q', + $table->getTableName(), + implode(', ', $sql)); + } catch (AphrontDuplicateKeyQueryException $ex) { + // Try to improve the readability of the exception. We might miss on + // this query if the lock has already been released, but most of the + // time we should be able to figure out which locks are already held. + $held = self::loadHeldLocks($locks); + $held = mpull($held, 'getOwnerPHID', 'getLockKey'); + throw new DrydockSlotLockException($held); + } } diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index 407910ae1e..4288662434 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -5,33 +5,12 @@ * @task resource Managing Resources * @task lease Managing Leases */ -final class DrydockAllocatorWorker extends PhabricatorWorker { - - private function getViewer() { - return PhabricatorUser::getOmnipotentUser(); - } - - private function loadLease() { - $viewer = $this->getViewer(); - - // TODO: Make the task data a dictionary like every other worker, and - // probably make this a PHID. - $lease_id = $this->getTaskData(); - - $lease = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withIDs(array($lease_id)) - ->executeOne(); - if (!$lease) { - throw new PhabricatorWorkerPermanentFailureException( - pht('No such lease "%s"!', $lease_id)); - } - - return $lease; - } +final class DrydockAllocatorWorker extends DrydockWorker { protected function doWork() { - $lease = $this->loadLease(); + $lease_phid = $this->getTaskDataValue('leasePHID'); + $lease = $this->loadLease($lease_phid); + $this->allocateAndAcquireLease($lease); } @@ -351,6 +330,20 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { DrydockLease $lease) { $resource = $blueprint->allocateResource($lease); $this->validateAllocatedResource($blueprint, $resource, $lease); + + // If this resource was allocated as a pending resource, queue a task to + // activate it. + if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { + PhabricatorWorker::scheduleTask( + 'DrydockResourceWorker', + array( + 'resourcePHID' => $resource->getPHID(), + ), + array( + 'objectPHID' => $resource->getPHID(), + )); + } + return $resource; } @@ -429,6 +422,19 @@ final class DrydockAllocatorWorker extends PhabricatorWorker { $blueprint->acquireLease($resource, $lease); $this->validateAcquiredLease($blueprint, $resource, $lease); + + // If this lease has been acquired but not activated, queue a task to + // activate it. + if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) { + PhabricatorWorker::scheduleTask( + 'DrydockLeaseWorker', + array( + 'leasePHID' => $lease->getPHID(), + ), + array( + 'objectPHID' => $lease->getPHID(), + )); + } } diff --git a/src/applications/drydock/worker/DrydockLeaseWorker.php b/src/applications/drydock/worker/DrydockLeaseWorker.php new file mode 100644 index 0000000000..2143a4e4cf --- /dev/null +++ b/src/applications/drydock/worker/DrydockLeaseWorker.php @@ -0,0 +1,81 @@ +getTaskDataValue('leasePHID'); + $lease = $this->loadLease($lease_phid); + + $this->activateLease($lease); + } + + + private function activateLease(DrydockLease $lease) { + $actual_status = $lease->getStatus(); + + if ($actual_status != DrydockLeaseStatus::STATUS_ACQUIRED) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Trying to activate lease from wrong status ("%s").', + $actual_status)); + } + + $resource_id = $lease->getResourceID(); + + $resource = id(new DrydockResourceQuery()) + ->setViewer($this->getViewer()) + ->withIDs(array($resource_id)) + ->executeOne(); + if (!$resource) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Trying to activate lease on invalid resource ("%s").', + $resource_id)); + } + + $resource_status = $resource->getStatus(); + + if ($resource_status == DrydockResourceStatus::STATUS_PENDING) { + // TODO: This is explicitly a temporary failure -- we are waiting for + // the resource to come up. + throw new Exception(pht('Resource still activating.')); + } + + if ($resource_status != DrydockResourceStatus::STATUS_OPEN) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Trying to activate lease on a dead resource (in status "%s").', + $resource_status)); + } + + // NOTE: We can race resource destruction here. Between the time we + // performed the read above and now, the resource might have closed, so + // we may activate leases on dead resources. At least for now, this seems + // fine: a resource dying right before we activate a lease on it should not + // be distinguisahble from a resource dying right after we activate a lease + // on it. We end up with an active lease on a dead resource either way, and + // can not prevent resources dying from lightning strikes. + + $blueprint = $resource->getBlueprint(); + $blueprint->activateLease($resource, $lease); + $this->validateActivatedLease($blueprint, $resource, $lease); + } + + private function validateActivatedLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + + if (!$lease->isActivatedLease()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. + 'returned from "%s" without activating a lease.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'acquireLease()')); + } + + } + +} diff --git a/src/applications/drydock/worker/DrydockResourceWorker.php b/src/applications/drydock/worker/DrydockResourceWorker.php new file mode 100644 index 0000000000..45d63029f7 --- /dev/null +++ b/src/applications/drydock/worker/DrydockResourceWorker.php @@ -0,0 +1,45 @@ +getTaskDataValue('resourcePHID'); + $resource = $this->loadResource($resource_phid); + + $this->activateResource($resource); + } + + + private function activateResource(DrydockResource $resource) { + $resource_status = $resource->getStatus(); + + if ($resource_status != DrydockResourceStatus::STATUS_PENDING) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Trying to activate resource from wrong status ("%s").', + $resource_status)); + } + + $blueprint = $resource->getBlueprint(); + $blueprint->activateResource($resource); + $this->validateActivatedResource($blueprint, $resource); + } + + + private function validateActivatedResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + + if (!$resource->isActivatedResource()) { + throw new Exception( + pht( + 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. + 'must actually allocate the resource it returns.', + $blueprint->getBlueprintName(), + $blueprint->getClassName(), + 'allocateResource()')); + } + + } + +} diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php new file mode 100644 index 0000000000..abff7bcd39 --- /dev/null +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -0,0 +1,39 @@ +getViewer(); + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withPHIDs(array($lease_phid)) + ->executeOne(); + if (!$lease) { + throw new PhabricatorWorkerPermanentFailureException( + pht('No such lease "%s"!', $lease_phid)); + } + + return $lease; + } + + protected function loadResource($resource_phid) { + $viewer = $this->getViewer(); + + $resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withPHIDs(array($resource_phid)) + ->executeOne(); + if (!$resource) { + throw new PhabricatorWorkerPermanentFailureException( + pht('No such resource "%s"!', $resource_phid)); + } + + return $resource; + } + +} diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index 629e688b90..9d79ed5ddc 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -87,6 +87,15 @@ abstract class PhabricatorWorker extends Phobject { return $this->data; } + final protected function getTaskDataValue($key, $default = null) { + $data = $this->getTaskData(); + if (!is_array($data)) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Expected task data to be a dictionary.')); + } + return idx($data, $key, $default); + } + final public function executeTask() { $this->doWork(); } @@ -149,8 +158,7 @@ abstract class PhabricatorWorker extends Phobject { /** - * Wait for tasks to complete. If tasks are not leased by other workers, they - * will be executed in this process while waiting. + * Wait for tasks to complete. * * @param list List of queued task IDs to wait for. * @return void @@ -178,24 +186,9 @@ abstract class PhabricatorWorker extends Phobject { break; } - $tasks = id(new PhabricatorWorkerLeaseQuery()) - ->withIDs($waiting) - ->setLimit(1) - ->execute(); - - if (!$tasks) { - // We were not successful in leasing anything. Sleep for a bit and - // see if we have better luck later. - sleep(1); - continue; - } - - $task = head($tasks)->executeTask(); - - $ex = $task->getExecutionException(); - if ($ex) { - throw $ex; - } + // We were not successful in leasing anything. Sleep for a bit and + // see if we have better luck later. + sleep(1); } $tasks = id(new PhabricatorWorkerArchiveTaskQuery()) From 26552a588b47dba5da0a9a577efb9a2ff3d0a139 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 21 Sep 2015 11:52:04 -0700 Subject: [PATCH 14/43] Tweakify the yellow in code embeds Summary: This runs 50% of our stock 'lightyellow'. Test Plan: staring intensifies Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14138 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 064a961080..7c927cd290 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '521656c5', + 'core.pkg.css' => 'a11c3643', 'core.pkg.js' => '47dc9ebb', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', - 'rsrc/css/core/remarkup.css' => 'e27a26b2', + 'rsrc/css/core/remarkup.css' => 'fa3a8225', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => '5a337049', @@ -733,7 +733,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '6920d200', - 'phabricator-remarkup-css' => 'e27a26b2', + 'phabricator-remarkup-css' => 'fa3a8225', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 98dad91a26..5c6cfd8c89 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -39,7 +39,7 @@ } .phabricator-remarkup .remarkup-code-block pre { - background: #FFFEF5; + background: #FEF9ED; border: 1px solid {$sh-lightyellowborder}; display: block; color: #000; From d5dc4588fce4f3eaa02d9687d801ac524ec1b58f Mon Sep 17 00:00:00 2001 From: June Rhodes Date: Mon, 21 Sep 2015 12:07:24 -0700 Subject: [PATCH 15/43] [harbormaster/abort-builds] Support aborting builds in Harbormaster Summary: Ref T1049. This adds support for aborting builds in Harbormaster. Test Plan: Tested it in production. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: yelirekim, traviscline, joshuaspence, Korvin, epriestley Projects: #harbormaster Maniphest Tasks: T1049 Differential Revision: https://secure.phabricator.com/D11870 --- .../PhabricatorHarbormasterApplication.php | 5 +- .../HarbormasterBuildActionController.php | 29 +++++-- .../HarbormasterBuildViewController.php | 21 +++-- .../HarbormasterBuildableActionController.php | 37 ++++++--- .../HarbormasterBuildableViewController.php | 34 +++++--- .../HarbormasterBuildTransactionEditor.php | 7 +- .../engine/HarbormasterBuildEngine.php | 10 ++- .../storage/HarbormasterBuildCommand.php | 3 +- .../storage/HarbormasterBuildTransaction.php | 17 ++-- .../HarbormasterBuildableTransaction.php | 10 +-- .../storage/build/HarbormasterBuild.php | 77 ++++++++++++++----- .../storage/build/HarbormasterBuildTarget.php | 1 + 12 files changed, 183 insertions(+), 68 deletions(-) diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php index 266ac4fafc..59b8ce6444 100644 --- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php +++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php @@ -65,12 +65,13 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication { 'delete/(?:(?P\d+)/)?' => 'HarbormasterStepDeleteController', ), 'buildable/' => array( - '(?P\d+)/(?Pstop|resume|restart)/' + '(?P\d+)/(?Ppause|resume|restart|abort)/' => 'HarbormasterBuildableActionController', ), 'build/' => array( '(?P\d+)/' => 'HarbormasterBuildViewController', - '(?Pstop|resume|restart)/(?P\d+)/(?:(?P[^/]+)/)?' + '(?Ppause|resume|restart|abort)/'. + '(?P\d+)/(?:(?P[^/]+)/)?' => 'HarbormasterBuildActionController', ), 'plan/' => array( diff --git a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php index 133ba27fa8..932a498a37 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php @@ -35,12 +35,15 @@ final class HarbormasterBuildActionController case HarbormasterBuildCommand::COMMAND_RESTART: $can_issue = $build->canRestartBuild(); break; - case HarbormasterBuildCommand::COMMAND_STOP: - $can_issue = $build->canStopBuild(); + case HarbormasterBuildCommand::COMMAND_PAUSE: + $can_issue = $build->canPauseBuild(); break; case HarbormasterBuildCommand::COMMAND_RESUME: $can_issue = $build->canResumeBuild(); break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $can_issue = $build->canAbortBuild(); + break; default: return new Aphront400Response(); } @@ -90,7 +93,19 @@ final class HarbormasterBuildActionController } } break; - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_ABORT: + if ($can_issue) { + $title = pht('Really abort build?'); + $body = pht( + 'Progress on this build will be discarded. Really '. + 'abort build?'); + $submit = pht('Abort Build'); + } else { + $title = pht('Unable to Abort Build'); + $body = pht('You can not abort this build.'); + } + break; + case HarbormasterBuildCommand::COMMAND_PAUSE: if ($can_issue) { $title = pht('Really pause build?'); $body = pht( @@ -103,11 +118,11 @@ final class HarbormasterBuildActionController $body = pht( 'This build is already complete. You can not pause a completed '. 'build.'); - } else if ($build->isStopped()) { + } else if ($build->isPaused()) { $body = pht( 'This build is already paused. You can not pause a build which '. 'has already been paused.'); - } else if ($build->isStopping()) { + } else if ($build->isPausing()) { $body = pht( 'This build is already pausing. You can not reissue a pause '. 'command to a pausing build.'); @@ -129,9 +144,9 @@ final class HarbormasterBuildActionController $body = pht( 'This build is already resuming. You can not reissue a resume '. 'command to a resuming build.'); - } else if (!$build->isStopped()) { + } else if (!$build->isPaused()) { $body = pht( - 'This build is not stopped. You can only resume a stopped '. + 'This build is not paused. You can only resume a paused '. 'build.'); } } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index b6f4473c5a..1d655bb2e0 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -29,10 +29,12 @@ final class HarbormasterBuildViewController if ($build->isRestarting()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Restarting')); - } else if ($build->isStopping()) { + } else if ($build->isPausing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Pausing')); } else if ($build->isResuming()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Resuming')); + } else if ($build->isAborting()) { + $header->setStatus('fa-exclamation-triangle', 'red', pht('Aborting')); } $box = id(new PHUIObjectBoxView()) @@ -447,8 +449,9 @@ final class HarbormasterBuildViewController ->setObjectURI("/build/{$id}"); $can_restart = $build->canRestartBuild(); - $can_stop = $build->canStopBuild(); + $can_pause = $build->canPauseBuild(); $can_resume = $build->canResumeBuild(); + $can_abort = $build->canAbortBuild(); $list->addAction( id(new PhabricatorActionView()) @@ -471,11 +474,19 @@ final class HarbormasterBuildViewController id(new PhabricatorActionView()) ->setName(pht('Pause Build')) ->setIcon('fa-pause') - ->setHref($this->getApplicationURI('/build/stop/'.$id.'/')) - ->setDisabled(!$can_stop) + ->setHref($this->getApplicationURI('/build/pause/'.$id.'/')) + ->setDisabled(!$can_pause) ->setWorkflow(true)); } + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Abort Build')) + ->setIcon('fa-exclamation-triangle') + ->setHref($this->getApplicationURI('/build/abort/'.$id.'/')) + ->setDisabled(!$can_abort) + ->setWorkflow(true)); + return $list; } @@ -522,7 +533,7 @@ final class HarbormasterBuildViewController $item = new PHUIStatusItemView(); - if ($build->isStopping()) { + if ($build->isPausing()) { $status_name = pht('Pausing'); $icon = PHUIStatusItemView::ICON_RIGHT; $color = 'dark'; diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php index 296e6a9b43..2716befd00 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php @@ -39,8 +39,8 @@ final class HarbormasterBuildableActionController $issuable[] = $build; } break; - case HarbormasterBuildCommand::COMMAND_STOP: - if ($build->canStopBuild()) { + case HarbormasterBuildCommand::COMMAND_PAUSE: + if ($build->canPauseBuild()) { $issuable[] = $build; } break; @@ -49,6 +49,11 @@ final class HarbormasterBuildableActionController $issuable[] = $build; } break; + case HarbormasterBuildCommand::COMMAND_ABORT: + if ($build->canAbortBuild()) { + $issuable[] = $build; + } + break; default: return new Aphront400Response(); } @@ -94,20 +99,32 @@ final class HarbormasterBuildableActionController 'restart all builds?'); $submit = pht('Restart All Builds'); } else { - $title = pht('Unable to Restart Build'); + $title = pht('Unable to Restart Builds'); $body = pht('No builds can be restarted.'); } break; - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_PAUSE: if ($issuable) { - $title = pht('Really stop all builds?'); + $title = pht('Really pause all builds?'); $body = pht( - 'If you stop all build, work will halt once the current steps '. + 'If you pause all builds, work will halt once the current steps '. 'complete. You can resume the builds later.'); - $submit = pht('Stop All Builds'); + $submit = pht('Pause All Builds'); } else { - $title = pht('Unable to Stop Build'); - $body = pht('No builds can be stopped.'); + $title = pht('Unable to Pause Builds'); + $body = pht('No builds can be paused.'); + } + break; + case HarbormasterBuildCommand::COMMAND_ABORT: + if ($issuable) { + $title = pht('Really abort all builds?'); + $body = pht( + 'If you abort all builds, work will halt immediately. Work '. + 'will be discarded, and builds must be completely restarted.'); + $submit = pht('Abort All Builds'); + } else { + $title = pht('Unable to Abort Builds'); + $body = pht('No builds can be aborted.'); } break; case HarbormasterBuildCommand::COMMAND_RESUME: @@ -116,7 +133,7 @@ final class HarbormasterBuildableActionController $body = pht('Work will continue on all builds. Really resume?'); $submit = pht('Resume All Builds'); } else { - $title = pht('Unable to Resume Build'); + $title = pht('Unable to Resume Builds'); $body = pht('No builds can be resumed.'); } break; diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index ac55bf40b0..e57c322be4 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -84,7 +84,8 @@ final class HarbormasterBuildableViewController $can_restart = false; $can_resume = false; - $can_stop = false; + $can_pause = false; + $can_abort = false; foreach ($buildable->getBuilds() as $build) { if ($build->canRestartBuild()) { @@ -93,14 +94,18 @@ final class HarbormasterBuildableViewController if ($build->canResumeBuild()) { $can_resume = true; } - if ($build->canStopBuild()) { - $can_stop = true; + if ($build->canPauseBuild()) { + $can_pause = true; + } + if ($build->canAbortBuild()) { + $can_abort = true; } } $restart_uri = "buildable/{$id}/restart/"; - $stop_uri = "buildable/{$id}/stop/"; + $pause_uri = "buildable/{$id}/pause/"; $resume_uri = "buildable/{$id}/resume/"; + $abort_uri = "buildable/{$id}/abort/"; $list->addAction( id(new PhabricatorActionView()) @@ -114,9 +119,9 @@ final class HarbormasterBuildableViewController id(new PhabricatorActionView()) ->setIcon('fa-pause') ->setName(pht('Pause All Builds')) - ->setHref($this->getApplicationURI($stop_uri)) + ->setHref($this->getApplicationURI($pause_uri)) ->setWorkflow(true) - ->setDisabled(!$can_stop || !$can_edit)); + ->setDisabled(!$can_pause || !$can_edit)); $list->addAction( id(new PhabricatorActionView()) @@ -126,6 +131,14 @@ final class HarbormasterBuildableViewController ->setWorkflow(true) ->setDisabled(!$can_resume || !$can_edit)); + $list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-exclamation-triangle') + ->setName(pht('Abort All Builds')) + ->setHref($this->getApplicationURI($abort_uri)) + ->setWorkflow(true) + ->setDisabled(!$can_abort || !$can_edit)); + return $list; } @@ -181,7 +194,7 @@ final class HarbormasterBuildableViewController if ($build->isRestarting()) { $item->addIcon('fa-repeat', pht('Restarting')); - } else if ($build->isStopping()) { + } else if ($build->isPausing()) { $item->addIcon('fa-pause', pht('Pausing')); } else if ($build->isResuming()) { $item->addIcon('fa-play', pht('Resuming')); @@ -191,7 +204,8 @@ final class HarbormasterBuildableViewController $restart_uri = "build/restart/{$build_id}/buildable/"; $resume_uri = "build/resume/{$build_id}/buildable/"; - $stop_uri = "build/stop/{$build_id}/buildable/"; + $pause_uri = "build/pause/{$build_id}/buildable/"; + $abort_uri = "build/abort/{$build_id}/buildable/"; $item->addAction( id(new PHUIListItemView()) @@ -213,9 +227,9 @@ final class HarbormasterBuildableViewController id(new PHUIListItemView()) ->setIcon('fa-pause') ->setName(pht('Pause')) - ->setHref($this->getApplicationURI($stop_uri)) + ->setHref($this->getApplicationURI($pause_uri)) ->setWorkflow(true) - ->setDisabled(!$build->canStopBuild())); + ->setDisabled(!$build->canPauseBuild())); } $targets = $build->getBuildTargets(); diff --git a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php index 6c59622f90..b8c39146cb 100644 --- a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php +++ b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php @@ -71,12 +71,15 @@ final class HarbormasterBuildTransactionEditor case HarbormasterBuildCommand::COMMAND_RESTART: $issuable = $build->canRestartBuild(); break; - case HarbormasterBuildCommand::COMMAND_STOP: - $issuable = $build->canStopBuild(); + case HarbormasterBuildCommand::COMMAND_PAUSE: + $issuable = $build->canPauseBuild(); break; case HarbormasterBuildCommand::COMMAND_RESUME: $issuable = $build->canResumeBuild(); break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $issuable = $build->canAbortBuild(); + break; default: throw new Exception(pht('Unknown command %s', $command)); } diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index f2ed44bb0d..36ca48b060 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -98,6 +98,12 @@ final class HarbormasterBuildEngine extends Phobject { } 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); @@ -110,8 +116,8 @@ final class HarbormasterBuildEngine extends Phobject { $build->save(); } - if ($build->isStopping() && !$build->isComplete()) { - $build->setBuildStatus(HarbormasterBuild::STATUS_STOPPED); + if ($build->isPausing() && !$build->isComplete()) { + $build->setBuildStatus(HarbormasterBuild::STATUS_PAUSED); $build->save(); } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildCommand.php b/src/applications/harbormaster/storage/HarbormasterBuildCommand.php index 1522a054ac..50a40d8e98 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildCommand.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildCommand.php @@ -2,9 +2,10 @@ final class HarbormasterBuildCommand extends HarbormasterDAO { - const COMMAND_STOP = 'stop'; + const COMMAND_PAUSE = 'pause'; const COMMAND_RESUME = 'resume'; const COMMAND_RESTART = 'restart'; + const COMMAND_ABORT = 'abort'; protected $authorPHID; protected $targetPHID; diff --git a/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php b/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php index 07d81b40cb..e16a51008c 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php @@ -31,13 +31,17 @@ final class HarbormasterBuildTransaction return pht( '%s restarted this build.', $this->renderHandleLink($author_phid)); + case HarbormasterBuildCommand::COMMAND_ABORT: + return pht( + '%s aborted this build.', + $this->renderHandleLink($author_phid)); case HarbormasterBuildCommand::COMMAND_RESUME: return pht( '%s resumed this build.', $this->renderHandleLink($author_phid)); - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_PAUSE: return pht( - '%s stopped this build.', + '%s paused this build.', $this->renderHandleLink($author_phid)); } } @@ -59,8 +63,10 @@ final class HarbormasterBuildTransaction return 'fa-backward'; case HarbormasterBuildCommand::COMMAND_RESUME: return 'fa-play'; - case HarbormasterBuildCommand::COMMAND_STOP: - return 'fa-stop'; + case HarbormasterBuildCommand::COMMAND_PAUSE: + return 'fa-pause'; + case HarbormasterBuildCommand::COMMAND_ABORT: + return 'fa-exclamation-triangle'; } } @@ -78,7 +84,8 @@ final class HarbormasterBuildTransaction return 'green'; case self::TYPE_COMMAND: switch ($new) { - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_PAUSE: + case HarbormasterBuildCommand::COMMAND_ABORT: return 'red'; } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php b/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php index 0696edbc1a..90a26d50c2 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php @@ -35,9 +35,9 @@ final class HarbormasterBuildableTransaction return pht( '%s resumed this buildable.', $this->renderHandleLink($author_phid)); - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_PAUSE: return pht( - '%s stopped this buildable.', + '%s paused this buildable.', $this->renderHandleLink($author_phid)); } } @@ -59,8 +59,8 @@ final class HarbormasterBuildableTransaction return 'fa-backward'; case HarbormasterBuildCommand::COMMAND_RESUME: return 'fa-play'; - case HarbormasterBuildCommand::COMMAND_STOP: - return 'fa-stop'; + case HarbormasterBuildCommand::COMMAND_PAUSE: + return 'fa-pause'; } } @@ -78,7 +78,7 @@ final class HarbormasterBuildableTransaction return 'green'; case self::TYPE_COMMAND: switch ($new) { - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_PAUSE: return 'red'; } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index e35744ecc9..bcbd5e0961 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -41,15 +41,20 @@ final class HarbormasterBuild extends HarbormasterDAO */ const STATUS_FAILED = 'failed'; + /** + * The build has aborted. + */ + const STATUS_ABORTED = 'aborted'; + /** * The build encountered an unexpected error. */ const STATUS_ERROR = 'error'; /** - * The build has been stopped. + * The build has been paused. */ - const STATUS_STOPPED = 'stopped'; + const STATUS_PAUSED = 'paused'; /** * The build has been deadlocked. @@ -75,9 +80,11 @@ final class HarbormasterBuild extends HarbormasterDAO return pht('Passed'); case self::STATUS_FAILED: return pht('Failed'); + case self::STATUS_ABORTED: + return pht('Aborted'); case self::STATUS_ERROR: return pht('Unexpected Error'); - case self::STATUS_STOPPED: + case self::STATUS_PAUSED: return pht('Paused'); case self::STATUS_DEADLOCKED: return pht('Deadlocked'); @@ -97,9 +104,11 @@ final class HarbormasterBuild extends HarbormasterDAO return PHUIStatusItemView::ICON_ACCEPT; case self::STATUS_FAILED: return PHUIStatusItemView::ICON_REJECT; + case self::STATUS_ABORTED: + return PHUIStatusItemView::ICON_MINUS; case self::STATUS_ERROR: return PHUIStatusItemView::ICON_MINUS; - case self::STATUS_STOPPED: + case self::STATUS_PAUSED: return PHUIStatusItemView::ICON_MINUS; case self::STATUS_DEADLOCKED: return PHUIStatusItemView::ICON_WARNING; @@ -118,10 +127,11 @@ final class HarbormasterBuild extends HarbormasterDAO case self::STATUS_PASSED: return 'green'; case self::STATUS_FAILED: + case self::STATUS_ABORTED: case self::STATUS_ERROR: case self::STATUS_DEADLOCKED: return 'red'; - case self::STATUS_STOPPED: + case self::STATUS_PAUSED: return 'dark'; default: return 'bluegrey'; @@ -284,16 +294,17 @@ final class HarbormasterBuild extends HarbormasterDAO switch ($this->getBuildStatus()) { case self::STATUS_PASSED: case self::STATUS_FAILED: + case self::STATUS_ABORTED: case self::STATUS_ERROR: - case self::STATUS_STOPPED: + case self::STATUS_PAUSED: return true; } return false; } - public function isStopped() { - return ($this->getBuildStatus() == self::STATUS_STOPPED); + public function isPaused() { + return ($this->getBuildStatus() == self::STATUS_PAUSED); } @@ -317,14 +328,22 @@ final class HarbormasterBuild extends HarbormasterDAO return !$this->isRestarting(); } - public function canStopBuild() { + public function canPauseBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete() && - !$this->isStopped() && - !$this->isStopping(); + !$this->isPaused() && + !$this->isPausing(); + } + + public function canAbortBuild() { + if ($this->isAutobuild()) { + return false; + } + + return !$this->isComplete(); } public function canResumeBuild() { @@ -332,26 +351,29 @@ final class HarbormasterBuild extends HarbormasterDAO return false; } - return $this->isStopped() && + return $this->isPaused() && !$this->isResuming(); } - public function isStopping() { - $is_stopping = false; + public function isPausing() { + $is_pausing = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { - case HarbormasterBuildCommand::COMMAND_STOP: - $is_stopping = true; + case HarbormasterBuildCommand::COMMAND_PAUSE: + $is_pausing = true; break; case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_RESTART: - $is_stopping = false; + $is_pausing = false; + break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $is_pausing = true; break; } } - return $is_stopping; + return $is_pausing; } public function isResuming() { @@ -363,7 +385,10 @@ final class HarbormasterBuild extends HarbormasterDAO case HarbormasterBuildCommand::COMMAND_RESUME: $is_resuming = true; break; - case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_PAUSE: + $is_resuming = false; + break; + case HarbormasterBuildCommand::COMMAND_ABORT: $is_resuming = false; break; } @@ -386,6 +411,20 @@ final class HarbormasterBuild extends HarbormasterDAO return $is_restarting; } + public function isAborting() { + $is_aborting = false; + foreach ($this->getUnprocessedCommands() as $command_object) { + $command = $command_object->getCommand(); + switch ($command) { + case HarbormasterBuildCommand::COMMAND_ABORT: + $is_aborting = true; + break; + } + } + + return $is_aborting; + } + public function deleteUnprocessedCommands() { foreach ($this->getUnprocessedCommands() as $key => $command_object) { $command_object->delete(); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index 7f08292787..1fdefb6ca3 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -268,6 +268,7 @@ final class HarbormasterBuildTarget extends HarbormasterDAO public function isFailed() { switch ($this->getTargetStatus()) { case self::STATUS_FAILED: + case self::STATUS_ABORTED: return true; } From b8567f176416bb5475774a22540da26505e94b2b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 21 Sep 2015 12:29:05 -0700 Subject: [PATCH 16/43] Use setTable on File Transforms tables Summary: Minor, but nicer. Test Plan: Review File Transforms page, see new UI. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14140 --- .../controller/PhabricatorFileTransformListController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php index 6ef1818962..767254ad3a 100644 --- a/src/applications/files/controller/PhabricatorFileTransformListController.php +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -116,11 +116,11 @@ final class PhabricatorFileTransformListController $dst_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('File Sources')) - ->appendChild($dst_table); + ->setTable($dst_table); $src_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Available Transforms')) - ->appendChild($src_table); + ->setTable($src_table); return $this->buildApplicationPage( array( From 799d86dc657fbfa00d2c76e50470487bd93ac1e9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 21 Sep 2015 12:29:17 -0700 Subject: [PATCH 17/43] Show file UI box as collapsed in Diffusion Summary: This UI should have a Collapsed PHUIObjectBoxView. Test Plan: review a file in sandbox Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14139 --- .../diffusion/controller/DiffusionBrowseFileController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php index f975984789..fbd2785725 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php @@ -337,7 +337,8 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController { $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) - ->appendChild($corpus); + ->appendChild($corpus) + ->setCollapsed(true); return $corpus; } From 8ded0927aa7f1912d8c3b744b2f5d743b052c98f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 22 Sep 2015 13:03:29 -0700 Subject: [PATCH 18/43] Recover gracefully from Conduit failure when building "Tags" field in commit mail Summary: Ref T9458. This is basically the same as D13319, but the "Tags" field didn't get covered in that change. Specifically, the issue is: - We try to generate mail to a disabled user (later, we'll drop it without delivering it, but that filtering doesn't happen yet). - The disabled user doesn't have permission to use Conduit (or any other Conduit-related problem occurs). - We fail here, then retry generating the mail again later. Instead, just degrade to not building the field and showing what went wrong. Test Plan: - Pushed some commits, saw mail generate. - Added a fake exception to the field, saw the mail generate with an error message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9458 Differential Revision: https://secure.phabricator.com/D14142 --- .../PhabricatorCommitTagsField.php | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index b69d246d85..001d81e960 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -29,18 +29,23 @@ final class PhabricatorCommitTagsField 'callsign' => $this->getObject()->getRepository()->getCallsign(), ); - $tags_raw = id(new ConduitCall('diffusion.tagsquery', $params)) - ->setUser($this->getViewer()) - ->execute(); + try { + $tags_raw = id(new ConduitCall('diffusion.tagsquery', $params)) + ->setUser($this->getViewer()) + ->execute(); - $tags = DiffusionRepositoryTag::newFromConduit($tags_raw); - if (!$tags) { - return; + $tags = DiffusionRepositoryTag::newFromConduit($tags_raw); + if (!$tags) { + return; + } + $tag_names = mpull($tags, 'getName'); + sort($tag_names); + $tag_names = implode(', ', $tag_names); + } catch (Exception $ex) { + $tag_names = pht('<%s: %s>', get_class($ex), $ex->getMessage()); } - $tag_names = mpull($tags, 'getName'); - sort($tag_names); - $body->addTextSection(pht('TAGS'), implode(', ', $tag_names)); + $body->addTextSection(pht('TAGS'), $tag_names); } } From 789df89c84b5a453770f51f296f3b0e6df677233 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 07:42:08 -0700 Subject: [PATCH 19/43] Add a command queue to Drydock to manage lease/resource release Summary: Ref T9252. Broadly, Drydock currently races on releasing objects from the "active" state. To reproduce this: - Scatter some sleep()s pretty much anywhere in the release code. - Release several times from web UI or CLI in quick succession. Resources or leases will execute some release code twice or otherwise do inconsistent things. (I didn't chase down a detailed reproduction scenario for this since inspection of the code makes it clear that there are no meaningful locks or mechanisms preventing this.) Instead, add a Harbormaster-style command queue to resources and leases. When something wants to do a release, it adds a command to the queue and schedules a worker. The workers acquire a lock, then try to consume commands from the queue. This guarantees that only one process is responsible for writes to active resource/leases. This is the last major step to giving resources and leases a single writer during all states: - Resource, Unsaved: AllocatorWorker - Resource, Pending: ResourceWorker (Possible rename to "Allocated?") - Resource, Open: This diff, ResourceUpdateWorker. (Likely rename to "Active"). - Resource, Closed/Broken: Future destruction worker. (Likely rename to "Released" / "Broken"; maybe remove "Broken"). - Resource, Destroyed: No writes. - Lease, Unsaved: Whatever wants the lease. - Lease, Pending: AllocatorWorker - Lease, Acquired: LeaseWorker - Lease, Active: This diff, LeaseUpdateWorker. - Lease, Released/Broken: Future destruction worker (Maybe remove "Broken"?) - Lease, Expired: No writes. (Likely rename to "Destroyed"). In most phases, we can already guarantee that there is a single writer without doing any extra work. This is more complicated in the "Active" case because the release buttons on the web UI, the release tools on the CLI, the lease requestor itself, the garbage collector, and any other release process cleaning up related objects may try to effect a release. All of these could race one another (and, in many cases, race other processes from other phases because all of these get to act immediately) as this code is currently written. Using a queue here lets us make sure there's only a single writer in this phase. One thing which is notable is that whatever acquires a lease **can not write to it**! It is never the writer once it queues the lease for activation. It can not write to any resources, either. And, likewise, Blueprints can not write to resources while acquiring or releasing leases. We may need to provide a mechinism so that blueprints and/or resource/lease holders get to attach some storage to resources/leases for bookkeeping. For example, a blueprint might need to keep some kind of cache on a resource to help it manage state. But I think we can cross that bridge when we come to it, and nothing else would need to write to this storage so it's technically straightforward to introduce such a mechanism if we need one. Test Plan: - Viewed buttons in web UI, checked enabled/disabled states. - Clicked the buttons. - Saw commands show up in the command queue. - Saw some daemon stuff get scheduled. - Ran CLI tools, saw commands get consumed and resources/leases release. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14143 --- .../20150922.drydock.commands.1.sql | 10 ++ src/__phutil_library_map__.php | 27 ++++-- .../PhabricatorDrydockApplication.php | 6 +- .../drydock/controller/DrydockController.php | 49 ++++++++++ .../DrydockLeaseReleaseController.php | 59 ++++++------ .../controller/DrydockLeaseViewController.php | 16 +++- .../DrydockResourceCloseController.php | 49 ---------- .../DrydockResourceReleaseController.php | 56 +++++++++++ .../DrydockResourceViewController.php | 21 +++-- .../DrydockManagementCloseWorkflow.php | 49 ---------- .../DrydockManagementReleaseLeaseWorkflow.php | 70 ++++++++++++++ ...ydockManagementReleaseResourceWorkflow.php | 71 ++++++++++++++ .../DrydockManagementReleaseWorkflow.php | 52 ----------- .../DrydockManagementUpdateLeaseWorkflow.php | 57 ++++++++++++ ...rydockManagementUpdateResourceWorkflow.php | 58 ++++++++++++ .../drydock/query/DrydockCommandQuery.php | 82 +++++++++++++++++ .../drydock/query/DrydockLeaseQuery.php | 1 + .../drydock/storage/DrydockCommand.php | 69 ++++++++++++++ .../drydock/storage/DrydockLease.php | 85 +++++++++++++---- .../drydock/storage/DrydockResource.php | 68 +++++++------- .../worker/DrydockLeaseUpdateWorker.php | 60 ++++++++++++ .../worker/DrydockResourceUpdateWorker.php | 92 +++++++++++++++++++ .../drydock/worker/DrydockWorker.php | 14 +++ .../artifact/HarbormasterHostArtifact.php | 15 +-- 24 files changed, 883 insertions(+), 253 deletions(-) create mode 100644 resources/sql/autopatches/20150922.drydock.commands.1.sql delete mode 100644 src/applications/drydock/controller/DrydockResourceCloseController.php create mode 100644 src/applications/drydock/controller/DrydockResourceReleaseController.php delete mode 100644 src/applications/drydock/management/DrydockManagementCloseWorkflow.php create mode 100644 src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php create mode 100644 src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php delete mode 100644 src/applications/drydock/management/DrydockManagementReleaseWorkflow.php create mode 100644 src/applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php create mode 100644 src/applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php create mode 100644 src/applications/drydock/query/DrydockCommandQuery.php create mode 100644 src/applications/drydock/storage/DrydockCommand.php create mode 100644 src/applications/drydock/worker/DrydockLeaseUpdateWorker.php create mode 100644 src/applications/drydock/worker/DrydockResourceUpdateWorker.php diff --git a/resources/sql/autopatches/20150922.drydock.commands.1.sql b/resources/sql/autopatches/20150922.drydock.commands.1.sql new file mode 100644 index 0000000000..173fe861ac --- /dev/null +++ b/resources/sql/autopatches/20150922.drydock.commands.1.sql @@ -0,0 +1,10 @@ +CREATE TABLE {$NAMESPACE}_drydock.drydock_command ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + authorPHID VARBINARY(64) NOT NULL, + targetPHID VARBINARY(64) NOT NULL, + command VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + isConsumed BOOL NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_target` (targetPHID, isConsumed) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 747e085023..229f074c58 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -817,7 +817,9 @@ phutil_register_library_map(array( 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', + 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', + 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 'DrydockController' => 'applications/drydock/controller/DrydockController.php', @@ -837,6 +839,7 @@ phutil_register_library_map(array( 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', + 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', 'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', @@ -845,22 +848,25 @@ phutil_register_library_map(array( 'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php', 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', - 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', - 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', + 'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php', + 'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php', + 'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php', + 'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', - 'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', + 'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php', 'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php', 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', + 'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php', 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 'DrydockResourceWorker' => 'applications/drydock/worker/DrydockResourceWorker.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', @@ -4535,7 +4541,12 @@ phutil_register_library_map(array( 'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction', 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DrydockBlueprintViewController' => 'DrydockBlueprintController', + 'DrydockCommand' => array( + 'DrydockDAO', + 'PhabricatorPolicyInterface', + ), 'DrydockCommandInterface' => 'DrydockInterface', + 'DrydockCommandQuery' => 'DrydockQuery', 'DrydockConsoleController' => 'DrydockController', 'DrydockConstants' => 'Phobject', 'DrydockController' => 'PhabricatorController', @@ -4558,6 +4569,7 @@ phutil_register_library_map(array( 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseStatus' => 'DrydockConstants', + 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 'DrydockLeaseViewController' => 'DrydockLeaseController', 'DrydockLeaseWorker' => 'DrydockWorker', 'DrydockLog' => array( @@ -4569,25 +4581,28 @@ phutil_register_library_map(array( 'DrydockLogListView' => 'AphrontView', 'DrydockLogQuery' => 'DrydockQuery', 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', - 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow', + 'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', 'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DrydockResource' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), - 'DrydockResourceCloseController' => 'DrydockResourceController', 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', 'DrydockResourceQuery' => 'DrydockQuery', + 'DrydockResourceReleaseController' => 'DrydockResourceController', 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockResourceStatus' => 'DrydockConstants', + 'DrydockResourceUpdateWorker' => 'DrydockWorker', 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockResourceWorker' => 'DrydockWorker', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 7919f1c9cf..d3a543a4f0 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -55,8 +55,10 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { ), 'resource/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', - '(?P[1-9]\d*)/' => 'DrydockResourceViewController', - '(?P[1-9]\d*)/close/' => 'DrydockResourceCloseController', + '(?P[1-9]\d*)/' => array( + '' => 'DrydockResourceViewController', + 'release/' => 'DrydockResourceReleaseController', + ), ), 'lease/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 3e3d83cc1d..e0130bdf56 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -36,4 +36,53 @@ abstract class DrydockController extends PhabricatorController { ->addRawContent($table); } + protected function buildCommandsTab($target_phid) { + $viewer = $this->getViewer(); + + $commands = id(new DrydockCommandQuery()) + ->setViewer($viewer) + ->withTargetPHIDs(array($target_phid)) + ->execute(); + + $consumed_yes = id(new PHUIIconView()) + ->setIconFont('fa-check green'); + $consumed_no = id(new PHUIIconView()) + ->setIconFont('fa-clock-o grey'); + + $rows = array(); + foreach ($commands as $command) { + $rows[] = array( + $command->getID(), + $viewer->renderHandle($command->getAuthorPHID()), + $command->getCommand(), + ($command->getIsConsumed() + ? $consumed_yes + : $consumed_no), + phabricator_datetime($command->getDateCreated(), $viewer), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No commands issued.')) + ->setHeaders( + array( + pht('ID'), + pht('From'), + pht('Command'), + null, + pht('Date'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide', + null, + null, + )); + + return id(new PHUIPropertyListView()) + ->addRawContent($table); + } + } diff --git a/src/applications/drydock/controller/DrydockLeaseReleaseController.php b/src/applications/drydock/controller/DrydockLeaseReleaseController.php index 4bac0330ba..52be31c97a 100644 --- a/src/applications/drydock/controller/DrydockLeaseReleaseController.php +++ b/src/applications/drydock/controller/DrydockLeaseReleaseController.php @@ -9,6 +9,11 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController { $lease = id(new DrydockLeaseQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->executeOne(); if (!$lease) { return new Aphront404Response(); @@ -17,43 +22,35 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController { $lease_uri = '/lease/'.$lease->getID().'/'; $lease_uri = $this->getApplicationURI($lease_uri); - if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Lease Not Active')) - ->appendChild( - phutil_tag( - 'p', - array(), - pht('You can only release "active" leases.'))) + if (!$lease->canRelease()) { + return $this->newDialog() + ->setTitle(pht('Lease Not Releasable')) + ->appendParagraph( + pht( + 'Leases can not be released after they are destroyed.')) ->addCancelButton($lease_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); } - if (!$request->isDialogFormPost()) { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Really release lease?')) - ->appendChild( - phutil_tag( - 'p', - array(), - pht( - 'Releasing a lease may cause trouble for the lease holder and '. - 'trigger cleanup of the underlying resource. It can not be '. - 'undone. Continue?'))) - ->addSubmitButton(pht('Release Lease')) - ->addCancelButton($lease_uri); + if ($request->isFormPost()) { + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($lease->getPHID()) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); - return id(new AphrontDialogResponse())->setDialog($dialog); + $lease->scheduleUpdate(); + + return id(new AphrontRedirectResponse())->setURI($lease_uri); } - $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - $blueprint->releaseLease($resource, $lease); - - return id(new AphrontReloadResponse())->setURI($lease_uri); + return $this->newDialog() + ->setTitle(pht('Release Lease?')) + ->appendParagraph( + pht( + 'Forcefully releasing a lease may interfere with the operation '. + 'of the lease holder and trigger destruction of the underlying '. + 'resource. It can not be undone.')) + ->addSubmitButton(pht('Release Lease')) + ->addCancelButton($lease_uri); } } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index aed1f6416f..eaa0683c87 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -43,11 +43,13 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $crumbs->addTextCrumb($title, $lease_uri); $locks = $this->buildLocksTab($lease->getPHID()); + $commands = $this->buildCommandsTab($lease->getPHID()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties, pht('Properties')) - ->addPropertyList($locks, pht('Slot Locks')); + ->addPropertyList($locks, pht('Slot Locks')) + ->addPropertyList($commands, pht('Commands')); $log_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lease Logs')) @@ -66,14 +68,20 @@ final class DrydockLeaseViewController extends DrydockLeaseController { } private function buildActionListView(DrydockLease $lease) { + $viewer = $this->getViewer(); + $view = id(new PhabricatorActionListView()) - ->setUser($this->getRequest()->getUser()) + ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($lease); $id = $lease->getID(); - $can_release = ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE); + $can_release = $lease->canRelease(); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $lease, + PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) @@ -81,7 +89,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController { ->setIcon('fa-times') ->setHref($this->getApplicationURI("/lease/{$id}/release/")) ->setWorkflow(true) - ->setDisabled(!$can_release)); + ->setDisabled(!$can_release || !$can_edit)); return $view; } diff --git a/src/applications/drydock/controller/DrydockResourceCloseController.php b/src/applications/drydock/controller/DrydockResourceCloseController.php deleted file mode 100644 index 915bf89452..0000000000 --- a/src/applications/drydock/controller/DrydockResourceCloseController.php +++ /dev/null @@ -1,49 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $resource = id(new DrydockResourceQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$resource) { - return new Aphront404Response(); - } - - $resource_uri = '/resource/'.$resource->getID().'/'; - $resource_uri = $this->getApplicationURI($resource_uri); - - if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Resource Not Open')) - ->appendChild(phutil_tag('p', array(), pht( - 'You can only close "open" resources.'))) - ->addCancelButton($resource_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - if ($request->isFormPost()) { - $resource->closeResource(); - return id(new AphrontReloadResponse())->setURI($resource_uri); - } - - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Really close resource?')) - ->appendChild( - pht( - 'Closing a resource releases all leases and destroys the '. - 'resource. It can not be undone. Continue?')) - ->addSubmitButton(pht('Close Resource')) - ->addCancelButton($resource_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/drydock/controller/DrydockResourceReleaseController.php b/src/applications/drydock/controller/DrydockResourceReleaseController.php new file mode 100644 index 0000000000..4e508597ef --- /dev/null +++ b/src/applications/drydock/controller/DrydockResourceReleaseController.php @@ -0,0 +1,56 @@ +getViewer(); + $id = $request->getURIData('id'); + + $resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$resource) { + return new Aphront404Response(); + } + + $resource_uri = '/resource/'.$resource->getID().'/'; + $resource_uri = $this->getApplicationURI($resource_uri); + + if (!$resource->canRelease()) { + return $this->newDialog() + ->setTitle(pht('Resource Not Releasable')) + ->appendParagraph( + pht( + 'Resources can not be released after they are destroyed.')) + ->addCancelButton($resource_uri); + } + + if ($request->isFormPost()) { + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($resource->getPHID()) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $resource->scheduleUpdate(); + + return id(new AphrontRedirectResponse())->setURI($resource_uri); + } + + + return $this->newDialog() + ->setTitle(pht('Really release resource?')) + ->appendChild( + pht( + 'Releasing a resource releases all leases and destroys the '. + 'resource. It can not be undone.')) + ->addSubmitButton(pht('Release Resource')) + ->addCancelButton($resource_uri); + } + +} diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index d4a37af33c..40009e34ce 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -55,11 +55,13 @@ final class DrydockResourceViewController extends DrydockResourceController { $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); $locks = $this->buildLocksTab($resource->getPHID()); + $commands = $this->buildCommandsTab($resource->getPHID()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties, pht('Properties')) - ->addPropertyList($locks, pht('Slot Locks')); + ->addPropertyList($locks, pht('Slot Locks')) + ->addPropertyList($commands, pht('Commands')); $lease_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Leases')) @@ -83,22 +85,29 @@ final class DrydockResourceViewController extends DrydockResourceController { } private function buildActionListView(DrydockResource $resource) { + $viewer = $this->getViewer(); + $view = id(new PhabricatorActionListView()) - ->setUser($this->getRequest()->getUser()) + ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($resource); - $can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN); - $uri = '/resource/'.$resource->getID().'/close/'; + $can_release = $resource->canRelease(); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $resource, + PhabricatorPolicyCapability::CAN_EDIT); + + $uri = '/resource/'.$resource->getID().'/release/'; $uri = $this->getApplicationURI($uri); $view->addAction( id(new PhabricatorActionView()) ->setHref($uri) - ->setName(pht('Close Resource')) + ->setName(pht('Release Resource')) ->setIcon('fa-times') ->setWorkflow(true) - ->setDisabled(!$can_close)); + ->setDisabled(!$can_release || !$can_edit)); return $view; } diff --git a/src/applications/drydock/management/DrydockManagementCloseWorkflow.php b/src/applications/drydock/management/DrydockManagementCloseWorkflow.php deleted file mode 100644 index f20b0e692b..0000000000 --- a/src/applications/drydock/management/DrydockManagementCloseWorkflow.php +++ /dev/null @@ -1,49 +0,0 @@ -setName('close') - ->setSynopsis(pht('Close a resource.')) - ->setArguments( - array( - array( - 'name' => 'ids', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $ids = $args->getArg('ids'); - if (!$ids) { - throw new PhutilArgumentUsageException( - pht('Specify one or more resource IDs to close.')); - } - - $viewer = $this->getViewer(); - - $resources = id(new DrydockResourceQuery()) - ->setViewer($viewer) - ->withIDs($ids) - ->execute(); - - foreach ($ids as $id) { - $resource = idx($resources, $id); - if (!$resource) { - $console->writeErr("%s\n", pht('Resource %d does not exist!', $id)); - } else if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { - $console->writeErr("%s\n", pht("Resource %d is not 'open'!", $id)); - } else { - $resource->closeResource(); - $console->writeErr("%s\n", pht('Closed resource %d.', $id)); - } - } - - } - -} diff --git a/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php new file mode 100644 index 0000000000..20af18ec21 --- /dev/null +++ b/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php @@ -0,0 +1,70 @@ +setName('release-lease') + ->setSynopsis(pht('Release a lease.')) + ->setArguments( + array( + array( + 'name' => 'id', + 'param' => 'id', + 'repeat' => true, + 'help' => pht('Lease ID to release.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $ids = $args->getArg('id'); + if (!$ids) { + throw new PhutilArgumentUsageException( + pht( + 'Specify one or more lease IDs to release with "%s".', + '--id')); + } + + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + foreach ($ids as $id) { + $lease = idx($leases, $id); + if (!$lease) { + echo tsprintf( + "%s\n", + pht('Lease "%s" does not exist.', $id)); + continue; + } + + if (!$lease->canRelease()) { + echo tsprintf( + "%s\n", + pht('Lease "%s" is not releasable.', $id)); + continue; + } + + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($lease->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $lease->scheduleUpdate(); + + echo tsprintf( + "%s\n", + pht('Scheduled release of lease "%s".', $id)); + } + + } + +} diff --git a/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php new file mode 100644 index 0000000000..01060a5325 --- /dev/null +++ b/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php @@ -0,0 +1,71 @@ +setName('release-resource') + ->setSynopsis(pht('Release a resource.')) + ->setArguments( + array( + array( + 'name' => 'id', + 'param' => 'id', + 'repeat' => true, + 'help' => pht('Resource ID to release.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $ids = $args->getArg('id'); + if (!$ids) { + throw new PhutilArgumentUsageException( + pht( + 'Specify one or more resource IDs to release with "%s".', + '--id')); + } + + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + foreach ($ids as $id) { + $resource = idx($resources, $id); + + if (!$resource) { + echo tsprintf( + "%s\n", + pht('Resource "%s" does not exist.', $id)); + continue; + } + + if (!$resource->canRelease()) { + echo tsprintf( + "%s\n", + pht('Resource "%s" is not releasable.', $id)); + continue; + } + + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($resource->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $resource->scheduleUpdate(); + + echo tsprintf( + "%s\n", + pht('Scheduled release of resource "%s".', $id)); + } + + } + +} diff --git a/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php deleted file mode 100644 index 616a5deb7b..0000000000 --- a/src/applications/drydock/management/DrydockManagementReleaseWorkflow.php +++ /dev/null @@ -1,52 +0,0 @@ -setName('release') - ->setSynopsis(pht('Release a lease.')) - ->setArguments( - array( - array( - 'name' => 'ids', - 'wildcard' => true, - ), - )); - } - - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - - $ids = $args->getArg('ids'); - if (!$ids) { - throw new PhutilArgumentUsageException( - pht('Specify one or more lease IDs to release.')); - } - - $viewer = $this->getViewer(); - - $leases = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withIDs($ids) - ->execute(); - - foreach ($ids as $id) { - $lease = idx($leases, $id); - if (!$lease) { - $console->writeErr("%s\n", pht('Lease %d does not exist!', $id)); - } else if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { - $console->writeErr("%s\n", pht("Lease %d is not 'active'!", $id)); - } else { - $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - $blueprint->releaseLease($resource, $lease); - - $console->writeErr("%s\n", pht('Released lease %d.', $id)); - } - } - - } - -} diff --git a/src/applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php new file mode 100644 index 0000000000..aa74bb0748 --- /dev/null +++ b/src/applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php @@ -0,0 +1,57 @@ +setName('update-lease') + ->setSynopsis(pht('Update a lease.')) + ->setArguments( + array( + array( + 'name' => 'id', + 'param' => 'id', + 'repeat' => true, + 'help' => pht('Lease ID to update.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $ids = $args->getArg('id'); + if (!$ids) { + throw new PhutilArgumentUsageException( + pht( + 'Specify one or more lease IDs to update with "%s".', + '--id')); + } + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + + foreach ($ids as $id) { + $lease = idx($leases, $id); + + if (!$lease) { + echo tsprintf( + "%s\n", + pht('Lease "%s" does not exist.', $id)); + continue; + } + + echo tsprintf( + "%s\n", + pht('Updating lease "%s".', $id)); + + $lease->scheduleUpdate(); + } + } + +} diff --git a/src/applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php new file mode 100644 index 0000000000..79928fda8d --- /dev/null +++ b/src/applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php @@ -0,0 +1,58 @@ +setName('update-resource') + ->setSynopsis(pht('Update a resource.')) + ->setArguments( + array( + array( + 'name' => 'id', + 'param' => 'id', + 'repeat' => true, + 'help' => pht('Resource ID to update.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $ids = $args->getArg('id'); + if (!$ids) { + throw new PhutilArgumentUsageException( + pht( + 'Specify one or more resource IDs to update with "%s".', + '--id')); + } + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + + foreach ($ids as $id) { + $resource = idx($resources, $id); + + if (!$resource) { + echo tsprintf( + "%s\n", + pht('Resource "%s" does not exist.', $id)); + continue; + } + + echo tsprintf( + "%s\n", + pht('Updating resource "%s".', $id)); + + $resource->scheduleUpdate(); + } + + } + +} diff --git a/src/applications/drydock/query/DrydockCommandQuery.php b/src/applications/drydock/query/DrydockCommandQuery.php new file mode 100644 index 0000000000..0d71288a85 --- /dev/null +++ b/src/applications/drydock/query/DrydockCommandQuery.php @@ -0,0 +1,82 @@ +ids = $ids; + return $this; + } + + public function withTargetPHIDs(array $phids) { + $this->targetPHIDs = $phids; + return $this; + } + + public function withConsumed($consumed) { + $this->consumed = $consumed; + return $this; + } + + public function newResultObject() { + return new DrydockCommand(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function willFilterPage(array $commands) { + $target_phids = mpull($commands, 'getTargetPHID'); + + $targets = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($target_phids) + ->execute(); + $targets = mpull($targets, null, 'getPHID'); + + foreach ($commands as $key => $command) { + $target = idx($targets, $command->getTargetPHID()); + if (!$target) { + $this->didRejectResult($command); + unset($commands[$key]); + continue; + } + $command->attachCommandTarget($target); + } + + return $commands; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->targetPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'targetPHID IN (%Ls)', + $this->targetPHIDs); + } + + if ($this->consumed !== null) { + $where[] = qsprintf( + $conn, + 'isConsumed = %d', + (int)$this->consumed); + } + + return $where; + } + +} diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index f7adc07cce..a212a27ca8 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -7,6 +7,7 @@ final class DrydockLeaseQuery extends DrydockQuery { private $resourceIDs; private $statuses; private $datasourceQuery; + private $needCommands; public function withIDs(array $ids) { $this->ids = $ids; diff --git a/src/applications/drydock/storage/DrydockCommand.php b/src/applications/drydock/storage/DrydockCommand.php new file mode 100644 index 0000000000..60cb363ecb --- /dev/null +++ b/src/applications/drydock/storage/DrydockCommand.php @@ -0,0 +1,69 @@ +setAuthorPHID($author->getPHID()) + ->setIsConsumed(0); + } + + protected function getConfiguration() { + return array( + self::CONFIG_COLUMN_SCHEMA => array( + 'command' => 'text32', + 'isConsumed' => 'bool', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_target' => array( + 'columns' => array('targetPHID', 'isConsumed'), + ), + ), + ) + parent::getConfiguration(); + } + + public function attachCommandTarget($target) { + $this->commandTarget = $target; + return $this; + } + + public function getCommandTarget() { + return $this->assertAttached($this->commandTarget); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getCommandTarget()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getCommandTarget()->hasAutomaticCapability( + $capability, + $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht('Drydock commands have the same policies as their targets.'); + } + +} diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 475d209ea0..bcdd008f68 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -30,11 +30,24 @@ final class DrydockLease extends DrydockDAO } public function __destruct() { - if ($this->releaseOnDestruction) { - if ($this->isActive()) { - $this->release(); - } + if (!$this->releaseOnDestruction) { + return; } + + if (!$this->canRelease()) { + return; + } + + $actor = PhabricatorUser::getOmnipotentUser(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + $command = DrydockCommand::initializeNewCommand($actor) + ->setTargetPHID($this->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $this->scheduleUpdate(); } public function getLeaseName() { @@ -130,18 +143,6 @@ final class DrydockLease extends DrydockDAO return $this; } - public function release() { - $this->assertActive(); - $this->setStatus(DrydockLeaseStatus::STATUS_RELEASED); - $this->save(); - - DrydockSlotLock::releaseLocks($this->getPHID()); - - $this->resource = null; - - return $this; - } - public function isActive() { switch ($this->status) { case DrydockLeaseStatus::STATUS_ACQUIRED: @@ -262,6 +263,10 @@ final class DrydockLease extends DrydockDAO $this->isAcquired = true; + if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) { + $this->didActivate(); + } + return $this; } @@ -301,6 +306,8 @@ final class DrydockLease extends DrydockDAO $this->isActivated = true; + $this->didActivate(); + return $this; } @@ -308,6 +315,48 @@ final class DrydockLease extends DrydockDAO return $this->isActivated; } + public function canRelease() { + if (!$this->getID()) { + return false; + } + + switch ($this->getStatus()) { + case DrydockLeaseStatus::STATUS_RELEASED: + return false; + default: + return true; + } + } + + public function scheduleUpdate() { + PhabricatorWorker::scheduleTask( + 'DrydockLeaseUpdateWorker', + array( + 'leasePHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); + } + + private function didActivate() { + $viewer = PhabricatorUser::getOmnipotentUser(); + $need_update = false; + + $commands = id(new DrydockCommandQuery()) + ->setViewer($viewer) + ->withTargetPHIDs(array($this->getPHID())) + ->withConsumed(false) + ->execute(); + if ($commands) { + $need_update = true; + } + + if ($need_update) { + $this->scheduleUpdate(); + } + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -315,6 +364,7 @@ final class DrydockLease extends DrydockDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -322,6 +372,9 @@ final class DrydockLease extends DrydockDAO if ($this->getResource()) { return $this->getResource()->getPolicy($capability); } + + // TODO: Implement reasonable policies. + return PhabricatorPolicies::getMostOpenPolicy(); } diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 6affb7863c..fefb6e7516 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -170,43 +170,44 @@ final class DrydockResource extends DrydockDAO return $this->isActivated; } - public function closeResource() { + public function canRelease() { + switch ($this->getStatus()) { + case DrydockResourceStatus::STATUS_CLOSED: + case DrydockResourceStatus::STATUS_DESTROYED: + return false; + default: + return true; + } + } - // TODO: This is super broken and will race other lease writers! + public function scheduleUpdate() { + PhabricatorWorker::scheduleTask( + 'DrydockResourceUpdateWorker', + array( + 'resourcePHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); + } - $this->openTransaction(); - $statuses = array( - DrydockLeaseStatus::STATUS_PENDING, - DrydockLeaseStatus::STATUS_ACTIVE, - ); + private function didActivate() { + $viewer = PhabricatorUser::getOmnipotentUser(); - $leases = id(new DrydockLeaseQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withResourceIDs(array($this->getID())) - ->withStatuses($statuses) - ->execute(); + $need_update = false; - foreach ($leases as $lease) { - switch ($lease->getStatus()) { - case DrydockLeaseStatus::STATUS_PENDING: - $message = pht('Breaking pending lease (resource closing).'); - $lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN); - break; - case DrydockLeaseStatus::STATUS_ACTIVE: - $message = pht('Releasing active lease (resource closing).'); - $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); - break; - } - DrydockBlueprintImplementation::writeLog($this, $lease, $message); - $lease->save(); - } + $commands = id(new DrydockCommandQuery()) + ->setViewer($viewer) + ->withTargetPHIDs(array($this->getPHID())) + ->withConsumed(false) + ->execute(); + if ($commands) { + $need_update = true; + } - $this->setStatus(DrydockResourceStatus::STATUS_CLOSED); - $this->save(); - - DrydockSlotLock::releaseLocks($this->getPHID()); - - $this->saveTransaction(); + if ($need_update) { + $this->scheduleUpdate(); + } } @@ -216,12 +217,15 @@ final class DrydockResource extends DrydockDAO public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: + case PhabricatorPolicyCapability::CAN_EDIT: + // TODO: Implement reasonable policies. return PhabricatorPolicies::getMostOpenPolicy(); } } diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php new file mode 100644 index 0000000000..5e96a608f6 --- /dev/null +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -0,0 +1,60 @@ +getTaskDataValue('leasePHID'); + + $hash = PhabricatorHash::digestForIndex($lease_phid); + $lock_key = 'drydock.lease:'.$hash; + + $lock = PhabricatorGlobalLock::newLock($lock_key) + ->lock(1); + + $lease = $this->loadLease($lease_phid); + $this->updateLease($lease); + + $lock->unlock(); + } + + private function updateLease(DrydockLease $lease) { + $commands = $this->loadCommands($lease->getPHID()); + foreach ($commands as $command) { + if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { + // Leases can't receive commands before they activate or after they + // release. + break; + } + + $this->processCommand($lease, $command); + $command + ->setIsConsumed(true) + ->save(); + } + } + + private function processCommand( + DrydockLease $lease, + DrydockCommand $command) { + switch ($command->getCommand()) { + case DrydockCommand::COMMAND_RELEASE: + $this->releaseLease($lease); + break; + } + } + + private function releaseLease(DrydockLease $lease) { + $lease->openTransaction(); + $lease + ->setStatus(DrydockLeaseStatus::STATUS_RELEASED) + ->save(); + + // TODO: Hold slot locks until destruction? + DrydockSlotLock::releaseLocks($lease->getPHID()); + $lease->saveTransaction(); + + // TODO: Hook for resource release behaviors. + // TODO: Schedule lease destruction. + } + +} diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php new file mode 100644 index 0000000000..78c5726b32 --- /dev/null +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -0,0 +1,92 @@ +getTaskDataValue('resourcePHID'); + + $hash = PhabricatorHash::digestForIndex($resource_phid); + $lock_key = 'drydock.resource:'.$hash; + + $lock = PhabricatorGlobalLock::newLock($lock_key) + ->lock(1); + + $resource = $this->loadResource($resource_phid); + $this->updateResource($resource); + + $lock->unlock(); + } + + private function updateResource(DrydockResource $resource) { + $commands = $this->loadCommands($resource->getPHID()); + foreach ($commands as $command) { + if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { + // Resources can't receive commands before they activate or after they + // release. + break; + } + + $this->processCommand($resource, $command); + + $command + ->setIsConsumed(true) + ->save(); + } + } + + private function processCommand( + DrydockResource $resource, + DrydockCommand $command) { + + switch ($command->getCommand()) { + case DrydockCommand::COMMAND_RELEASE: + $this->releaseResource($resource); + break; + } + } + + private function releaseResource(DrydockResource $resource) { + if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { + // If we had multiple release commands + // This command is only meaningful to resources in the "Open" state. + return; + } + + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + $resource->openTransaction(); + $resource + ->setStatus(DrydockResourceStatus::STATUS_CLOSED) + ->save(); + + // TODO: Hold slot locks until destruction? + DrydockSlotLock::releaseLocks($resource->getPHID()); + $resource->saveTransaction(); + + $statuses = array( + DrydockLeaseStatus::STATUS_PENDING, + DrydockLeaseStatus::STATUS_ACQUIRED, + DrydockLeaseStatus::STATUS_ACTIVE, + ); + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withResourceIDs(array($resource->getID())) + ->withStatuses($statuses) + ->execute(); + + foreach ($leases as $lease) { + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($lease->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $lease->scheduleUpdate(); + } + + // TODO: Schedule resource destruction. + } + +} diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index abff7bcd39..d41643de47 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -36,4 +36,18 @@ abstract class DrydockWorker extends PhabricatorWorker { return $resource; } + protected function loadCommands($target_phid) { + $viewer = $this->getViewer(); + + $commands = id(new DrydockCommandQuery()) + ->setViewer($viewer) + ->withTargetPHIDs(array($target_phid)) + ->withConsumed(false) + ->execute(); + + $commands = msort($commands, 'getID'); + + return $commands; + } + } diff --git a/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php index 73d00844af..ce109eb498 100644 --- a/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php +++ b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php @@ -62,13 +62,16 @@ final class HarbormasterHostArtifact extends HarbormasterArtifact { public function releaseArtifact(PhabricatorUser $actor) { $lease = $this->loadArtifactLease($actor); - $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - - if ($lease->isActive()) { - $blueprint->releaseLease($resource, $lease); + if (!$lease->canRelease()) { + return; } + + $command = DrydockCommand::initializeNewCommand($actor) + ->setTargetPHID($lease->getPHID()) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $lease->scheduleUpdate(); } - } From 0a37145072083cde06f8aad9739a25f43f81af33 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 10:52:17 -0700 Subject: [PATCH 20/43] [drydock/core] Show blueprints / resources as links in Drydock view controllers Summary: Ref T2015. This updates the blueprint / resource references in the Drydock view controllers to render as handles. Test Plan: Viewed the controllers, saw links. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: devurandom, joshuaspence, Korvin, epriestley Maniphest Tasks: T2015 Differential Revision: https://secure.phabricator.com/D10873 --- .../controller/DrydockLeaseViewController.php | 17 ++++++++++++++--- .../drydock/phid/DrydockResourcePHIDType.php | 7 ++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index eaa0683c87..1d305652fd 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -130,9 +130,20 @@ final class DrydockLeaseViewController extends DrydockLeaseController { pht('Resource Type'), $lease->getResourceType()); - $view->addProperty( - pht('Resource'), - $lease->getResourceID()); + $resource = id(new DrydockResourceQuery()) + ->setViewer($this->getViewer()) + ->withIDs(array($lease->getResourceID())) + ->executeOne(); + + if ($resource !== null) { + $view->addProperty( + pht('Resource'), + $this->getViewer()->renderHandle($resource->getPHID())); + } else { + $view->addProperty( + pht('Resource'), + pht('No Resource')); + } $attributes = $lease->getAttributes(); if ($attributes) { diff --git a/src/applications/drydock/phid/DrydockResourcePHIDType.php b/src/applications/drydock/phid/DrydockResourcePHIDType.php index 1bfcc68597..6b266ff169 100644 --- a/src/applications/drydock/phid/DrydockResourcePHIDType.php +++ b/src/applications/drydock/phid/DrydockResourcePHIDType.php @@ -29,7 +29,12 @@ final class DrydockResourcePHIDType extends PhabricatorPHIDType { $resource = $objects[$phid]; $id = $resource->getID(); - $handle->setName($resource->getName()); + $handle->setName( + pht( + 'Resource %d: %s', + $id, + $resource->getName())); + $handle->setURI("/drydock/resource/{$id}/"); } } From 1f311d64c608a66f95e57a719e996b8eb7760b88 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 11:20:20 -0700 Subject: [PATCH 21/43] Give Drydock resources and leases a real "destroy" lifecycle phase Summary: Ref T9252. Some leases or resources may need to remove data, tear down VMs, etc., during cleanup. After they are released, queue a "destroy" phase for performing teardown. Test Plan: - Used `bin/drydock lease ...` to create a working copy lease. - Used `bin/drydock release-lease` and `bin/drydock release-resource` to release the lease and then the working copy and host. - Saw working copy and host get destroyed and cleaned up properly. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T6569, T9252 Differential Revision: https://secure.phabricator.com/D14144 --- src/__phutil_library_map__.php | 4 + ...anacServiceHostBlueprintImplementation.php | 26 ++++++ .../DrydockBlueprintImplementation.php | 84 +++++++++++++------ ...dockWorkingCopyBlueprintImplementation.php | 40 +++++++++ .../DrydockManagementLeaseWorkflow.php | 15 +++- .../drydock/storage/DrydockBlueprint.php | 33 +++++++- .../worker/DrydockLeaseDestroyWorker.php | 39 +++++++++ .../worker/DrydockLeaseUpdateWorker.php | 15 +++- .../worker/DrydockResourceDestroyWorker.php | 35 ++++++++ .../worker/DrydockResourceUpdateWorker.php | 9 +- 10 files changed, 264 insertions(+), 36 deletions(-) create mode 100644 src/applications/drydock/worker/DrydockLeaseDestroyWorker.php create mode 100644 src/applications/drydock/worker/DrydockResourceDestroyWorker.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 229f074c58..83f5916630 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -832,6 +832,7 @@ phutil_register_library_map(array( 'DrydockLease' => 'applications/drydock/storage/DrydockLease.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', + 'DrydockLeaseDestroyWorker' => 'applications/drydock/worker/DrydockLeaseDestroyWorker.php', 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', 'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php', 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', @@ -859,6 +860,7 @@ phutil_register_library_map(array( 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', + 'DrydockResourceDestroyWorker' => 'applications/drydock/worker/DrydockResourceDestroyWorker.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', @@ -4562,6 +4564,7 @@ phutil_register_library_map(array( ), 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', + 'DrydockLeaseDestroyWorker' => 'DrydockWorker', 'DrydockLeaseListController' => 'DrydockLeaseController', 'DrydockLeaseListView' => 'AphrontView', 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', @@ -4595,6 +4598,7 @@ phutil_register_library_map(array( ), 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', + 'DrydockResourceDestroyWorker' => 'DrydockWorker', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 64c002bdb0..d906df5b04 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -87,6 +87,14 @@ final class DrydockAlmanacServiceHostBlueprintImplementation $exceptions); } + public function destroyResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + // We don't create anything when allocating hosts, so we don't need to do + // any cleanup here. + return; + } + public function canAcquireLeaseOnResource( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -110,6 +118,24 @@ final class DrydockAlmanacServiceHostBlueprintImplementation ->acquireOnResource($resource); } + public function didReleaseLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // Almanac hosts stick around indefinitely so we don't need to recycle them + // if they don't have any leases. + return; + } + + public function destroyLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // We don't create anything when activating a lease, so we don't need to + // throw anything away. + return; + } + private function getLeaseSlotLock(DrydockResource $resource) { $resource_phid = $resource->getPHID(); return "almanac.host.lease({$resource_phid})"; diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index d5085002eb..e781379568 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -67,6 +67,11 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockResource $resource, DrydockLease $lease); + + /** + * @return void + * @task lease + */ public function activateLease( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -74,37 +79,40 @@ abstract class DrydockBlueprintImplementation extends Phobject { throw new PhutilMethodNotImplementedException(); } - final public function releaseLease( + + /** + * React to a lease being released. + * + * This callback is primarily useful for automatically releasing resources + * once all leases are released. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource a lease was released on. + * @param DrydockLease Recently released lease. + * @return void + * @task lease + */ + abstract public function didReleaseLease( DrydockBlueprint $blueprint, DrydockResource $resource, - DrydockLease $lease) { + DrydockLease $lease); - // TODO: This is all broken nonsense. - - $scope = $this->pushActiveScope(null, $lease); - - $released = false; - - $lease->openTransaction(); - $lease->beginReadLocking(); - $lease->reload(); - - if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { - $lease->release(); - $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); - $lease->save(); - $released = true; - } - - $lease->endReadLocking(); - $lease->saveTransaction(); - - if (!$released) { - throw new Exception(pht('Unable to release lease: lease not active!')); - } - - } + /** + * Destroy any temporary data associated with a lease. + * + * If a lease creates temporary state while held, destroy it here. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource the lease is acquired on. + * @param DrydockLease The lease being destroyed. + * @return void + * @task lease + */ + abstract public function destroyLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease); /* -( Resource Allocation )------------------------------------------------ */ @@ -204,12 +212,34 @@ abstract class DrydockBlueprintImplementation extends Phobject { DrydockBlueprint $blueprint, DrydockLease $lease); + + /** + * @task resource + */ public function activateResource( DrydockBlueprint $blueprint, DrydockResource $resource) { throw new PhutilMethodNotImplementedException(); } + + /** + * Destroy any temporary data associated with a resource. + * + * If a resource creates temporary state when allocated, destroy that state + * here. For example, you might shut down a virtual host or destroy a working + * copy on disk. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource being destroyed. + * @return void + * @task resource + */ + abstract public function destroyResource( + DrydockBlueprint $blueprint, + DrydockResource $resource); + + /* -( Resource Interfaces )------------------------------------------------ */ diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 6fdc787274..6ffe90469d 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -126,6 +126,26 @@ final class DrydockWorkingCopyBlueprintImplementation ->activateResource(); } + public function destroyResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + + $lease = $this->loadHostLease($resource); + + // Destroy the lease on the host. + $lease->releaseOnDestruction(); + + // Destroy the working copy on disk. + $command_type = DrydockCommandInterface::INTERFACE_TYPE; + $interface = $lease->getInterface($command_type); + + $root_key = 'workingcopy.root'; + $root = $resource->getAttribute($root_key); + if (strlen($root)) { + $interface->execx('rm -rf -- %s', $root); + } + } + public function activateLease( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -162,6 +182,26 @@ final class DrydockWorkingCopyBlueprintImplementation $lease->activateOnResource($resource); } + public function didReleaseLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // We leave working copies around even if there are no leases on them, + // since the cost to maintain them is nearly zero but rebuilding them is + // moderately expensive and it's likely that they'll be reused. + return; + } + + public function destroyLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // When we activate a lease we just reset the working copy state and do + // not create any new state, so we don't need to do anything special when + // destroying a lease. + return; + } + public function getType() { return 'working-copy'; } diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index f3617f9b44..117ab2b6a2 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -45,11 +45,18 @@ final class DrydockManagementLeaseWorkflow if ($attributes) { $lease->setAttributes($attributes); } - $lease - ->queueForActivation() - ->waitUntilActive(); + $lease->queueForActivation(); + + echo tsprintf( + "%s\n", + pht('Waiting for daemons to activate lease...')); + + $lease->waitUntilActive(); + + echo tsprintf( + "%s\n", + pht('Activated lease "%s".', $lease->getID())); - $console->writeOut("%s\n", pht('Acquired Lease %s', $lease->getID())); return 0; } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 0907948d14..ed83ded571 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -143,6 +143,18 @@ final class DrydockBlueprint extends DrydockDAO $resource); } + + /** + * @task resource + */ + public function destroyResource(DrydockResource $resource) { + $this->getImplementation()->destroyResource( + $this, + $resource); + return $this; + } + + /* -( Acquiring Leases )--------------------------------------------------- */ @@ -188,10 +200,27 @@ final class DrydockBlueprint extends DrydockDAO /** * @task lease */ - public function releaseLease( + public function didReleaseLease( DrydockResource $resource, DrydockLease $lease) { - $this->getImplementation()->releaseLease($this, $resource, $lease); + $this->getImplementation()->didReleaseLease( + $this, + $resource, + $lease); + return $this; + } + + + /** + * @task lease + */ + public function destroyLease( + DrydockResource $resource, + DrydockLease $lease) { + $this->getImplementation()->destroyLease( + $this, + $resource, + $lease); return $this; } diff --git a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php new file mode 100644 index 0000000000..c019f29cb7 --- /dev/null +++ b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php @@ -0,0 +1,39 @@ +getTaskDataValue('leasePHID'); + $lease = $this->loadLease($lease_phid); + $this->destroyLease($lease); + } + + private function destroyLease(DrydockLease $lease) { + $status = $lease->getStatus(); + + switch ($status) { + case DrydockLeaseStatus::STATUS_RELEASED: + case DrydockLeaseStatus::STATUS_BROKEN: + break; + default: + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to destroy lease ("%s"), lease has the wrong '. + 'status ("%s").', + $lease->getPHID(), + $status)); + } + + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + $blueprint->destroyLease($resource, $lease); + + // TODO: Rename DrydockLeaseStatus::STATUS_EXPIRED to STATUS_DESTROYED. + + $lease + ->setStatus(DrydockLeaseStatus::STATUS_EXPIRED) + ->save(); + } + +} diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 5e96a608f6..3206d6ea13 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -53,8 +53,19 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { DrydockSlotLock::releaseLocks($lease->getPHID()); $lease->saveTransaction(); - // TODO: Hook for resource release behaviors. - // TODO: Schedule lease destruction. + PhabricatorWorker::scheduleTask( + 'DrydockLeaseDestroyWorker', + array( + 'leasePHID' => $lease->getPHID(), + ), + array( + 'objectPHID' => $lease->getPHID(), + )); + + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + $blueprint->didReleaseLease($resource, $lease); } } diff --git a/src/applications/drydock/worker/DrydockResourceDestroyWorker.php b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php new file mode 100644 index 0000000000..812edc682a --- /dev/null +++ b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php @@ -0,0 +1,35 @@ +getTaskDataValue('resourcePHID'); + $resource = $this->loadResource($resource_phid); + $this->destroyResource($resource); + } + + private function destroyResource(DrydockResource $resource) { + $status = $resource->getStatus(); + + switch ($status) { + case DrydockResourceStatus::STATUS_CLOSED: + case DrydockResourceStatus::STATUS_BROKEN: + break; + default: + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to destroy resource ("%s"), resource has the wrong '. + 'status ("%s").', + $resource->getPHID(), + $status)); + } + + $blueprint = $resource->getBlueprint(); + $blueprint->destroyResource($resource); + + $resource + ->setStatus(DrydockResourceStatus::STATUS_DESTROYED) + ->save(); + } + +} diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 78c5726b32..7528df8d12 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -86,7 +86,14 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $lease->scheduleUpdate(); } - // TODO: Schedule resource destruction. + PhabricatorWorker::scheduleTask( + 'DrydockResourceDestroyWorker', + array( + 'resourcePHID' => $resource->getPHID(), + ), + array( + 'objectPHID' => $resource->getPHID(), + )); } } From be9cc235b2edbef23242b05203eb88a29e42949a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 23 Sep 2015 12:48:19 -0700 Subject: [PATCH 22/43] Add Application Routes to Phame AppSearch queries Summary: Fixes T9388, lays in basic ApplicationSearch. Test Plan: Build a dashboard with Posts and Blogs, click on search icon, get sent to correct page. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9388 Differential Revision: https://secure.phabricator.com/D14146 --- .../PhabricatorPhameApplication.php | 2 + .../blog/PhameBlogListController.php | 95 ++++------------ .../post/PhamePostListController.php | 103 ++++++------------ .../phame/query/PhamePostSearchEngine.php | 1 + 4 files changed, 55 insertions(+), 146 deletions(-) diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index f7643c6097..ec98087adf 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -42,6 +42,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { 'live/(?P[^/]+)/(?P.*)' => 'PhameBlogLiveController', 'post/' => array( '(?:(?Pdraft|all)/)?' => 'PhamePostListController', + '(?:query/(?P[^/]+)/)?' => 'PhamePostListController', 'blogger/(?P[\w\.-_]+)/' => 'PhamePostListController', 'delete/(?P[^/]+)/' => 'PhamePostDeleteController', 'edit/(?:(?P[^/]+)/)?' => 'PhamePostEditController', @@ -56,6 +57,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { ), 'blog/' => array( '(?:(?Puser|all)/)?' => 'PhameBlogListController', + '(?:query/(?P[^/]+)/)?' => 'PhameBlogListController', 'delete/(?P[^/]+)/' => 'PhameBlogDeleteController', 'edit/(?P[^/]+)/' => 'PhameBlogEditController', 'view/(?P[^/]+)/' => 'PhameBlogViewController', diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php index 9d0253d89c..965e1eec49 100644 --- a/src/applications/phame/controller/blog/PhameBlogListController.php +++ b/src/applications/phame/controller/blog/PhameBlogListController.php @@ -2,86 +2,33 @@ final class PhameBlogListController extends PhameController { - public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); - - $nav = $this->renderSideNavFilterView(null); - $filter = $request->getURIData('filter'); - $filter = $nav->selectFilter('blog/'.$filter, 'blog/user'); - - $query = id(new PhameBlogQuery()) - ->setViewer($user); - - switch ($filter) { - case 'blog/all': - $title = pht('All Blogs'); - $nodata = pht('No blogs have been created.'); - break; - case 'blog/user': - $title = pht('Joinable Blogs'); - $nodata = pht('There are no blogs you can contribute to.'); - $query->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_JOIN, - )); - break; - default: - throw new Exception(pht("Unknown filter '%s'!", $filter)); - } - - $pager = id(new PHUIPagerView()) - ->setURI($request->getRequestURI(), 'offset') - ->setOffset($request->getInt('offset')); - - $blogs = $query->executeWithOffsetPager($pager); - - $blog_list = $this->renderBlogList($blogs, $user, $nodata); - $blog_list->setPager($pager); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setObjectList($blog_list); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, $this->getApplicationURI()); - - $nav->appendChild( - array( - $crumbs, - $box, - )); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + public function shouldAllowPublic() { + return true; } - private function renderBlogList( - array $blogs, - PhabricatorUser $viewer, - $nodata) { + public function handleRequest(AphrontRequest $request) { + $query_key = $request->getURIData('queryKey'); + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($query_key) + ->setSearchEngine(new PhameBlogSearchEngine()) + ->setNavigation($this->buildSideNavView()); - $view = new PHUIObjectItemListView(); - $view->setNoDataString($nodata); - $view->setUser($viewer); - foreach ($blogs as $blog) { + return $this->delegateToController($controller); + } - $id = $blog->getID(); - $item = id(new PHUIObjectItemView()) - ->setUser($viewer) - ->setObject($blog) - ->setHeader($blog->getName()) - ->setStatusIcon('fa-star') - ->setHref($this->getApplicationURI("/blog/view/{$id}/")) - ->addAttribute($blog->getSkin()) - ->addAttribute($blog->getDomain()); + public function buildSideNavView() { + $viewer = $this->getRequest()->getUser(); - $view->addItem($item); - } + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - return $view; + id(new PhameBlogSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; } } diff --git a/src/applications/phame/controller/post/PhamePostListController.php b/src/applications/phame/controller/post/PhamePostListController.php index b2b28bd4eb..fb38a830bb 100644 --- a/src/applications/phame/controller/post/PhamePostListController.php +++ b/src/applications/phame/controller/post/PhamePostListController.php @@ -2,78 +2,37 @@ final class PhamePostListController extends PhameController { - public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $filter = $request->getURIData('filter'); - $bloggername = $request->getURIData('bloggername'); - - $query = id(new PhamePostQuery()) - ->setViewer($viewer); - - $nav = $this->renderSideNavFilterView(); - $nodata = null; - - switch ($filter) { - case 'draft': - $query->withBloggerPHIDs(array($viewer->getPHID())); - $query->withVisibility(PhamePost::VISIBILITY_DRAFT); - $nodata = pht('You have no unpublished drafts.'); - $title = pht('Unpublished Drafts'); - $nav->selectFilter('post/draft'); - break; - case 'blogger': - if ($bloggername) { - $blogger = id(new PhabricatorUser())->loadOneWhere( - 'username = %s', - $bloggername); - if (!$blogger) { - return new Aphront404Response(); - } - } else { - $blogger = $viewer; - } - - $query->withBloggerPHIDs(array($blogger->getPHID())); - if ($blogger->getPHID() == $viewer->getPHID()) { - $nav->selectFilter('post'); - $nodata = pht('You have not written any posts.'); - } else { - $nodata = pht('%s has not written any posts.', $blogger); - } - $title = pht('Posts by %s', $blogger); - break; - default: - case 'all': - $nodata = pht('There are no visible posts.'); - $title = pht('Posts'); - $nav->selectFilter('post/all'); - break; - } - - $pager = id(new AphrontCursorPagerView()) - ->readFromRequest($request); - - $posts = $query->executeWithCursorPager($pager); - - $post_list = $this->renderPostList($posts, $viewer, $nodata); - $post_list = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->appendChild($post_list); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, $this->getApplicationURI()); - - $nav->appendChild( - array( - $crumbs, - $post_list, - )); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + public function shouldAllowPublic() { + return true; } + public function handleRequest(AphrontRequest $request) { + $query_key = $request->getURIData('queryKey'); + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($query_key) + ->setSearchEngine(new PhamePostSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function buildSideNavView() { + $viewer = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new PhamePostSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + + $nav->addLabel(pht('Blogs')); + $nav->addFilter('blog/', pht('Manage Blogs')); + + $nav->selectFilter(null); + + return $nav; + } + + } diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index a5feb3d634..cedc02cc7a 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -29,6 +29,7 @@ final class PhamePostSearchEngine return array( id(new PhabricatorSearchSelectField()) ->setKey('visibility') + ->setLabel(pht('Visibility')) ->setOptions(array( '' => pht('All'), PhamePost::VISIBILITY_PUBLISHED => pht('Live'), From fcb6d1e2faa5d229211e32fa38e22a31b8728c28 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 13:21:41 -0700 Subject: [PATCH 23/43] Strip some obsolete code out of Drydock Summary: Ref T9252. This simplifies some Drydock code. Most of this code relates to the old notion of Drydock being able to enumerate all the tasks it needs to complete in order to acquire a lease. The code has stepped back from this, since it's unnecessary, the queue is more powerful than it used to be, and it would be a lot of work to keep track of. The ~only thing that should ever wait for leases in modern code is `bin/drydock lease`, and it's fine for it to just sit there sleeping, so this just does that. This reduces the granularity of logging, but I'll address that separately in future logging-focused changes. Test Plan: Used `bin/drydock lease` to acquire a lease, saw it acquire cleanly. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14147 --- .../autopatches/20150923.drydock.taskid.1.sql | 2 + src/__phutil_library_map__.php | 2 - .../DrydockBlueprintImplementation.php | 35 +------ .../drydock/storage/DrydockLease.php | 95 ++++++------------- .../util/DrydockBlueprintScopeGuard.php | 15 --- .../daemon/workers/PhabricatorWorker.php | 45 --------- 6 files changed, 34 insertions(+), 160 deletions(-) create mode 100644 resources/sql/autopatches/20150923.drydock.taskid.1.sql delete mode 100644 src/applications/drydock/util/DrydockBlueprintScopeGuard.php diff --git a/resources/sql/autopatches/20150923.drydock.taskid.1.sql b/resources/sql/autopatches/20150923.drydock.taskid.1.sql new file mode 100644 index 0000000000..cbd6a1dc02 --- /dev/null +++ b/resources/sql/autopatches/20150923.drydock.taskid.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + DROP taskID; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 83f5916630..11e5134628 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -812,7 +812,6 @@ phutil_register_library_map(array( 'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php', 'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php', 'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php', - 'DrydockBlueprintScopeGuard' => 'applications/drydock/util/DrydockBlueprintScopeGuard.php', 'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php', 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', @@ -4538,7 +4537,6 @@ phutil_register_library_map(array( 'DrydockBlueprintListController' => 'DrydockBlueprintController', 'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType', 'DrydockBlueprintQuery' => 'DrydockQuery', - 'DrydockBlueprintScopeGuard' => 'Phobject', 'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction', 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index e781379568..f58767c4fc 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -8,9 +8,6 @@ */ abstract class DrydockBlueprintImplementation extends Phobject { - private $activeResource; - private $activeLease; - abstract public function getType(); abstract public function isEnabled(); @@ -265,10 +262,7 @@ abstract class DrydockBlueprintImplementation extends Phobject { * @task log */ protected function log($message) { - self::writeLog( - $this->activeResource, - $this->activeLease, - $message); + self::writeLog(null, null, $message); } @@ -320,13 +314,6 @@ abstract class DrydockBlueprintImplementation extends Phobject { // Pre-allocate the resource PHID. $resource->setPHID($resource->generatePHID()); - $this->activeResource = $resource; - - $this->log( - pht( - "Blueprint '%s': Created New Template", - get_class($this))); - return $resource; } @@ -349,24 +336,4 @@ abstract class DrydockBlueprintImplementation extends Phobject { } } - private function pushActiveScope( - DrydockResource $resource = null, - DrydockLease $lease = null) { - - if (($this->activeResource !== null) || - ($this->activeLease !== null)) { - throw new Exception(pht('There is already an active resource or lease!')); - } - - $this->activeResource = $resource; - $this->activeLease = $lease; - - return new DrydockBlueprintScopeGuard($this); - } - - public function popActiveScope() { - $this->activeResource = null; - $this->activeLease = null; - } - } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index bcdd008f68..871b5a8e93 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -9,7 +9,6 @@ final class DrydockLease extends DrydockDAO protected $ownerPHID; protected $attributes = array(); protected $status = DrydockLeaseStatus::STATUS_PENDING; - protected $taskID; private $resource = self::ATTACHABLE; private $releaseOnDestruction; @@ -64,7 +63,6 @@ final class DrydockLease extends DrydockDAO 'status' => 'uint32', 'until' => 'epoch?', 'resourceType' => 'text128', - 'taskID' => 'id?', 'ownerPHID' => 'phid?', 'resourceID' => 'id?', ), @@ -108,20 +106,15 @@ final class DrydockLease extends DrydockDAO return ($this->resource !== null); } - public function loadResource() { - return id(new DrydockResource())->loadOneWhere( - 'id = %d', - $this->getResourceID()); - } - public function queueForActivation() { if ($this->getID()) { throw new Exception( pht('Only new leases may be queued for activation!')); } - $this->setStatus(DrydockLeaseStatus::STATUS_PENDING); - $this->save(); + $this + ->setStatus(DrydockLeaseStatus::STATUS_PENDING) + ->save(); $task = PhabricatorWorker::scheduleTask( 'DrydockAllocatorWorker', @@ -132,14 +125,6 @@ final class DrydockLease extends DrydockDAO 'objectPHID' => $this->getPHID(), )); - // NOTE: Scheduling the task might execute it in-process, if we're running - // from a CLI script. Reload the lease to make sure we have the most - // up-to-date information. Normally, this has no effect. - $this->reload(); - - $this->setTaskID($task->getID()); - $this->save(); - return $this; } @@ -161,54 +146,36 @@ final class DrydockLease extends DrydockDAO } } - public static function waitForLeases(array $leases) { - assert_instances_of($leases, __CLASS__); - - $task_ids = array_filter(mpull($leases, 'getTaskID')); - - PhabricatorWorker::waitForTasks($task_ids); - - $unresolved = $leases; - while (true) { - foreach ($unresolved as $key => $lease) { - $lease->reload(); - switch ($lease->getStatus()) { - case DrydockLeaseStatus::STATUS_ACTIVE: - unset($unresolved[$key]); - break; - case DrydockLeaseStatus::STATUS_RELEASED: - throw new Exception(pht('Lease has already been released!')); - case DrydockLeaseStatus::STATUS_EXPIRED: - throw new Exception(pht('Lease has already expired!')); - case DrydockLeaseStatus::STATUS_BROKEN: - throw new Exception(pht('Lease has been broken!')); - case DrydockLeaseStatus::STATUS_PENDING: - case DrydockLeaseStatus::STATUS_ACQUIRED: - break; - default: - throw new Exception(pht('Unknown status??')); - } - } - - if ($unresolved) { - sleep(1); - } else { - break; - } - } - - foreach ($leases as $lease) { - $lease->attachResource($lease->loadResource()); - } - } - public function waitUntilActive() { - if (!$this->getID()) { - $this->queueForActivation(); - } + while (true) { + $lease = $this->reload(); + if (!$lease) { + throw new Exception(pht('Failed to reload lease.')); + } - self::waitForLeases(array($this)); - return $this; + $status = $lease->getStatus(); + + switch ($status) { + case DrydockLeaseStatus::STATUS_ACTIVE: + return; + case DrydockLeaseStatus::STATUS_RELEASED: + throw new Exception(pht('Lease has already been released!')); + case DrydockLeaseStatus::STATUS_EXPIRED: + throw new Exception(pht('Lease has already expired!')); + case DrydockLeaseStatus::STATUS_BROKEN: + throw new Exception(pht('Lease has been broken!')); + case DrydockLeaseStatus::STATUS_PENDING: + case DrydockLeaseStatus::STATUS_ACQUIRED: + break; + default: + throw new Exception( + pht( + 'Lease has unknown status "%s".', + $status)); + } + + sleep(1); + } } public function setActivateWhenAcquired($activate) { diff --git a/src/applications/drydock/util/DrydockBlueprintScopeGuard.php b/src/applications/drydock/util/DrydockBlueprintScopeGuard.php deleted file mode 100644 index 343428683b..0000000000 --- a/src/applications/drydock/util/DrydockBlueprintScopeGuard.php +++ /dev/null @@ -1,15 +0,0 @@ -blueprint = $blueprint; - } - - public function __destruct() { - $this->blueprint->popActiveScope(); - } - -} diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index 9d79ed5ddc..83dc40b681 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -157,51 +157,6 @@ abstract class PhabricatorWorker extends Phobject { } - /** - * Wait for tasks to complete. - * - * @param list List of queued task IDs to wait for. - * @return void - */ - final public static function waitForTasks(array $task_ids) { - if (!$task_ids) { - return; - } - - $task_table = new PhabricatorWorkerActiveTask(); - - $waiting = array_fuse($task_ids); - while ($waiting) { - $conn_w = $task_table->establishConnection('w'); - - // Check if any of the tasks we're waiting on are still queued. If they - // are not, we're done waiting. - $row = queryfx_one( - $conn_w, - 'SELECT COUNT(*) N FROM %T WHERE id IN (%Ld)', - $task_table->getTableName(), - $waiting); - if (!$row['N']) { - // Nothing is queued anymore. Stop waiting. - break; - } - - // We were not successful in leasing anything. Sleep for a bit and - // see if we have better luck later. - sleep(1); - } - - $tasks = id(new PhabricatorWorkerArchiveTaskQuery()) - ->withIDs($task_ids) - ->execute(); - - foreach ($tasks as $task) { - if ($task->getResult() != PhabricatorWorkerArchiveTask::RESULT_SUCCESS) { - throw new Exception(pht('Task %d failed!', $task->getID())); - } - } - } - public function renderForDisplay(PhabricatorUser $viewer) { return null; } From 337990423740a28557b44d9da18c97da0f087e68 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 13:54:27 -0700 Subject: [PATCH 24/43] Allow Drydock leases to expire after a time limit Summary: Ref T6569. If a lease is activated with an expiration date, schedule a task to try to clean it up after that time. Test Plan: - Used `bin/drydock lease ... --until ...` to activate a lease in the near future. - Waited for a bit. - Saw it expire and get destroyed at the scheduled time. Reviewers: hach-que, chad Reviewed By: chad Maniphest Tasks: T6569 Differential Revision: https://secure.phabricator.com/D14148 --- .../controller/DrydockLeaseViewController.php | 9 +++++ .../DrydockManagementLeaseWorkflow.php | 22 ++++++++++ .../drydock/storage/DrydockLease.php | 9 ++++- .../worker/DrydockLeaseUpdateWorker.php | 40 ++++++++++++++++++- .../daemon/workers/PhabricatorWorker.php | 5 +++ 5 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 1d305652fd..42f675a8c0 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -97,6 +97,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController { private function buildPropertyListView( DrydockLease $lease, PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); $view->setActionList($actions); @@ -145,6 +146,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController { pht('No Resource')); } + $until = $lease->getUntil(); + if ($until) { + $until_display = phabricator_datetime($until, $viewer); + } else { + $until_display = phutil_tag('em', array(), pht('Never')); + } + $view->addProperty(pht('Expires'), $until_display); + $attributes = $lease->getAttributes(); if ($attributes) { $view->addSectionHeader( diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 117ab2b6a2..4c1e0d875c 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -14,6 +14,11 @@ final class DrydockManagementLeaseWorkflow 'param' => 'resource_type', 'help' => pht('Resource type.'), ), + array( + 'name' => 'until', + 'param' => 'time', + 'help' => pht('Set lease expiration time.'), + ), array( 'name' => 'attributes', 'param' => 'name=value,...', @@ -33,6 +38,17 @@ final class DrydockManagementLeaseWorkflow '--type')); } + $until = $args->getArg('until'); + if (strlen($until)) { + $until = strtotime($until); + if ($until <= 0) { + throw new PhutilArgumentUsageException( + pht( + 'Unable to parse argument to "%s".', + '--until')); + } + } + $attributes = $args->getArg('attributes'); if ($attributes) { $options = new PhutilSimpleOptions(); @@ -42,9 +58,15 @@ final class DrydockManagementLeaseWorkflow $lease = id(new DrydockLease()) ->setResourceType($resource_type); + if ($attributes) { $lease->setAttributes($attributes); } + + if ($until) { + $lease->setUntil($until); + } + $lease->queueForActivation(); echo tsprintf( diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 871b5a8e93..a2a6abdfa2 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -295,14 +295,16 @@ final class DrydockLease extends DrydockDAO } } - public function scheduleUpdate() { + public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockLeaseUpdateWorker', array( 'leasePHID' => $this->getPHID(), + 'isExpireTask' => ($epoch !== null), ), array( 'objectPHID' => $this->getPHID(), + 'delayUntil' => $epoch, )); } @@ -322,6 +324,11 @@ final class DrydockLease extends DrydockDAO if ($need_update) { $this->scheduleUpdate(); } + + $expires = $this->getUntil(); + if ($expires) { + $this->scheduleUpdate($expires); + } } diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index 3206d6ea13..98ecf2b498 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -11,13 +11,38 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $lock = PhabricatorGlobalLock::newLock($lock_key) ->lock(1); - $lease = $this->loadLease($lease_phid); - $this->updateLease($lease); + try { + $lease = $this->loadLease($lease_phid); + $this->updateLease($lease); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } $lock->unlock(); } private function updateLease(DrydockLease $lease) { + if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { + return; + } + + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + // Check if the lease has expired. If it is, we're going to send it a + // release command. This command will be handled immediately below, it + // just generates a command log and improves consistency. + $now = PhabricatorTime::getNow(); + $expires = $lease->getUntil(); + if ($expires && ($expires <= $now)) { + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($lease->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + } + $commands = $this->loadCommands($lease->getPHID()); foreach ($commands as $command) { if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { @@ -27,10 +52,21 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } $this->processCommand($lease, $command); + $command ->setIsConsumed(true) ->save(); } + + // If this is the task which will eventually release the lease after it + // expires but it is still active, reschedule the task to run after the + // lease expires. This can happen if the lease's expiration was pushed + // forward. + if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { + if ($this->getTaskDataValue('isExpireTask') && $expires) { + throw new PhabricatorWorkerYieldException($expires - $now); + } + } } private function processCommand( diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index 83dc40b681..4e71440604 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -117,6 +117,11 @@ abstract class PhabricatorWorker extends Phobject { ->setPriority($priority) ->setObjectPHID($object_phid); + $delay = idx($options, 'delayUntil'); + if ($delay) { + $task->setLeaseExpires($delay); + } + if (self::$runAllTasksInProcess) { // Do the work in-process. $worker = newv($task_class, array($data)); From 99e4472447529a9648f3e0c9dfe604051f204094 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 19:18:44 -0700 Subject: [PATCH 25/43] Soften checks on a very old Maniphest transactionmigration Summary: Ref T9464. If an ancient transaction doesn't have array values for whatever reason, we fail here. Instead, just recover as gracefully as we can. We may get the transaction "wrong" in some sense, but this only impacts what is rendered in the transaction log. Test Plan: This is nearly a year old and there's no real way to test it. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9464 Differential Revision: https://secure.phabricator.com/D14149 --- .../autopatches/20141222.maniphestprojtxn.php | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/resources/sql/autopatches/20141222.maniphestprojtxn.php b/resources/sql/autopatches/20141222.maniphestprojtxn.php index ba7a8f2f1f..31aae8b986 100644 --- a/resources/sql/autopatches/20141222.maniphestprojtxn.php +++ b/resources/sql/autopatches/20141222.maniphestprojtxn.php @@ -9,41 +9,53 @@ $metadata = array( 'edge:type' => PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, ); foreach (new LiskMigrationIterator($table) as $txn) { - // ManiphestTransaction::TYPE_PROJECTS - if ($txn->getTransactionType() == 'projects') { - $old_value = mig20141222_build_edge_data( - $txn->getOldValue(), - $txn->getObjectPHID()); - $new_value = mig20141222_build_edge_data( - $txn->getNewvalue(), - $txn->getObjectPHID()); - queryfx( - $conn_w, - 'UPDATE %T SET '. - 'transactionType = %s, oldValue = %s, newValue = %s, metaData = %s '. - 'WHERE id = %d', - $table->getTableName(), - PhabricatorTransactions::TYPE_EDGE, - json_encode($old_value), - json_encode($new_value), - json_encode($metadata), - $txn->getID()); + if ($txn->getTransactionType() != 'projects') { + continue; } + + $old_value = mig20141222_build_edge_data( + $txn->getOldValue(), + $txn->getObjectPHID()); + + $new_value = mig20141222_build_edge_data( + $txn->getNewValue(), + $txn->getObjectPHID()); + + queryfx( + $conn_w, + 'UPDATE %T SET '. + 'transactionType = %s, oldValue = %s, newValue = %s, metaData = %s '. + 'WHERE id = %d', + $table->getTableName(), + PhabricatorTransactions::TYPE_EDGE, + json_encode($old_value), + json_encode($new_value), + json_encode($metadata), + $txn->getID()); } echo pht('Done.')."\n"; -function mig20141222_build_edge_data(array $project_phids, $task_phid) { +function mig20141222_build_edge_data($project_phids, $task_phid) { $edge_data = array(); + + // See T9464. If we didn't get a proper array value out of the transaction, + // just return an empty value so we can move forward. + if (!is_array($project_phids)) { + return $edge_data; + } + foreach ($project_phids as $project_phid) { if (!is_scalar($project_phid)) { continue; } + $edge_data[$project_phid] = array( 'src' => $task_phid, 'type' => PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 'dst' => $project_phid, ); } + return $edge_data; } From 309aadc595a1b9c9791b4ede2509c8d006913965 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 23 Sep 2015 20:48:51 -0700 Subject: [PATCH 26/43] Rename Drydock Lease STATUS_EXPIRED to STATUS_DESTROYED Summary: Ref T9252. This is now more consistent (same as the equivalent Resource state) and accurate (leases can end up in this state a bunch of ways, including by expiring). Test Plan: `grep`, browsed around web UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14150 --- .../drydock/constants/DrydockLeaseStatus.php | 26 +++++++++---------- .../controller/DrydockLeaseViewController.php | 23 +--------------- .../drydock/storage/DrydockLease.php | 5 ++-- .../worker/DrydockLeaseDestroyWorker.php | 4 +-- 4 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/applications/drydock/constants/DrydockLeaseStatus.php b/src/applications/drydock/constants/DrydockLeaseStatus.php index 645c1c1708..06794d898e 100644 --- a/src/applications/drydock/constants/DrydockLeaseStatus.php +++ b/src/applications/drydock/constants/DrydockLeaseStatus.php @@ -2,21 +2,21 @@ final class DrydockLeaseStatus extends DrydockConstants { - const STATUS_PENDING = 0; - const STATUS_ACQUIRED = 5; - const STATUS_ACTIVE = 1; - const STATUS_RELEASED = 2; - const STATUS_BROKEN = 3; - const STATUS_EXPIRED = 4; + const STATUS_PENDING = 0; + const STATUS_ACQUIRED = 5; + const STATUS_ACTIVE = 1; + const STATUS_RELEASED = 2; + const STATUS_BROKEN = 3; + const STATUS_DESTROYED = 4; public static function getNameForStatus($status) { $map = array( - self::STATUS_PENDING => pht('Pending'), - self::STATUS_ACQUIRED => pht('Acquired'), - self::STATUS_ACTIVE => pht('Active'), - self::STATUS_RELEASED => pht('Released'), - self::STATUS_BROKEN => pht('Broken'), - self::STATUS_EXPIRED => pht('Expired'), + self::STATUS_PENDING => pht('Pending'), + self::STATUS_ACQUIRED => pht('Acquired'), + self::STATUS_ACTIVE => pht('Active'), + self::STATUS_RELEASED => pht('Released'), + self::STATUS_BROKEN => pht('Broken'), + self::STATUS_DESTROYED => pht('Destroyed'), ); return idx($map, $status, pht('Unknown')); @@ -29,7 +29,7 @@ final class DrydockLeaseStatus extends DrydockConstants { self::STATUS_ACTIVE, self::STATUS_RELEASED, self::STATUS_BROKEN, - self::STATUS_EXPIRED, + self::STATUS_DESTROYED, ); } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 42f675a8c0..d068602bd8 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -102,30 +102,9 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $view = new PHUIPropertyListView(); $view->setActionList($actions); - switch ($lease->getStatus()) { - case DrydockLeaseStatus::STATUS_ACTIVE: - $status = pht('Active'); - break; - case DrydockLeaseStatus::STATUS_RELEASED: - $status = pht('Released'); - break; - case DrydockLeaseStatus::STATUS_EXPIRED: - $status = pht('Expired'); - break; - case DrydockLeaseStatus::STATUS_PENDING: - $status = pht('Pending'); - break; - case DrydockLeaseStatus::STATUS_BROKEN: - $status = pht('Broken'); - break; - default: - $status = pht('Unknown'); - break; - } - $view->addProperty( pht('Status'), - $status); + DrydockLeaseStatus::getNameForStatus($lease->getStatus())); $view->addProperty( pht('Resource Type'), diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index a2a6abdfa2..8d88b5760b 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -160,8 +160,8 @@ final class DrydockLease extends DrydockDAO return; case DrydockLeaseStatus::STATUS_RELEASED: throw new Exception(pht('Lease has already been released!')); - case DrydockLeaseStatus::STATUS_EXPIRED: - throw new Exception(pht('Lease has already expired!')); + case DrydockLeaseStatus::STATUS_DESTROYED: + throw new Exception(pht('Lease has already been destroyed!')); case DrydockLeaseStatus::STATUS_BROKEN: throw new Exception(pht('Lease has been broken!')); case DrydockLeaseStatus::STATUS_PENDING: @@ -289,6 +289,7 @@ final class DrydockLease extends DrydockDAO switch ($this->getStatus()) { case DrydockLeaseStatus::STATUS_RELEASED: + case DrydockLeaseStatus::STATUS_DESTROYED: return false; default: return true; diff --git a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php index c019f29cb7..e0b15095c7 100644 --- a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php @@ -29,10 +29,8 @@ final class DrydockLeaseDestroyWorker extends DrydockWorker { $blueprint->destroyLease($resource, $lease); - // TODO: Rename DrydockLeaseStatus::STATUS_EXPIRED to STATUS_DESTROYED. - $lease - ->setStatus(DrydockLeaseStatus::STATUS_EXPIRED) + ->setStatus(DrydockLeaseStatus::STATUS_DESTROYED) ->save(); } From c6aade439283e0fad248b91ad9ff17cbf60b38a3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 04:19:27 -0700 Subject: [PATCH 27/43] Give Drydock leases a resourcePHID instead of a resourceID Summary: Ref T9252. Leases currently have a `resourceID`, but this is a bit nonstandard and generally less flexible than giving them a `resourcePHID`. In particular, a `resourcePHID` is easier to use when rendering interfaces, since you can get handles out of a PHID. Add a PHID column, copy over all the PHIDs that correspond to existing IDs, then drop the ID column. Test Plan: - Browsed web UIs. - Inspected database during/after migration. - Grepped for `resourceID`. - Allocated a new lease with `bin/drydock lease`. Reviewers: chad, hach-que Reviewed By: hach-que Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14151 --- .../20150923.drydock.resourceid.1.sql | 2 ++ .../20150923.drydock.resourceid.2.sql | 5 ++++ .../20150923.drydock.resourceid.3.sql | 2 ++ .../controller/DrydockLeaseViewController.php | 17 ++++--------- .../DrydockResourceViewController.php | 2 +- .../drydock/query/DrydockLeaseQuery.php | 24 ++++++++++--------- .../drydock/storage/DrydockLease.php | 12 ++++------ .../drydock/worker/DrydockAllocatorWorker.php | 6 ++--- .../drydock/worker/DrydockLeaseWorker.php | 11 ++------- .../worker/DrydockResourceUpdateWorker.php | 2 +- 10 files changed, 39 insertions(+), 44 deletions(-) create mode 100644 resources/sql/autopatches/20150923.drydock.resourceid.1.sql create mode 100644 resources/sql/autopatches/20150923.drydock.resourceid.2.sql create mode 100644 resources/sql/autopatches/20150923.drydock.resourceid.3.sql diff --git a/resources/sql/autopatches/20150923.drydock.resourceid.1.sql b/resources/sql/autopatches/20150923.drydock.resourceid.1.sql new file mode 100644 index 0000000000..ad87d64669 --- /dev/null +++ b/resources/sql/autopatches/20150923.drydock.resourceid.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + ADD resourcePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20150923.drydock.resourceid.2.sql b/resources/sql/autopatches/20150923.drydock.resourceid.2.sql new file mode 100644 index 0000000000..22f6d32d47 --- /dev/null +++ b/resources/sql/autopatches/20150923.drydock.resourceid.2.sql @@ -0,0 +1,5 @@ +UPDATE + {$NAMESPACE}_drydock.drydock_lease l, + {$NAMESPACE}_drydock.drydock_resource r + SET l.resourcePHID = r.phid + WHERE l.resourceID = r.id; diff --git a/resources/sql/autopatches/20150923.drydock.resourceid.3.sql b/resources/sql/autopatches/20150923.drydock.resourceid.3.sql new file mode 100644 index 0000000000..f3520fa510 --- /dev/null +++ b/resources/sql/autopatches/20150923.drydock.resourceid.3.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + DROP resourceID; diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index d068602bd8..bb748cceee 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -110,20 +110,13 @@ final class DrydockLeaseViewController extends DrydockLeaseController { pht('Resource Type'), $lease->getResourceType()); - $resource = id(new DrydockResourceQuery()) - ->setViewer($this->getViewer()) - ->withIDs(array($lease->getResourceID())) - ->executeOne(); - - if ($resource !== null) { - $view->addProperty( - pht('Resource'), - $this->getViewer()->renderHandle($resource->getPHID())); + $resource_phid = $lease->getResourcePHID(); + if ($resource_phid) { + $resource_display = $viewer->renderHandle($resource_phid); } else { - $view->addProperty( - pht('Resource'), - pht('No Resource')); + $resource_display = phutil_tag('em', array(), pht('No Resource')); } + $view->addProperty(pht('Resource'), $resource_display); $until = $lease->getUntil(); if ($until) { diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 40009e34ce..659f7b4fc1 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -27,7 +27,7 @@ final class DrydockResourceViewController extends DrydockResourceController { $leases = id(new DrydockLeaseQuery()) ->setViewer($viewer) - ->withResourceIDs(array($resource->getID())) + ->withResourcePHIDs(array($resource->getPHID())) ->execute(); $lease_list = id(new DrydockLeaseListView()) diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index a212a27ca8..0d12a1fa21 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -4,7 +4,7 @@ final class DrydockLeaseQuery extends DrydockQuery { private $ids; private $phids; - private $resourceIDs; + private $resourcePHIDs; private $statuses; private $datasourceQuery; private $needCommands; @@ -19,8 +19,8 @@ final class DrydockLeaseQuery extends DrydockQuery { return $this; } - public function withResourceIDs(array $ids) { - $this->resourceIDs = $ids; + public function withResourcePHIDs(array $phids) { + $this->resourcePHIDs = $phids; return $this; } @@ -43,22 +43,24 @@ final class DrydockLeaseQuery extends DrydockQuery { } protected function willFilterPage(array $leases) { - $resource_ids = array_filter(mpull($leases, 'getResourceID')); - if ($resource_ids) { + $resource_phids = array_filter(mpull($leases, 'getResourcePHID')); + if ($resource_phids) { $resources = id(new DrydockResourceQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) - ->withIDs(array_unique($resource_ids)) + ->withPHIDs(array_unique($resource_phids)) ->execute(); + $resources = mpull($resources, null, 'getPHID'); } else { $resources = array(); } foreach ($leases as $key => $lease) { $resource = null; - if ($lease->getResourceID()) { - $resource = idx($resources, $lease->getResourceID()); + if ($lease->getResourcePHID()) { + $resource = idx($resources, $lease->getResourcePHID()); if (!$resource) { + $this->didRejectResult($lease); unset($leases[$key]); continue; } @@ -72,11 +74,11 @@ final class DrydockLeaseQuery extends DrydockQuery { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); - if ($this->resourceIDs !== null) { + if ($this->resourcePHIDs !== null) { $where[] = qsprintf( $conn, - 'resourceID IN (%Ld)', - $this->resourceIDs); + 'resourcePHID IN (%Ls)', + $this->resourcePHIDs); } if ($this->ids !== null) { diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 8d88b5760b..b0c084d56f 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -3,7 +3,7 @@ final class DrydockLease extends DrydockDAO implements PhabricatorPolicyInterface { - protected $resourceID; + protected $resourcePHID; protected $resourceType; protected $until; protected $ownerPHID; @@ -64,13 +64,11 @@ final class DrydockLease extends DrydockDAO 'until' => 'epoch?', 'resourceType' => 'text128', 'ownerPHID' => 'phid?', - 'resourceID' => 'id?', + 'resourcePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, + 'key_resource' => array( + 'columns' => array('resourcePHID', 'status'), ), ), ) + parent::getConfiguration(); @@ -219,7 +217,7 @@ final class DrydockLease extends DrydockDAO $this->openTransaction(); $this - ->setResourceID($resource->getID()) + ->setResourcePHID($resource->getPHID()) ->setStatus($new_status) ->save(); diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index 4288662434..2c6caa41b5 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -462,10 +462,10 @@ final class DrydockAllocatorWorker extends DrydockWorker { 'acquireLease()')); } - $lease_id = $lease->getResourceID(); - $resource_id = $resource->getID(); + $lease_phid = $lease->getResourcePHID(); + $resource_phid = $resource->getPHID(); - if ($lease_id !== $resource_id) { + if ($lease_phid !== $resource_phid) { // TODO: Destroy the lease? throw new Exception( pht( diff --git a/src/applications/drydock/worker/DrydockLeaseWorker.php b/src/applications/drydock/worker/DrydockLeaseWorker.php index 2143a4e4cf..82a5d1891e 100644 --- a/src/applications/drydock/worker/DrydockLeaseWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseWorker.php @@ -20,17 +20,10 @@ final class DrydockLeaseWorker extends DrydockWorker { $actual_status)); } - $resource_id = $lease->getResourceID(); - - $resource = id(new DrydockResourceQuery()) - ->setViewer($this->getViewer()) - ->withIDs(array($resource_id)) - ->executeOne(); + $resource = $lease->getResource(); if (!$resource) { throw new PhabricatorWorkerPermanentFailureException( - pht( - 'Trying to activate lease on invalid resource ("%s").', - $resource_id)); + pht('Trying to activate lease with no resource.')); } $resource_status = $resource->getStatus(); diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 7528df8d12..532090bf35 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -72,7 +72,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $leases = id(new DrydockLeaseQuery()) ->setViewer($viewer) - ->withResourceIDs(array($resource->getID())) + ->withResourcePHIDs(array($resource->getPHID())) ->withStatuses($statuses) ->execute(); From e117ace8c7fb2305183b31a3c19cab88bba6816f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 07:57:05 -0700 Subject: [PATCH 28/43] Convert Drydock lease and resource constants to strings Summary: Ref T9252. Drydock currently uses integer statuses, but there's no reason for this (they don't need to be ordered) and it makes debugging them, working with them, future APIs, etc., more cumbersome. Switch to string instead. Also rename `STATUS_OPEN` to `STATUS_ACTIVE` and `STATUS_CLOSED` to `STATUS_RELEASED` for consistency. This makes resources and leases have more similar states, and gives resource states more accurate names. Test Plan: Browsed web UI, grepped for changed constants, applied patch, inspected database. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14153 --- .../autopatches/20150924.drydock.status.1.sql | 39 +++++++++++++++++++ ...anacServiceHostBlueprintImplementation.php | 4 +- .../drydock/constants/DrydockLeaseStatus.php | 12 +++--- .../constants/DrydockResourceStatus.php | 24 ++++++------ .../drydock/query/DrydockLeaseQuery.php | 2 +- .../query/DrydockResourceSearchEngine.php | 2 +- .../drydock/storage/DrydockLease.php | 2 +- .../drydock/storage/DrydockResource.php | 17 ++++---- .../drydock/view/DrydockResourceListView.php | 2 +- .../drydock/worker/DrydockAllocatorWorker.php | 2 +- .../drydock/worker/DrydockLeaseWorker.php | 2 +- .../worker/DrydockResourceDestroyWorker.php | 2 +- .../worker/DrydockResourceUpdateWorker.php | 6 +-- 13 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 resources/sql/autopatches/20150924.drydock.status.1.sql diff --git a/resources/sql/autopatches/20150924.drydock.status.1.sql b/resources/sql/autopatches/20150924.drydock.status.1.sql new file mode 100644 index 0000000000..dc8ec2bd22 --- /dev/null +++ b/resources/sql/autopatches/20150924.drydock.status.1.sql @@ -0,0 +1,39 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + CHANGE status status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; + +UPDATE {$NAMESPACE}_drydock.drydock_lease + SET status = 'pending' WHERE status = '0'; + +UPDATE {$NAMESPACE}_drydock.drydock_lease + SET status = 'acquired' WHERE status = '5'; + +UPDATE {$NAMESPACE}_drydock.drydock_lease + SET status = 'active' WHERE status = '1'; + +UPDATE {$NAMESPACE}_drydock.drydock_lease + SET status = 'released' WHERE status = '2'; + +UPDATE {$NAMESPACE}_drydock.drydock_lease + SET status = 'broken' WHERE status = '3'; + +UPDATE {$NAMESPACE}_drydock.drydock_lease + SET status = 'destroyed' WHERE status = '4'; + + +ALTER TABLE {$NAMESPACE}_drydock.drydock_resource + CHANGE status status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; + +UPDATE {$NAMESPACE}_drydock.drydock_resource + SET status = 'pending' WHERE status = '0'; + +UPDATE {$NAMESPACE}_drydock.drydock_resource + SET status = 'active' WHERE status = '1'; + +UPDATE {$NAMESPACE}_drydock.drydock_resource + SET status = 'released' WHERE status = '2'; + +UPDATE {$NAMESPACE}_drydock.drydock_resource + SET status = 'broken' WHERE status = '3'; + +UPDATE {$NAMESPACE}_drydock.drydock_resource + SET status = 'destroyed' WHERE status = '4'; diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index d906df5b04..4ae64b39be 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -252,8 +252,8 @@ final class DrydockAlmanacServiceHostBlueprintImplementation ->withStatuses( array( DrydockResourceStatus::STATUS_PENDING, - DrydockResourceStatus::STATUS_OPEN, - DrydockResourceStatus::STATUS_CLOSED, + DrydockResourceStatus::STATUS_ACTIVE, + DrydockResourceStatus::STATUS_RELEASED, )) ->execute(); diff --git a/src/applications/drydock/constants/DrydockLeaseStatus.php b/src/applications/drydock/constants/DrydockLeaseStatus.php index 06794d898e..39f4ce5e1b 100644 --- a/src/applications/drydock/constants/DrydockLeaseStatus.php +++ b/src/applications/drydock/constants/DrydockLeaseStatus.php @@ -2,12 +2,12 @@ final class DrydockLeaseStatus extends DrydockConstants { - const STATUS_PENDING = 0; - const STATUS_ACQUIRED = 5; - const STATUS_ACTIVE = 1; - const STATUS_RELEASED = 2; - const STATUS_BROKEN = 3; - const STATUS_DESTROYED = 4; + const STATUS_PENDING = 'pending'; + const STATUS_ACQUIRED = 'acquired'; + const STATUS_ACTIVE = 'active'; + const STATUS_RELEASED = 'released'; + const STATUS_BROKEN = 'broken'; + const STATUS_DESTROYED = 'destroyed'; public static function getNameForStatus($status) { $map = array( diff --git a/src/applications/drydock/constants/DrydockResourceStatus.php b/src/applications/drydock/constants/DrydockResourceStatus.php index d653138faf..d88f77625a 100644 --- a/src/applications/drydock/constants/DrydockResourceStatus.php +++ b/src/applications/drydock/constants/DrydockResourceStatus.php @@ -2,19 +2,19 @@ final class DrydockResourceStatus extends DrydockConstants { - const STATUS_PENDING = 0; - const STATUS_OPEN = 1; - const STATUS_CLOSED = 2; - const STATUS_BROKEN = 3; - const STATUS_DESTROYED = 4; + const STATUS_PENDING = 'pending'; + const STATUS_ACTIVE = 'active'; + const STATUS_RELEASED = 'released'; + const STATUS_BROKEN = 'broken'; + const STATUS_DESTROYED = 'destroyed'; public static function getNameForStatus($status) { $map = array( - self::STATUS_PENDING => pht('Pending'), - self::STATUS_OPEN => pht('Open'), - self::STATUS_CLOSED => pht('Closed'), - self::STATUS_BROKEN => pht('Broken'), - self::STATUS_DESTROYED => pht('Destroyed'), + self::STATUS_PENDING => pht('Pending'), + self::STATUS_ACTIVE => pht('Active'), + self::STATUS_RELEASED => pht('Released'), + self::STATUS_BROKEN => pht('Broken'), + self::STATUS_DESTROYED => pht('Destroyed'), ); return idx($map, $status, pht('Unknown')); @@ -23,8 +23,8 @@ final class DrydockResourceStatus extends DrydockConstants { public static function getAllStatuses() { return array( self::STATUS_PENDING, - self::STATUS_OPEN, - self::STATUS_CLOSED, + self::STATUS_ACTIVE, + self::STATUS_RELEASED, self::STATUS_BROKEN, self::STATUS_DESTROYED, ); diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index 0d12a1fa21..c7c770bbea 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -98,7 +98,7 @@ final class DrydockLeaseQuery extends DrydockQuery { if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ld)', + 'status IN (%Ls)', $this->statuses); } diff --git a/src/applications/drydock/query/DrydockResourceSearchEngine.php b/src/applications/drydock/query/DrydockResourceSearchEngine.php index 5a47e40721..2ebd3971d0 100644 --- a/src/applications/drydock/query/DrydockResourceSearchEngine.php +++ b/src/applications/drydock/query/DrydockResourceSearchEngine.php @@ -73,7 +73,7 @@ final class DrydockResourceSearchEngine 'statuses', array( DrydockResourceStatus::STATUS_PENDING, - DrydockResourceStatus::STATUS_OPEN, + DrydockResourceStatus::STATUS_ACTIVE, )); case 'all': return $query; diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index b0c084d56f..afe618c77e 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -60,7 +60,7 @@ final class DrydockLease extends DrydockDAO 'attributes' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'status' => 'uint32', + 'status' => 'text32', 'until' => 'epoch?', 'resourceType' => 'text128', 'ownerPHID' => 'phid?', diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index fefb6e7516..a2962ee293 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -30,14 +30,15 @@ final class DrydockResource extends DrydockDAO self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text255', 'ownerPHID' => 'phid?', - 'status' => 'uint32', + 'status' => 'text32', 'type' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, + 'key_type' => array( + 'columns' => array('type', 'status'), + ), + 'key_blueprint' => array( + 'columns' => array('blueprintPHID', 'status'), ), ), ) + parent::getConfiguration(); @@ -107,7 +108,7 @@ final class DrydockResource extends DrydockDAO } if ($this->activateWhenAllocated) { - $new_status = DrydockResourceStatus::STATUS_OPEN; + $new_status = DrydockResourceStatus::STATUS_ACTIVE; } else { $new_status = DrydockResourceStatus::STATUS_PENDING; } @@ -153,7 +154,7 @@ final class DrydockResource extends DrydockDAO $this->openTransaction(); $this - ->setStatus(DrydockResourceStatus::STATUS_OPEN) + ->setStatus(DrydockResourceStatus::STATUS_ACTIVE) ->save(); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); @@ -172,7 +173,7 @@ final class DrydockResource extends DrydockDAO public function canRelease() { switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_CLOSED: + case DrydockResourceStatus::STATUS_RELEASED: case DrydockResourceStatus::STATUS_DESTROYED: return false; default: diff --git a/src/applications/drydock/view/DrydockResourceListView.php b/src/applications/drydock/view/DrydockResourceListView.php index 92ec762b81..9b7706c38b 100644 --- a/src/applications/drydock/view/DrydockResourceListView.php +++ b/src/applications/drydock/view/DrydockResourceListView.php @@ -29,7 +29,7 @@ final class DrydockResourceListView extends AphrontView { case DrydockResourceStatus::STATUS_PENDING: $item->setStatusIcon('fa-dot-circle-o yellow'); break; - case DrydockResourceStatus::STATUS_OPEN: + case DrydockResourceStatus::STATUS_ACTIVE: $item->setStatusIcon('fa-dot-circle-o green'); break; case DrydockResourceStatus::STATUS_DESTROYED: diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index 2c6caa41b5..ec676bf2fb 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -229,7 +229,7 @@ final class DrydockAllocatorWorker extends DrydockWorker { ->withStatuses( array( DrydockResourceStatus::STATUS_PENDING, - DrydockResourceStatus::STATUS_OPEN, + DrydockResourceStatus::STATUS_ACTIVE, )) ->execute(); diff --git a/src/applications/drydock/worker/DrydockLeaseWorker.php b/src/applications/drydock/worker/DrydockLeaseWorker.php index 82a5d1891e..5919c59613 100644 --- a/src/applications/drydock/worker/DrydockLeaseWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseWorker.php @@ -34,7 +34,7 @@ final class DrydockLeaseWorker extends DrydockWorker { throw new Exception(pht('Resource still activating.')); } - if ($resource_status != DrydockResourceStatus::STATUS_OPEN) { + if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) { throw new PhabricatorWorkerPermanentFailureException( pht( 'Trying to activate lease on a dead resource (in status "%s").', diff --git a/src/applications/drydock/worker/DrydockResourceDestroyWorker.php b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php index 812edc682a..af00ebeb2a 100644 --- a/src/applications/drydock/worker/DrydockResourceDestroyWorker.php +++ b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php @@ -12,7 +12,7 @@ final class DrydockResourceDestroyWorker extends DrydockWorker { $status = $resource->getStatus(); switch ($status) { - case DrydockResourceStatus::STATUS_CLOSED: + case DrydockResourceStatus::STATUS_RELEASED: case DrydockResourceStatus::STATUS_BROKEN: break; default: diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php index 532090bf35..4afc51bc23 100644 --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -20,7 +20,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { private function updateResource(DrydockResource $resource) { $commands = $this->loadCommands($resource->getPHID()); foreach ($commands as $command) { - if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { + if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) { // Resources can't receive commands before they activate or after they // release. break; @@ -46,7 +46,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { } private function releaseResource(DrydockResource $resource) { - if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { + if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) { // If we had multiple release commands // This command is only meaningful to resources in the "Open" state. return; @@ -57,7 +57,7 @@ final class DrydockResourceUpdateWorker extends DrydockWorker { $resource->openTransaction(); $resource - ->setStatus(DrydockResourceStatus::STATUS_CLOSED) + ->setStatus(DrydockResourceStatus::STATUS_RELEASED) ->save(); // TODO: Hold slot locks until destruction? From b71ce90b9cc13140cf0a924808612c032040ec96 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 09:56:35 -0700 Subject: [PATCH 29/43] Straighten out Drydock policies for Resources Summary: Ref T9252. Resources always have a corresponding blueprint, and it makes sense to use the same policies for both. Test Plan: Viewed resources in web UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14154 --- .../controller/DrydockResourceViewController.php | 6 ++++-- .../drydock/storage/DrydockResource.php | 13 +++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 659f7b4fc1..be1db24a0c 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -17,6 +17,8 @@ final class DrydockResourceViewController extends DrydockResourceController { $title = pht('Resource %s %s', $resource->getID(), $resource->getName()); $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($resource) ->setHeader($title); $actions = $this->buildActionListView($resource); @@ -117,8 +119,8 @@ final class DrydockResourceViewController extends DrydockResourceController { PhabricatorActionListView $actions) { $viewer = $this->getViewer(); - $view = new PHUIPropertyListView(); - $view->setActionList($actions); + $view = id(new PHUIPropertyListView()) + ->setActionList($actions); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index a2962ee293..f2be89a6f2 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -223,19 +223,16 @@ final class DrydockResource extends DrydockDAO } public function getPolicy($capability) { - switch ($capability) { - case PhabricatorPolicyCapability::CAN_VIEW: - case PhabricatorPolicyCapability::CAN_EDIT: - // TODO: Implement reasonable policies. - return PhabricatorPolicies::getMostOpenPolicy(); - } + return $this->getBlueprint()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return false; + return $this->getBlueprint()->hasAutomaticCapability( + $capability, + $viewer); } public function describeAutomaticCapability($capability) { - return null; + return pht('Resources inherit the policies of their blueprints.'); } } From 1491269b72e4688ba275645263a4f806da926d2c Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 09:56:49 -0700 Subject: [PATCH 30/43] Modernize Drydock SearchEngine implementations Summary: Ref T9252. Move these to the more modern stuff to pick up ordering and interface support for free. Also work around the blueprint / custom field integration a little more gracefully. Test Plan: Searched for blueprints, resources and leases. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14155 --- .../drydock/constants/DrydockLeaseStatus.php | 16 +++---- .../constants/DrydockResourceStatus.php | 15 +++---- .../DrydockBlueprintCoreCustomField.php | 6 +++ .../drydock/query/DrydockBlueprintQuery.php | 17 ------- .../query/DrydockBlueprintSearchEngine.php | 16 ++++--- .../query/DrydockLeaseSearchEngine.php | 45 ++++++------------- .../query/DrydockResourceSearchEngine.php | 44 ++++++------------ .../drydock/storage/DrydockBlueprint.php | 4 ++ 8 files changed, 57 insertions(+), 106 deletions(-) diff --git a/src/applications/drydock/constants/DrydockLeaseStatus.php b/src/applications/drydock/constants/DrydockLeaseStatus.php index 39f4ce5e1b..f37e4ab9be 100644 --- a/src/applications/drydock/constants/DrydockLeaseStatus.php +++ b/src/applications/drydock/constants/DrydockLeaseStatus.php @@ -9,8 +9,8 @@ final class DrydockLeaseStatus extends DrydockConstants { const STATUS_BROKEN = 'broken'; const STATUS_DESTROYED = 'destroyed'; - public static function getNameForStatus($status) { - $map = array( + public static function getStatusMap() { + return array( self::STATUS_PENDING => pht('Pending'), self::STATUS_ACQUIRED => pht('Acquired'), self::STATUS_ACTIVE => pht('Active'), @@ -18,19 +18,15 @@ final class DrydockLeaseStatus extends DrydockConstants { self::STATUS_BROKEN => pht('Broken'), self::STATUS_DESTROYED => pht('Destroyed'), ); + } + public static function getNameForStatus($status) { + $map = self::getStatusMap(); return idx($map, $status, pht('Unknown')); } public static function getAllStatuses() { - return array( - self::STATUS_PENDING, - self::STATUS_ACQUIRED, - self::STATUS_ACTIVE, - self::STATUS_RELEASED, - self::STATUS_BROKEN, - self::STATUS_DESTROYED, - ); + return array_keys(self::getStatusMap()); } } diff --git a/src/applications/drydock/constants/DrydockResourceStatus.php b/src/applications/drydock/constants/DrydockResourceStatus.php index d88f77625a..d8a860d6a0 100644 --- a/src/applications/drydock/constants/DrydockResourceStatus.php +++ b/src/applications/drydock/constants/DrydockResourceStatus.php @@ -8,26 +8,23 @@ final class DrydockResourceStatus extends DrydockConstants { const STATUS_BROKEN = 'broken'; const STATUS_DESTROYED = 'destroyed'; - public static function getNameForStatus($status) { - $map = array( + public static function getStatusMap() { + return array( self::STATUS_PENDING => pht('Pending'), self::STATUS_ACTIVE => pht('Active'), self::STATUS_RELEASED => pht('Released'), self::STATUS_BROKEN => pht('Broken'), self::STATUS_DESTROYED => pht('Destroyed'), ); + } + public static function getNameForStatus($status) { + $map = self::getStatusMap(); return idx($map, $status, pht('Unknown')); } public static function getAllStatuses() { - return array( - self::STATUS_PENDING, - self::STATUS_ACTIVE, - self::STATUS_RELEASED, - self::STATUS_BROKEN, - self::STATUS_DESTROYED, - ); + return array_keys(self::getStatusMap()); } } diff --git a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php index 4317242c10..f4e6ba3a27 100644 --- a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php +++ b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php @@ -9,6 +9,12 @@ final class DrydockBlueprintCoreCustomField } public function createFields($object) { + // If this is a generic object without an attached implementation (for + // example, via ApplicationSearch), just don't build any custom fields. + if (!$object->hasImplementation()) { + return array(); + } + $impl = $object->getImplementation(); $specs = $impl->getFieldSpecifications(); diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php index c8e53217c9..7404c9457a 100644 --- a/src/applications/drydock/query/DrydockBlueprintQuery.php +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -85,21 +85,4 @@ final class DrydockBlueprintQuery extends DrydockQuery { return $where; } - public function getOrderableColumns() { - // TODO: Blueprints implement CustomFields, but can not be ordered by - // custom field classes because the custom fields are not global. There - // is no graceful way to handle this in ApplicationSearch at the moment. - // Just brute force around it until we can clean this up. - - return array( - 'id' => array( - 'table' => $this->getPrimaryTableAlias(), - 'column' => 'id', - 'reverse' => false, - 'type' => 'int', - 'unique' => true, - ), - ); - } - } diff --git a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php index ccb4c80803..a0420c610c 100644 --- a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php +++ b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php @@ -11,17 +11,19 @@ final class DrydockBlueprintSearchEngine return 'PhabricatorDrydockApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - return new PhabricatorSavedQuery(); + public function newQuery() { + return id(new DrydockBlueprintQuery()); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - return new DrydockBlueprintQuery(); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) {} + protected function buildCustomSearchFields() { + return array(); + } protected function getURI($path) { return '/drydock/blueprint/'.$path; diff --git a/src/applications/drydock/query/DrydockLeaseSearchEngine.php b/src/applications/drydock/query/DrydockLeaseSearchEngine.php index eb5338b2cb..7d85ddbe70 100644 --- a/src/applications/drydock/query/DrydockLeaseSearchEngine.php +++ b/src/applications/drydock/query/DrydockLeaseSearchEngine.php @@ -11,46 +11,27 @@ final class DrydockLeaseSearchEngine return 'PhabricatorDrydockApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'statuses', - $this->readListFromRequest($request, 'statuses')); - - return $saved; + public function newQuery() { + return new DrydockLeaseQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new DrydockLeaseQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $statuses = $saved->getParameter('statuses', array()); - if ($statuses) { - $query->withStatuses($statuses); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $statuses = $saved->getParameter('statuses', array()); - - $status_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Status')); - foreach (DrydockLeaseStatus::getAllStatuses() as $status) { - $status_control->addCheckbox( - 'statuses[]', - $status, - DrydockLeaseStatus::getNameForStatus($status), - in_array($status, $statuses)); - } - - $form - ->appendChild($status_control); - + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Statuses')) + ->setKey('statuses') + ->setOptions(DrydockLeaseStatus::getStatusMap()), + ); } protected function getURI($path) { diff --git a/src/applications/drydock/query/DrydockResourceSearchEngine.php b/src/applications/drydock/query/DrydockResourceSearchEngine.php index 2ebd3971d0..7b0bca0324 100644 --- a/src/applications/drydock/query/DrydockResourceSearchEngine.php +++ b/src/applications/drydock/query/DrydockResourceSearchEngine.php @@ -11,45 +11,27 @@ final class DrydockResourceSearchEngine return 'PhabricatorDrydockApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'statuses', - $this->readListFromRequest($request, 'statuses')); - - return $saved; + public function newQuery() { + return new DrydockResourceQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new DrydockResourceQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $statuses = $saved->getParameter('statuses', array()); - if ($statuses) { - $query->withStatuses($statuses); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $statuses = $saved->getParameter('statuses', array()); - - $status_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Status')); - foreach (DrydockResourceStatus::getAllStatuses() as $status) { - $status_control->addCheckbox( - 'statuses[]', - $status, - DrydockResourceStatus::getNameForStatus($status), - in_array($status, $statuses)); - } - - $form - ->appendChild($status_control); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Statuses')) + ->setKey('statuses') + ->setOptions(DrydockResourceStatus::getStatusMap()), + ); } protected function getURI($path) { diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index ed83ded571..c687c996a9 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -64,6 +64,10 @@ final class DrydockBlueprint extends DrydockDAO return $this; } + public function hasImplementation() { + return ($this->implementation !== self::ATTACHABLE); + } + public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } From b441e8b81e3154c9ccd2fa084f07a69c30864bf3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 10:18:17 -0700 Subject: [PATCH 31/43] Allow Drydock blueprints to be disabled Summary: Ref T9252. If you have a blueprint and you do not like that blueprint very much, you can disable it. Test Plan: Disabled / enabled some blueprints. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14156 --- .../20150924.drydock.disable.1.sql | 2 + src/__phutil_library_map__.php | 2 + .../PhabricatorDrydockApplication.php | 12 +++- .../DrydockBlueprintDisableController.php | 64 +++++++++++++++++++ .../DrydockBlueprintEditController.php | 6 +- .../DrydockBlueprintViewController.php | 32 ++++++++-- .../drydock/editor/DrydockBlueprintEditor.php | 34 ++++++---- .../drydock/query/DrydockBlueprintQuery.php | 13 ++++ .../query/DrydockBlueprintSearchEngine.php | 23 ++++++- .../drydock/storage/DrydockBlueprint.php | 5 +- .../storage/DrydockBlueprintTransaction.php | 13 +++- .../drydock/worker/DrydockAllocatorWorker.php | 4 +- 12 files changed, 183 insertions(+), 27 deletions(-) create mode 100644 resources/sql/autopatches/20150924.drydock.disable.1.sql create mode 100644 src/applications/drydock/controller/DrydockBlueprintDisableController.php diff --git a/resources/sql/autopatches/20150924.drydock.disable.1.sql b/resources/sql/autopatches/20150924.drydock.disable.1.sql new file mode 100644 index 0000000000..6e5dafe5ec --- /dev/null +++ b/resources/sql/autopatches/20150924.drydock.disable.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_blueprint + ADD isDisabled BOOL NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 11e5134628..5cc4d58561 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -805,6 +805,7 @@ phutil_register_library_map(array( 'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php', 'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php', 'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php', + 'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php', 'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php', 'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php', 'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php', @@ -4530,6 +4531,7 @@ phutil_register_library_map(array( 'DrydockBlueprintCreateController' => 'DrydockBlueprintController', 'DrydockBlueprintCustomField' => 'PhabricatorCustomField', 'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource', + 'DrydockBlueprintDisableController' => 'DrydockBlueprintController', 'DrydockBlueprintEditController' => 'DrydockBlueprintController', 'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor', 'DrydockBlueprintImplementation' => 'Phobject', diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index d3a543a4f0..afc4219d1f 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -49,7 +49,11 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '' => 'DrydockConsoleController', 'blueprint/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockBlueprintListController', - '(?P[1-9]\d*)/' => 'DrydockBlueprintViewController', + '(?P[1-9]\d*)/' => array( + '' => 'DrydockBlueprintViewController', + '(?Pdisable|enable)/' => + 'DrydockBlueprintDisableController', + ), 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ), @@ -62,8 +66,10 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { ), 'lease/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', - '(?P[1-9]\d*)/' => 'DrydockLeaseViewController', - '(?P[1-9]\d*)/release/' => 'DrydockLeaseReleaseController', + '(?P[1-9]\d*)/' => array( + '' => 'DrydockLeaseViewController', + 'release/' => 'DrydockLeaseReleaseController', + ), ), 'log/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', diff --git a/src/applications/drydock/controller/DrydockBlueprintDisableController.php b/src/applications/drydock/controller/DrydockBlueprintDisableController.php new file mode 100644 index 0000000000..525e55228b --- /dev/null +++ b/src/applications/drydock/controller/DrydockBlueprintDisableController.php @@ -0,0 +1,64 @@ +getViewer(); + $id = $request->getURIData('id'); + + $blueprint = id(new DrydockBlueprintQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$blueprint) { + return new Aphront404Response(); + } + + $is_disable = ($request->getURIData('action') == 'disable'); + $id = $blueprint->getID(); + $cancel_uri = $this->getApplicationURI("blueprint/{$id}/"); + + if ($request->isFormPost()) { + $xactions = array(); + + $xactions[] = id(new DrydockBlueprintTransaction()) + ->setTransactionType(DrydockBlueprintTransaction::TYPE_DISABLED) + ->setNewValue($is_disable ? 1 : 0); + + $editor = id(new DrydockBlueprintEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($blueprint, $xactions); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + if ($is_disable) { + $title = pht('Disable Blueprint'); + $body = pht( + 'If you disable this blueprint, Drydock will no longer use it to '. + 'allocate new resources. Existing resources will not be affected.'); + $button = pht('Disable Blueprint'); + } else { + $title = pht('Enable Blueprint'); + $body = pht( + 'If you enable this blueprint, Drydock will start using it to '. + 'allocate new resources.'); + $button = pht('Enable Blueprint'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addCancelButton($cancel_uri) + ->addSubmitButton($button); + } +} diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index 1c75bf708d..2e61ec868c 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -33,8 +33,10 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { return new Aphront400Response(); } - $blueprint = DrydockBlueprint::initializeNewBlueprint($viewer); - $blueprint->setClassName($class); + $blueprint = DrydockBlueprint::initializeNewBlueprint($viewer) + ->setClassName($class) + ->attachImplementation($impl); + $cancel_uri = $this->getApplicationURI('blueprint/'); } diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 5da92539cf..815ace7026 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -21,6 +21,12 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { ->setUser($viewer) ->setPolicyObject($blueprint); + if ($blueprint->getIsDisabled()) { + $header->setStatus('fa-ban', 'red', pht('Disabled')); + } else { + $header->setStatus('fa-check', 'bluegrey', pht('Active')); + } + $actions = $this->buildActionListView($blueprint); $properties = $this->buildPropertyListView($blueprint, $actions); @@ -84,15 +90,15 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { } private function buildActionListView(DrydockBlueprint $blueprint) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + $id = $blueprint->getID(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($blueprint); - $uri = '/blueprint/edit/'.$blueprint->getID().'/'; - $uri = $this->getApplicationURI($uri); + $edit_uri = $this->getApplicationURI("blueprint/edit/{$id}/"); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -101,12 +107,30 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $view->addAction( id(new PhabricatorActionView()) - ->setHref($uri) + ->setHref($edit_uri) ->setName(pht('Edit Blueprint')) ->setIcon('fa-pencil') ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); + if (!$blueprint->getIsDisabled()) { + $disable_name = pht('Disable Blueprint'); + $disable_icon = 'fa-ban'; + $disable_uri = $this->getApplicationURI("blueprint/{$id}/disable/"); + } else { + $disable_name = pht('Enable Blueprint'); + $disable_icon = 'fa-check'; + $disable_uri = $this->getApplicationURI("blueprint/{$id}/enable/"); + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setHref($disable_uri) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + return $view; } diff --git a/src/applications/drydock/editor/DrydockBlueprintEditor.php b/src/applications/drydock/editor/DrydockBlueprintEditor.php index b211e25aa8..b5fd584945 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditor.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditor.php @@ -16,7 +16,9 @@ final class DrydockBlueprintEditor $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = DrydockBlueprintTransaction::TYPE_NAME; + $types[] = DrydockBlueprintTransaction::TYPE_DISABLED; return $types; } @@ -28,7 +30,11 @@ final class DrydockBlueprintEditor switch ($xaction->getTransactionType()) { case DrydockBlueprintTransaction::TYPE_NAME: return $object->getBlueprintName(); + case DrydockBlueprintTransaction::TYPE_DISABLED: + return (int)$object->getIsDisabled(); } + + return parent::getCustomTransactionOldValue($object, $xaction); } protected function getCustomTransactionNewValue( @@ -38,7 +44,11 @@ final class DrydockBlueprintEditor switch ($xaction->getTransactionType()) { case DrydockBlueprintTransaction::TYPE_NAME: return $xaction->getNewValue(); + case DrydockBlueprintTransaction::TYPE_DISABLED: + return (int)$xaction->getNewValue(); } + + return parent::getCustomTransactionNewValue($object, $xaction); } protected function applyCustomInternalTransaction( @@ -48,26 +58,26 @@ final class DrydockBlueprintEditor switch ($xaction->getTransactionType()) { case DrydockBlueprintTransaction::TYPE_NAME: $object->setBlueprintName($xaction->getNewValue()); - break; + return; + case DrydockBlueprintTransaction::TYPE_DISABLED: + $object->setIsDisabled((int)$xaction->getNewValue()); + return; } + + return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - return; - } - protected function extractFilePHIDsFromCustomTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return array(); - } + switch ($xaction->getTransactionType()) { + case DrydockBlueprintTransaction::TYPE_NAME: + case DrydockBlueprintTransaction::TYPE_DISABLED: + return; + } - protected function shouldSendMail( - PhabricatorLiskDAO $object, - array $xactions) { - return false; + return parent::applyCustomExternalTransaction($object, $xaction); } } diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php index 7404c9457a..7ce5dcbe5b 100644 --- a/src/applications/drydock/query/DrydockBlueprintQuery.php +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -6,6 +6,7 @@ final class DrydockBlueprintQuery extends DrydockQuery { private $phids; private $blueprintClasses; private $datasourceQuery; + private $disabled; public function withIDs(array $ids) { $this->ids = $ids; @@ -27,6 +28,11 @@ final class DrydockBlueprintQuery extends DrydockQuery { return $this; } + public function withDisabled($disabled) { + $this->disabled = $disabled; + return $this; + } + public function newResultObject() { return new DrydockBlueprint(); } @@ -82,6 +88,13 @@ final class DrydockBlueprintQuery extends DrydockQuery { $this->blueprintClasses); } + if ($this->disabled !== null) { + $where[] = qsprintf( + $conn, + 'isDisabled = %d', + (int)$this->disabled); + } + return $where; } diff --git a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php index a0420c610c..64859649df 100644 --- a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php +++ b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php @@ -18,11 +18,23 @@ final class DrydockBlueprintSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['isDisabled'] !== null) { + $query->withDisabled($map['isDisabled']); + } + return $query; } protected function buildCustomSearchFields() { - return array(); + return array( + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Disabled')) + ->setKey('isDisabled') + ->setOptions( + pht('(Show All)'), + pht('Show Only Disabled Blueprints'), + pht('Hide Disabled Blueprints')), + ); } protected function getURI($path) { @@ -31,6 +43,7 @@ final class DrydockBlueprintSearchEngine protected function getBuiltinQueryNames() { return array( + 'active' => pht('Active Blueprints'), 'all' => pht('All Blueprints'), ); } @@ -40,6 +53,8 @@ final class DrydockBlueprintSearchEngine $query->setQueryKey($query_key); switch ($query_key) { + case 'active': + return $query->setParameter('isDisabled', false); case 'all': return $query; } @@ -64,6 +79,12 @@ final class DrydockBlueprintSearchEngine if (!$blueprint->getImplementation()->isEnabled()) { $item->setDisabled(true); + $item->addIcon('fa-chain-broken grey', pht('Implementation')); + } + + if ($blueprint->getIsDisabled()) { + $item->setDisabled(true); + $item->addIcon('fa-ban grey', pht('Disabled')); } $item->addAttribute($blueprint->getImplementation()->getBlueprintName()); diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index c687c996a9..429e5c2971 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -15,6 +15,7 @@ final class DrydockBlueprint extends DrydockDAO protected $viewPolicy; protected $editPolicy; protected $details = array(); + protected $isDisabled; private $implementation = self::ATTACHABLE; private $customFields = self::ATTACHABLE; @@ -34,7 +35,8 @@ final class DrydockBlueprint extends DrydockDAO return id(new DrydockBlueprint()) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) - ->setBlueprintName(''); + ->setBlueprintName('') + ->setIsDisabled(0); } protected function getConfiguration() { @@ -46,6 +48,7 @@ final class DrydockBlueprint extends DrydockDAO self::CONFIG_COLUMN_SCHEMA => array( 'className' => 'text255', 'blueprintName' => 'sort255', + 'isDisabled' => 'bool', ), ) + parent::getConfiguration(); } diff --git a/src/applications/drydock/storage/DrydockBlueprintTransaction.php b/src/applications/drydock/storage/DrydockBlueprintTransaction.php index 0e3b2e2e3e..0856f01fdb 100644 --- a/src/applications/drydock/storage/DrydockBlueprintTransaction.php +++ b/src/applications/drydock/storage/DrydockBlueprintTransaction.php @@ -3,7 +3,8 @@ final class DrydockBlueprintTransaction extends PhabricatorApplicationTransaction { - const TYPE_NAME = 'drydock:blueprint:name'; + const TYPE_NAME = 'drydock:blueprint:name'; + const TYPE_DISABLED = 'drydock:blueprint:disabled'; public function getApplicationName() { return 'drydock'; @@ -31,6 +32,16 @@ final class DrydockBlueprintTransaction $old, $new); } + case self::TYPE_DISABLED: + if ($new) { + return pht( + '%s disabled this blueprint.', + $author_handle); + } else { + return pht( + '%s enabled this blueprint.', + $author_handle); + } } return parent::getTitle(); diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php index ec676bf2fb..a67f242c01 100644 --- a/src/applications/drydock/worker/DrydockAllocatorWorker.php +++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -184,12 +184,10 @@ final class DrydockAllocatorWorker extends DrydockWorker { return array(); } - // TODO: When blueprints can be disabled, this query should ignore disabled - // blueprints. - $blueprints = id(new DrydockBlueprintQuery()) ->setViewer($viewer) ->withBlueprintClasses(array_keys($impls)) + ->withDisabled(false) ->execute(); $keep = array(); From 3b2f4c258f1bae4d65391987045c0b7385cf7d63 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 13:52:43 -0700 Subject: [PATCH 32/43] Show recent active resources on Drydock blueprint detail, with link to all Summary: Ref T9252. Currently, Drydock blueprint pages: - show all resources, even if there are a million; - show resources in all states, although destroyed resources are usually uninteresting; - have some junky `$pager` code. Instead, show the few most recent active resources and link to a filtered resource view in ApplicationSearch. Test Plan: - Viewed some blueprints. - Clicked "View All Resources". - Saw all resources. - Used query / crumbs / etc. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14157 --- .../PhabricatorDrydockApplication.php | 2 + .../DrydockBlueprintViewController.php | 61 ++++++++++++------- .../controller/DrydockResourceController.php | 36 +++++++++-- .../DrydockResourceListController.php | 20 +++++- .../query/DrydockResourceSearchEngine.php | 28 ++++++++- 5 files changed, 118 insertions(+), 29 deletions(-) diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index afc4219d1f..bf95a57009 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -53,6 +53,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '' => 'DrydockBlueprintViewController', '(?Pdisable|enable)/' => 'DrydockBlueprintDisableController', + 'resources/(?:query/(?P[^/]+)/)?' => + 'DrydockResourceListController', ), 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 815ace7026..6991e18fa2 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -30,24 +30,6 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $actions = $this->buildActionListView($blueprint); $properties = $this->buildPropertyListView($blueprint, $actions); - $blueprint_uri = 'blueprint/'.$blueprint->getID().'/'; - $blueprint_uri = $this->getApplicationURI($blueprint_uri); - - $resources = id(new DrydockResourceQuery()) - ->withBlueprintPHIDs(array($blueprint->getPHID())) - ->setViewer($viewer) - ->execute(); - - $resource_list = id(new DrydockResourceListView()) - ->setUser($viewer) - ->setResources($resources) - ->render(); - $resource_list->setNoDataString(pht('This blueprint has no resources.')); - - $pager = new PHUIPagerView(); - $pager->setURI(new PhutilURI($blueprint_uri), 'offset'); - $pager->setOffset($request->getInt('offset')); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); @@ -67,9 +49,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $viewer, $properties); - $resource_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Resources')) - ->setObjectList($resource_list); + $resource_box = $this->buildResourceBox($blueprint); $timeline = $this->buildTransactionTimeline( $blueprint, @@ -148,4 +128,43 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return $view; } + private function buildResourceBox(DrydockBlueprint $blueprint) { + $viewer = $this->getViewer(); + + $resources = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withBlueprintPHIDs(array($blueprint->getPHID())) + ->withStatuses( + array( + DrydockResourceStatus::STATUS_PENDING, + DrydockResourceStatus::STATUS_ACTIVE, + )) + ->setLimit(100) + ->execute(); + + $resource_list = id(new DrydockResourceListView()) + ->setUser($viewer) + ->setResources($resources) + ->render() + ->setNoDataString(pht('This blueprint has no active resources.')); + + $id = $blueprint->getID(); + $resources_uri = "blueprint/{$id}/resources/query/all/"; + $resources_uri = $this->getApplicationURI($resources_uri); + + $resource_header = id(new PHUIHeaderView()) + ->setHeader(pht('Active Resources')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($resources_uri) + ->setIconFont('fa-search') + ->setText(pht('View All Resources'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($resource_header) + ->setObjectList($resource_list); + } + + } diff --git a/src/applications/drydock/controller/DrydockResourceController.php b/src/applications/drydock/controller/DrydockResourceController.php index e29b13e2c2..8675b5bf59 100644 --- a/src/applications/drydock/controller/DrydockResourceController.php +++ b/src/applications/drydock/controller/DrydockResourceController.php @@ -3,12 +3,23 @@ abstract class DrydockResourceController extends DrydockController { + private $blueprint; + + public function setBlueprint($blueprint) { + $this->blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->blueprint; + } + public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new DrydockResourceSearchEngine()) - ->setViewer($this->getRequest()->getUser()) + ->setViewer($this->getViewer()) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); @@ -18,9 +29,26 @@ abstract class DrydockResourceController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Resources'), - $this->getApplicationURI('resource/')); + + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $id = $blueprint->getID(); + $crumbs->addTextCrumb( + pht('Blueprints'), + $this->getApplicationURI('blueprint/')); + + $crumbs->addTextCrumb( + $blueprint->getBlueprintName(), + $this->getApplicationURI("blueprint/{$id}/")); + + $crumbs->addTextCrumb( + pht('Resources'), + $this->getApplicationURI("blueprint/{$id}/resources/")); + } else { + $crumbs->addTextCrumb( + pht('Resources'), + $this->getApplicationURI('resource/')); + } return $crumbs; } diff --git a/src/applications/drydock/controller/DrydockResourceListController.php b/src/applications/drydock/controller/DrydockResourceListController.php index d2f34ec25b..6e1f80cbd6 100644 --- a/src/applications/drydock/controller/DrydockResourceListController.php +++ b/src/applications/drydock/controller/DrydockResourceListController.php @@ -7,12 +7,28 @@ final class DrydockResourceListController extends DrydockResourceController { } public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); + $viewer = $this->getViewer(); + + $engine = new DrydockResourceSearchEngine(); + + $id = $request->getURIData('id'); + if ($id) { + $blueprint = id(new DrydockBlueprintQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$blueprint) { + return new Aphront404Response(); + } + $this->setBlueprint($blueprint); + $engine->setBlueprint($blueprint); + } + $querykey = $request->getURIData('queryKey'); $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($querykey) - ->setSearchEngine(new DrydockResourceSearchEngine()) + ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); diff --git a/src/applications/drydock/query/DrydockResourceSearchEngine.php b/src/applications/drydock/query/DrydockResourceSearchEngine.php index 7b0bca0324..8f72fdf217 100644 --- a/src/applications/drydock/query/DrydockResourceSearchEngine.php +++ b/src/applications/drydock/query/DrydockResourceSearchEngine.php @@ -3,6 +3,17 @@ final class DrydockResourceSearchEngine extends PhabricatorApplicationSearchEngine { + private $blueprint; + + public function setBlueprint(DrydockBlueprint $blueprint) { + $this->blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->blueprint; + } + public function getResultTypeDescription() { return pht('Drydock Resources'); } @@ -12,7 +23,14 @@ final class DrydockResourceSearchEngine } public function newQuery() { - return new DrydockResourceQuery(); + $query = new DrydockResourceQuery(); + + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $query->withBlueprintPHIDs(array($blueprint->getPHID())); + } + + return $query; } protected function buildQueryFromParameters(array $map) { @@ -35,7 +53,13 @@ final class DrydockResourceSearchEngine } protected function getURI($path) { - return '/drydock/resource/'.$path; + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $id = $blueprint->getID(); + return "/drydock/blueprint/{$id}/resources/".$path; + } else { + return '/drydock/resource/'.$path; + } } protected function getBuiltinQueryNames() { From 64ed971039930fdc4c84b27b58151526785683de Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 15:28:59 -0700 Subject: [PATCH 33/43] Show recent active leases on Drydock resource detail Summary: Ref T9252. This is the same as D14157, just for Resources and their leases. Test Plan: Viewed a resource, saw only active leases, clicked "View All Leases", queried, clicked around, used crumbs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14158 --- .../PhabricatorDrydockApplication.php | 2 + .../controller/DrydockLeaseController.php | 47 +++++++++++++--- .../controller/DrydockLeaseListController.php | 21 ++++++-- .../controller/DrydockResourceController.php | 11 ++-- .../DrydockResourceViewController.php | 54 ++++++++++++++----- .../query/DrydockLeaseSearchEngine.php | 28 +++++++++- 6 files changed, 135 insertions(+), 28 deletions(-) diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index bf95a57009..5df54593ee 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -64,6 +64,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '(?P[1-9]\d*)/' => array( '' => 'DrydockResourceViewController', 'release/' => 'DrydockResourceReleaseController', + 'leases/(?:query/(?P[^/]+)/)?' => + 'DrydockLeaseListController', ), ), 'lease/' => array( diff --git a/src/applications/drydock/controller/DrydockLeaseController.php b/src/applications/drydock/controller/DrydockLeaseController.php index d520fc66ee..d5a6335454 100644 --- a/src/applications/drydock/controller/DrydockLeaseController.php +++ b/src/applications/drydock/controller/DrydockLeaseController.php @@ -3,13 +3,29 @@ abstract class DrydockLeaseController extends DrydockController { + private $resource; + + public function setResource($resource) { + $this->resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new DrydockLeaseSearchEngine()) - ->setViewer($this->getRequest()->getUser()) - ->addNavigationItems($nav->getMenu()); + $engine = id(new DrydockLeaseSearchEngine()) + ->setViewer($this->getRequest()->getUser()); + + if ($this->getResource()) { + $engine->setResource($this->getResource()); + } + + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); @@ -18,9 +34,28 @@ abstract class DrydockLeaseController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Leases'), - $this->getApplicationURI('lease/')); + + $resource = $this->getResource(); + if ($resource) { + $id = $resource->getID(); + + $crumbs->addTextCrumb( + pht('Resources'), + $this->getApplicationURI('resource/')); + + $crumbs->addTextCrumb( + $resource->getName(), + $this->getApplicationURI("resource/{$id}/")); + + $crumbs->addTextCrumb( + pht('Leases'), + $this->getApplicationURI("resource/{$id}/leases/")); + + } else { + $crumbs->addTextCrumb( + pht('Leases'), + $this->getApplicationURI('lease/')); + } return $crumbs; } diff --git a/src/applications/drydock/controller/DrydockLeaseListController.php b/src/applications/drydock/controller/DrydockLeaseListController.php index e370467a5f..321e1d4ae7 100644 --- a/src/applications/drydock/controller/DrydockLeaseListController.php +++ b/src/applications/drydock/controller/DrydockLeaseListController.php @@ -8,11 +8,26 @@ final class DrydockLeaseListController extends DrydockLeaseController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $querykey = $request->getURIData('queryKey'); + $query_key = $request->getURIData('queryKey'); + + $engine = new DrydockLeaseSearchEngine(); + + $id = $request->getURIData('id'); + if ($id) { + $resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$resource) { + return new Aphront404Response(); + } + $this->setResource($resource); + $engine->setResource($resource); + } $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new DrydockLeaseSearchEngine()) + ->setQueryKey($query_key) + ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); diff --git a/src/applications/drydock/controller/DrydockResourceController.php b/src/applications/drydock/controller/DrydockResourceController.php index 8675b5bf59..120d6d7cb0 100644 --- a/src/applications/drydock/controller/DrydockResourceController.php +++ b/src/applications/drydock/controller/DrydockResourceController.php @@ -18,9 +18,14 @@ abstract class DrydockResourceController $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new DrydockResourceSearchEngine()) - ->setViewer($this->getViewer()) - ->addNavigationItems($nav->getMenu()); + $engine = id(new DrydockResourceSearchEngine()) + ->setViewer($this->getViewer()); + + if ($this->getBlueprint()) { + $engine->setBlueprint($this->getBlueprint()); + } + + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index be1db24a0c..0641ced96d 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -27,17 +27,6 @@ final class DrydockResourceViewController extends DrydockResourceController { $resource_uri = 'resource/'.$resource->getID().'/'; $resource_uri = $this->getApplicationURI($resource_uri); - $leases = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withResourcePHIDs(array($resource->getPHID())) - ->execute(); - - $lease_list = id(new DrydockLeaseListView()) - ->setUser($viewer) - ->setLeases($leases) - ->render(); - $lease_list->setNoDataString(pht('This resource has no leases.')); - $pager = new PHUIPagerView(); $pager->setURI(new PhutilURI($resource_uri), 'offset'); $pager->setOffset($request->getInt('offset')); @@ -65,9 +54,7 @@ final class DrydockResourceViewController extends DrydockResourceController { ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $lease_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Leases')) - ->setObjectList($lease_list); + $lease_box = $this->buildLeaseBox($resource); $log_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Resource Logs')) @@ -149,4 +136,43 @@ final class DrydockResourceViewController extends DrydockResourceController { return $view; } + private function buildLeaseBox(DrydockResource $resource) { + $viewer = $this->getViewer(); + + $leases = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withResourcePHIDs(array($resource->getPHID())) + ->withStatuses( + array( + DrydockLeaseStatus::STATUS_PENDING, + DrydockLeaseStatus::STATUS_ACQUIRED, + DrydockLeaseStatus::STATUS_ACTIVE, + )) + ->setLimit(100) + ->execute(); + + $id = $resource->getID(); + $leases_uri = "resource/{$id}/leases/query/all/"; + $leases_uri = $this->getApplicationURI($leases_uri); + + $lease_header = id(new PHUIHeaderView()) + ->setHeader(pht('Active Leases')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($leases_uri) + ->setIconFont('fa-search') + ->setText(pht('View All Leases'))); + + $lease_list = id(new DrydockLeaseListView()) + ->setUser($viewer) + ->setLeases($leases) + ->render() + ->setNoDataString(pht('This resource has no active leases.')); + + return id(new PHUIObjectBoxView()) + ->setHeader($lease_header) + ->setObjectList($lease_list); + } + } diff --git a/src/applications/drydock/query/DrydockLeaseSearchEngine.php b/src/applications/drydock/query/DrydockLeaseSearchEngine.php index 7d85ddbe70..3b551023b2 100644 --- a/src/applications/drydock/query/DrydockLeaseSearchEngine.php +++ b/src/applications/drydock/query/DrydockLeaseSearchEngine.php @@ -3,6 +3,17 @@ final class DrydockLeaseSearchEngine extends PhabricatorApplicationSearchEngine { + private $resource; + + public function setResource($resource) { + $this->resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + public function getResultTypeDescription() { return pht('Drydock Leases'); } @@ -12,7 +23,14 @@ final class DrydockLeaseSearchEngine } public function newQuery() { - return new DrydockLeaseQuery(); + $query = new DrydockLeaseQuery(); + + $resource = $this->getResource(); + if ($resource) { + $query->withResourcePHIDs(array($resource->getPHID())); + } + + return $query; } protected function buildQueryFromParameters(array $map) { @@ -35,7 +53,13 @@ final class DrydockLeaseSearchEngine } protected function getURI($path) { - return '/drydock/lease/'.$path; + $resource = $this->getResource(); + if ($resource) { + $id = $resource->getID(); + return "/drydock/resource/{$id}/leases/".$path; + } else { + return '/drydock/lease/'.$path; + } } protected function getBuiltinQueryNames() { From 381fa611fd8d7b55a759323916233f1e5f7c5a7d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 15:34:09 -0700 Subject: [PATCH 34/43] Check that the viewer can actually create badges before letting them create badges Summary: Fixes T9467. Test Plan: Set policy to "no one", got blocked. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9467 Differential Revision: https://secure.phabricator.com/D14159 --- .../badges/controller/PhabricatorBadgesEditController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php index 42f685bbf6..77ced98533 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php @@ -22,6 +22,9 @@ final class PhabricatorBadgesEditController } $is_new = false; } else { + $this->requireApplicationCapability( + PhabricatorBadgesCreateCapability::CAPABILITY); + $badge = PhabricatorBadgesBadge::initializeNewBadge($viewer); $is_new = true; } From 284fe0fe51ce20c08182bc416e2c97712a720c1a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 24 Sep 2015 17:29:47 -0700 Subject: [PATCH 35/43] Allow Harbormaster to lease working copies from Drydock Summary: Ref T9252. This is still crude in a few ways but basically works, at least for commits. Test Plan: - Made a build plan with just this build step. - Ran `bin/harbormaster build --plan 10 ...` on a commit. - It actually built a working copy, leased it, took no action, and released the lease. MAGIC~~~ Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14160 --- src/__phutil_library_map__.php | 8 +- .../differential/storage/DifferentialDiff.php | 3 + .../drydock/storage/DrydockLease.php | 19 ++-- .../drydock/view/DrydockLeaseListView.php | 5 +- .../HarbormasterDrydockLeaseArtifact.php | 73 ++++++++++++ .../artifact/HarbormasterHostArtifact.php | 65 +---------- .../HarbormasterWorkingCopyArtifact.php | 16 +++ ...easeWorkingCopyBuildStepImplementation.php | 106 ++++++++++++++++++ .../storage/build/HarbormasterBuild.php | 1 + .../storage/PhabricatorRepositoryCommit.php | 3 + 10 files changed, 225 insertions(+), 74 deletions(-) create mode 100644 src/applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php create mode 100644 src/applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php create mode 100644 src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5cc4d58561..d6662ebfa8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1002,11 +1002,13 @@ phutil_register_library_map(array( 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', + 'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php', 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', + 'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', 'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php', @@ -1047,6 +1049,7 @@ phutil_register_library_map(array( 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', + 'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php', 'HeraldAction' => 'applications/herald/action/HeraldAction.php', 'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php', 'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php', @@ -4786,11 +4789,13 @@ phutil_register_library_map(array( 'HarbormasterController' => 'PhabricatorController', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterDAO' => 'PhabricatorLiskDAO', + 'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', - 'HarbormasterHostArtifact' => 'HarbormasterArtifact', + 'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', + 'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', 'HarbormasterLintPropertyView' => 'AphrontView', 'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability', @@ -4831,6 +4836,7 @@ phutil_register_library_map(array( 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWorker' => 'PhabricatorWorker', + 'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact', 'HeraldAction' => 'Phobject', 'HeraldActionGroup' => 'HeraldGroup', 'HeraldActionRecord' => 'HeraldDAO', diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 7a5bc24203..25421bafe6 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -443,6 +443,7 @@ final class DifferentialDiff if ($repo) { $results['repository.callsign'] = $repo->getCallsign(); + $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); } @@ -459,6 +460,8 @@ final class DifferentialDiff pht('The differential revision ID, if applicable.'), 'repository.callsign' => pht('The callsign of the repository in Phabricator.'), + 'repository.phid' => + pht('The PHID of the repository in Phabricator.'), 'repository.vcs' => pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index afe618c77e..fe38f54fe0 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -126,22 +126,23 @@ final class DrydockLease extends DrydockDAO return $this; } - public function isActive() { - switch ($this->status) { + public function isActivating() { + switch ($this->getStatus()) { + case DrydockLeaseStatus::STATUS_PENDING: case DrydockLeaseStatus::STATUS_ACQUIRED: - case DrydockLeaseStatus::STATUS_ACTIVE: return true; } + return false; } - private function assertActive() { - if (!$this->isActive()) { - throw new Exception( - pht( - 'Lease is not active! You can not interact with resources through '. - 'an inactive lease.')); + public function isActive() { + switch ($this->getStatus()) { + case DrydockLeaseStatus::STATUS_ACTIVE: + return true; } + + return false; } public function waitUntilActive() { diff --git a/src/applications/drydock/view/DrydockLeaseListView.php b/src/applications/drydock/view/DrydockLeaseListView.php index 7d7d8e77ee..d3507546ad 100644 --- a/src/applications/drydock/view/DrydockLeaseListView.php +++ b/src/applications/drydock/view/DrydockLeaseListView.php @@ -41,7 +41,10 @@ final class DrydockLeaseListView extends AphrontView { $item->addAttribute($status); $item->setEpoch($lease->getDateCreated()); - if ($lease->isActive()) { + // TODO: Tailor this for clarity. + if ($lease->isActivating()) { + $item->setStatusIcon('fa-dot-circle-o yellow'); + } else if ($lease->isActive()) { $item->setStatusIcon('fa-dot-circle-o green'); } else { $item->setStatusIcon('fa-dot-circle-o red'); diff --git a/src/applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php b/src/applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php new file mode 100644 index 0000000000..bd3868e7db --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php @@ -0,0 +1,73 @@ + 'string', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'drydockLeasePHID' => pht( + 'Drydock working copy lease to create an artifact from.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst', + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $lease_phid = $artifact->getProperty('drydockLeasePHID'); + return $viewer->renderHandle($lease_phid); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + $this->loadArtifactLease($actor); + } + + public function loadArtifactLease(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $lease_phid = $artifact->getProperty('drydockLeasePHID'); + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withPHIDs(array($lease_phid)) + ->executeOne(); + if (!$lease) { + throw new Exception( + pht( + 'Drydock lease PHID "%s" does not correspond to a valid lease.', + $lease_phid)); + } + + return $lease; + } + + public function releaseArtifact(PhabricatorUser $actor) { + $lease = $this->loadArtifactLease($actor); + if (!$lease->canRelease()) { + return; + } + + $author_phid = $actor->getPHID(); + if (!$author_phid) { + $author_phid = id(new PhabricatorHarbormasterApplication())->getPHID(); + } + + $command = DrydockCommand::initializeNewCommand($actor) + ->setTargetPHID($lease->getPHID()) + ->setAuthorPHID($author_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + + $lease->scheduleUpdate(); + } + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php index ce109eb498..1c2eec8264 100644 --- a/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php +++ b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php @@ -1,6 +1,7 @@ 'string', - ); - } - - public function getArtifactParameterDescriptions() { - return array( - 'drydockLeasePHID' => pht( - 'Drydock host lease to create an artifact from.'), - ); - } - - public function getArtifactDataExample() { - return array( - 'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst', - ); - } - - public function renderArtifactSummary(PhabricatorUser $viewer) { - $artifact = $this->getBuildArtifact(); - $file_phid = $artifact->getProperty('drydockLeasePHID'); - return $viewer->renderHandle($file_phid); - } - - public function willCreateArtifact(PhabricatorUser $actor) { - $this->loadArtifactLease($actor); - } - - public function loadArtifactLease(PhabricatorUser $viewer) { - $artifact = $this->getBuildArtifact(); - $lease_phid = $artifact->getProperty('drydockLeasePHID'); - - $lease = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withPHIDs(array($lease_phid)) - ->executeOne(); - if (!$lease) { - throw new Exception( - pht( - 'Drydock lease PHID "%s" does not correspond to a valid lease.', - $lease_phid)); - } - - return $lease; - } - - public function releaseArtifact(PhabricatorUser $actor) { - $lease = $this->loadArtifactLease($actor); - if (!$lease->canRelease()) { - return; - } - - $command = DrydockCommand::initializeNewCommand($actor) - ->setTargetPHID($lease->getPHID()) - ->setCommand(DrydockCommand::COMMAND_RELEASE) - ->save(); - - $lease->scheduleUpdate(); - } - } diff --git a/src/applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php b/src/applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php new file mode 100644 index 0000000000..84ce403124 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php @@ -0,0 +1,16 @@ +getSettings(); + + // TODO: We should probably have a separate temporary storage area for + // execution stuff that doesn't step on configuration state? + $lease_phid = $build_target->getDetail('exec.leasePHID'); + + if ($lease_phid) { + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withPHIDs(array($lease_phid)) + ->executeOne(); + if (!$lease) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Lease "%s" could not be loaded.', + $lease_phid)); + } + } else { + $working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation()) + ->getType(); + + $lease = id(new DrydockLease()) + ->setResourceType($working_copy_type) + ->setOwnerPHID($build_target->getPHID()); + + $variables = $build_target->getVariables(); + + $repository_phid = idx($variables, 'repository.phid'); + $commit = idx($variables, 'repository.commit'); + + $lease + ->setAttribute('repositoryPHID', $repository_phid) + ->setAttribute('commit', $commit); + + $lease->queueForActivation(); + + $build_target + ->setDetail('exec.leasePHID', $lease->getPHID()) + ->save(); + } + + if ($lease->isActivating()) { + // TODO: Smart backoff? + throw new PhabricatorWorkerYieldException(15); + } + + if (!$lease->isActive()) { + // TODO: We could just forget about this lease and retry? + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Lease "%s" never activated.', + $lease->getPHID())); + } + + $artifact = $build_target->createArtifact( + $viewer, + $settings['name'], + HarbormasterWorkingCopyArtifact::ARTIFACTCONST, + array( + 'drydockLeasePHID' => $lease->getPHID(), + )); + } + + public function getArtifactOutputs() { + return array( + array( + 'name' => pht('Working Copy'), + 'key' => $this->getSetting('name'), + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, + ), + ); + } + + public function getFieldSpecifications() { + return array( + 'name' => array( + 'name' => pht('Artifact Name'), + 'type' => 'text', + 'required' => true, + ), + ); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index bcbd5e0961..1c012b1842 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -251,6 +251,7 @@ final class HarbormasterBuild extends HarbormasterDAO 'buildable.revision' => null, 'buildable.commit' => null, 'repository.callsign' => null, + 'repository.phid' => null, 'repository.vcs' => null, 'repository.uri' => null, 'step.timestamp' => null, diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 984e6325f7..531bac9dad 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -332,6 +332,7 @@ final class PhabricatorRepositoryCommit $repo = $this->getRepository(); $results['repository.callsign'] = $repo->getCallsign(); + $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); @@ -343,6 +344,8 @@ final class PhabricatorRepositoryCommit 'buildable.commit' => pht('The commit identifier, if applicable.'), 'repository.callsign' => pht('The callsign of the repository in Phabricator.'), + 'repository.phid' => + pht('The PHID of the repository in Phabricator.'), 'repository.vcs' => pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => From 8ce90a7c4287070d70d5876bb5e8edefd87f51c5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 10:42:57 -0700 Subject: [PATCH 36/43] Allow lint codes to be up to 128 bytes long Summary: Fixes T9145. We currently restrict lint codes to 32 bytes, but PHPCS generates codes like "PHPCS.E.PEAR.Comments.Messages.Line.TooLong". These codes seem reasonable as codes, and we don't currently have any key-length problems or other technical concerns with simply raising the size of this column. Test Plan: Ran `bin/storage upgrade` to pick up adjustments. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9145 Differential Revision: https://secure.phabricator.com/D14166 --- .../harbormaster/storage/build/HarbormasterBuildLintMessage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php index 2bd37b2f79..4fe230d452 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php @@ -104,7 +104,7 @@ final class HarbormasterBuildLintMessage 'path' => 'text', 'line' => 'uint32?', 'characterOffset' => 'uint32?', - 'code' => 'text32', + 'code' => 'text128', 'severity' => 'text32', 'name' => 'text255', ), From 8d3bb92b91abbe06f8fe9168bf30b206094a283e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 10:43:04 -0700 Subject: [PATCH 37/43] Make some Herald errors more spider-resistant Summary: Fixes T9328. There's no way to hit these error states by clicking things in the UI that I could find, but if you mash stuff into your URL bar or "Inspect Element..." and then edit the form to be full of garbage you can hit them. Make them a little more informative and don't send them to the log, since these are pretty much just fancy 404s. Test Plan: Bashed my fist on the URL bar to hit all these messages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9328 Differential Revision: https://secure.phabricator.com/D14164 --- .../controller/HeraldRuleController.php | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index ce7a46cd00..37a583c7ef 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -24,6 +24,8 @@ final class HeraldRuleController extends HeraldController { } $cancel_uri = $this->getApplicationURI("rule/{$id}/"); } else { + $new_uri = $this->getApplicationURI('new/'); + $rule = new HeraldRule(); $rule->setAuthorPHID($viewer->getPHID()); $rule->setMustMatchAll(1); @@ -33,18 +35,40 @@ final class HeraldRuleController extends HeraldController { $rule_type = $request->getStr('rule_type'); if (!isset($rule_type_map[$rule_type])) { - $rule_type = HeraldRuleTypeConfig::RULE_TYPE_PERSONAL; + return $this->newDialog() + ->setTitle(pht('Invalid Rule Type')) + ->appendParagraph( + pht( + 'The selected rule type ("%s") is not recognized by Herald.', + $rule_type)) + ->addCancelButton($new_uri); } $rule->setRuleType($rule_type); - $adapter = HeraldAdapter::getAdapterForContentType( - $rule->getContentType()); + try { + $adapter = HeraldAdapter::getAdapterForContentType( + $rule->getContentType()); + } catch (Exception $ex) { + return $this->newDialog() + ->setTitle(pht('Invalid Content Type')) + ->appendParagraph( + pht( + 'The selected content type ("%s") is not recognized by '. + 'Herald.', + $rule->getContentType())) + ->addCancelButton($new_uri); + } if (!$adapter->supportsRuleType($rule->getRuleType())) { - throw new Exception( - pht( - "This rule's content type does not support the selected rule ". - "type.")); + return $this->newDialog() + ->setTitle(pht('Rule/Content Mismatch')) + ->appendParagraph( + pht( + 'The selected rule type ("%s") is not supported by the selected '. + 'content type ("%s").', + $rule->getRuleType(), + $rule->getContentType())) + ->addCancelButton($new_uri); } if ($rule->isObjectRule()) { From 36d9908e6cd158b4beb8298a5f3c063cd76565c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 10:43:11 -0700 Subject: [PATCH 38/43] Move commits to the "COMMIT" mail prefix Summary: Fixes T9427. Currently, replies to audits/commits go to "Cxxx", but so do replies to countdowns. There is non real non-disruptive approach available here and this seems least-bad. Test Plan: - Made a comment on a commit. - Fished the reply-to address out of `bin/mail list-oubound` + `bin/mail show-outbound` (it was now "COMMIT..."). - Sent mail to that address. - Grabbed the raw message and wrote it to `mail.txt`. - Ran `cat mail.txt | ./scripts/mail/mail_handler.php --process-duplicates`. - Used `bin/mail list-inbound` + `bin/mail show-inbound` to verify receipt. - Saw comment appear on audit. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9427 Differential Revision: https://secure.phabricator.com/D14163 --- src/applications/audit/mail/PhabricatorAuditMailReceiver.php | 4 ++-- src/applications/audit/mail/PhabricatorAuditReplyHandler.php | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/applications/audit/mail/PhabricatorAuditMailReceiver.php b/src/applications/audit/mail/PhabricatorAuditMailReceiver.php index 29b1748264..36e68c76a9 100644 --- a/src/applications/audit/mail/PhabricatorAuditMailReceiver.php +++ b/src/applications/audit/mail/PhabricatorAuditMailReceiver.php @@ -8,11 +8,11 @@ final class PhabricatorAuditMailReceiver extends PhabricatorObjectMailReceiver { } protected function getObjectPattern() { - return 'C[1-9]\d*'; + return 'COMMIT[1-9]\d*'; } protected function loadObject($pattern, PhabricatorUser $viewer) { - $id = (int)trim($pattern, 'C'); + $id = (int)preg_replace('/^COMMIT/', '', $pattern); return id(new DiffusionCommitQuery()) ->setViewer($viewer) diff --git a/src/applications/audit/mail/PhabricatorAuditReplyHandler.php b/src/applications/audit/mail/PhabricatorAuditReplyHandler.php index c1eb562bfa..3b619fdd7f 100644 --- a/src/applications/audit/mail/PhabricatorAuditReplyHandler.php +++ b/src/applications/audit/mail/PhabricatorAuditReplyHandler.php @@ -13,9 +13,7 @@ final class PhabricatorAuditReplyHandler } public function getObjectPrefix() { - // TODO: This conflicts with Countdown and will probably need to be - // changed eventually. - return 'C'; + return 'COMMIT'; } } From bd546f44a9bec7d532bea573da5e364a5da39e81 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 10:43:17 -0700 Subject: [PATCH 39/43] Load audit requests when querying audits Summary: Fixes T9434. I'm not sure exactly what changed behavior here, but we need a `needAuditRequests()`. Test Plan: Ran a query which hit the exception (empty query was good enough, locally), then applied this patch; saw exception go away. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9434 Differential Revision: https://secure.phabricator.com/D14162 --- src/applications/audit/conduit/AuditQueryConduitAPIMethod.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php b/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php index 2fc9ca47ee..97dbec3d68 100644 --- a/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php +++ b/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php @@ -37,7 +37,8 @@ final class AuditQueryConduitAPIMethod extends AuditConduitAPIMethod { protected function execute(ConduitAPIRequest $request) { $query = id(new DiffusionCommitQuery()) - ->setViewer($request->getUser()); + ->setViewer($request->getUser()) + ->needAuditRequests(true); $auditor_phids = $request->getValue('auditorPHIDs', array()); if ($auditor_phids) { From d735c7adf2d5897d3ae08f3aba12774030c0d3b1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 10:43:32 -0700 Subject: [PATCH 40/43] Allow Harbormaster to run commands on Drydock working copies Summary: Ref T9252. This mostly cleans up future and log handling, and edges us closer to being able to do useful work with Harbormaster / Drydock. Test Plan: - Added a "Run `ls -alh`" step to my trivial build plan. - Ran it a bunch of times. - Worked great. - Also did an HTTP plan. {F835227} {F835228} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14161 --- src/__phutil_library_map__.php | 4 + .../future/HarbormasterExecFuture.php | 50 +++++++++++ .../HarbormasterBuildStepImplementation.php | 9 +- ...rDrydockCommandBuildStepImplementation.php | 90 +++++++++++++++++++ ...sterHTTPRequestBuildStepImplementation.php | 27 ++++-- ...easeWorkingCopyBuildStepImplementation.php | 2 +- .../storage/build/HarbormasterBuildLog.php | 9 ++ .../storage/build/HarbormasterBuildTarget.php | 14 +++ 8 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 src/applications/harbormaster/future/HarbormasterExecFuture.php create mode 100644 src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d6662ebfa8..50d0a57d57 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1002,7 +1002,9 @@ phutil_register_library_map(array( 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', + 'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php', 'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php', + 'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php', 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', @@ -4789,7 +4791,9 @@ phutil_register_library_map(array( 'HarbormasterController' => 'PhabricatorController', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterDAO' => 'PhabricatorLiskDAO', + 'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact', + 'HarbormasterExecFuture' => 'Future', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', diff --git a/src/applications/harbormaster/future/HarbormasterExecFuture.php b/src/applications/harbormaster/future/HarbormasterExecFuture.php new file mode 100644 index 0000000000..9dc4e172fd --- /dev/null +++ b/src/applications/harbormaster/future/HarbormasterExecFuture.php @@ -0,0 +1,50 @@ +future = $future; + return $this; + } + + public function getFuture() { + return $this->future; + } + + public function setLogs( + HarbormasterBuildLog $stdout, + HarbormasterBuildLog $stderr) { + $this->stdout = $stdout; + $this->stderr = $stderr; + return $this; + } + + public function isReady() { + $future = $this->getFuture(); + + $result = $future->isReady(); + + list($stdout, $stderr) = $future->read(); + $future->discardBuffers(); + + if ($this->stdout) { + $this->stdout->append($stdout); + } + + if ($this->stderr) { + $this->stderr->append($stderr); + } + + return $result; + } + + protected function getResult() { + return $this->getFuture()->getResult(); + } + +} diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index 15f2dd9943..744ad2474f 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -238,22 +238,21 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { return $build->getBuildGeneration() !== $target->getBuildGeneration(); } - protected function resolveFuture( + protected function resolveFutures( HarbormasterBuild $build, HarbormasterBuildTarget $target, - Future $future) { + array $futures) { - $futures = new FutureIterator(array($future)); + $futures = new FutureIterator($futures); foreach ($futures->setUpdateInterval(5) as $key => $future) { if ($future === null) { $build->reload(); if ($this->shouldAbort($build, $target)) { throw new HarbormasterBuildAbortedException(); } - } else { - return $future->resolve(); } } + } diff --git a/src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php new file mode 100644 index 0000000000..0b19704197 --- /dev/null +++ b/src/applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php @@ -0,0 +1,90 @@ +formatSettingForDescription('command'), + $this->formatSettingForDescription('artifact')); + } + + public function execute( + HarbormasterBuild $build, + HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $settings = $this->getSettings(); + $variables = $build_target->getVariables(); + + $artifact = $build_target->loadArtifact($settings['artifact']); + $impl = $artifact->getArtifactImplementation(); + $lease = $impl->loadArtifactLease($viewer); + + // TODO: Require active lease. + + $command = $this->mergeVariables( + 'vcsprintf', + $settings['command'], + $variables); + + $interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE); + + $exec_future = $interface->getExecFuture('%C', $command); + + $harbor_future = id(new HarbormasterExecFuture()) + ->setFuture($exec_future) + ->setLogs( + $build_target->newLog('remote', 'stdout'), + $build_target->newLog('remote', 'stderr')); + + $this->resolveFutures( + $build, + $build_target, + array($harbor_future)); + + list($err) = $harbor_future->resolve(); + if ($err) { + throw new HarbormasterBuildFailureException(); + } + } + + public function getArtifactInputs() { + return array( + array( + 'name' => pht('Drydock Lease'), + 'key' => $this->getSetting('artifact'), + 'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST, + ), + ); + } + + public function getFieldSpecifications() { + return array( + 'command' => array( + 'name' => pht('Command'), + 'type' => 'text', + 'required' => true, + ), + 'artifact' => array( + 'name' => pht('Drydock Lease'), + 'type' => 'text', + 'required' => true, + ), + ); + } + +} diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index 3910ac0b24..bfaa5c6a56 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -51,9 +51,6 @@ final class HarbormasterHTTPRequestBuildStepImplementation $settings['uri'], $variables); - $log_body = $build->createLog($build_target, $uri, 'http-body'); - $start = $log_body->start(); - $method = nonempty(idx($settings, 'method'), 'POST'); $future = id(new HTTPSFuture($uri)) @@ -70,16 +67,30 @@ final class HarbormasterHTTPRequestBuildStepImplementation $key->getPasswordEnvelope()); } - list($status, $body, $headers) = $this->resolveFuture( + $this->resolveFutures( $build, $build_target, - $future); + array($future)); - $log_body->append($body); - $log_body->finalize($start); + list($status, $body, $headers) = $future->resolve(); + + $header_lines = array(); + foreach ($headers as $header) { + list($head, $tail) = $header; + $header_lines[] = "{$head}: {$tail}"; + } + $header_lines = implode("\n", $header_lines); + + $build_target + ->newLog($uri, 'http.head') + ->append($header_lines); + + $build_target + ->newLog($uri, 'http.body') + ->append($body); if ($status->getStatusCode() != 200) { - $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); + throw new HarbormasterBuildFailureException(); } } diff --git a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php index 21b88cfe11..1f5b139008 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php @@ -88,7 +88,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation array( 'name' => pht('Working Copy'), 'key' => $this->getSetting('name'), - 'type' => HarbormasterHostArtifact::ARTIFACTCONST, + 'type' => HarbormasterWorkingCopyArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index d03f588719..2e964e8931 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -10,6 +10,7 @@ final class HarbormasterBuildLog extends HarbormasterDAO protected $live; private $buildTarget = self::ATTACHABLE; + private $start; const CHUNK_BYTE_LIMIT = 102400; @@ -18,6 +19,12 @@ final class HarbormasterBuildLog extends HarbormasterDAO */ const ENCODING_TEXT = 'text'; + public function __destruct() { + if ($this->live) { + $this->finalize($this->start); + } + } + public static function initializeNewBuildLog( HarbormasterBuildTarget $build_target) { @@ -75,6 +82,8 @@ final class HarbormasterBuildLog extends HarbormasterDAO $this->setLive(1); $this->save(); + $this->start = PhabricatorTime::getNow(); + return time(); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index 1fdefb6ca3..fffa30a883 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -249,6 +249,20 @@ final class HarbormasterBuildTarget extends HarbormasterDAO return $artifact; } + public function newLog($log_source, $log_type) { + $log_source = id(new PhutilUTF8StringTruncator()) + ->setMaximumBytes(250) + ->truncateString($log_source); + + $log = HarbormasterBuildLog::initializeNewBuildLog($this) + ->setLogSource($log_source) + ->setLogType($log_type); + + $log->start(); + + return $log; + } + /* -( Status )------------------------------------------------------------- */ From b7ca5a2d29813817085d65bc01cb13c2fc14ea7f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 10:45:01 -0700 Subject: [PATCH 41/43] Provide a stable URI for getting raw paste content Summary: Fixes T9312. This is a bit fluff, but does simplify the view controller slightly and seems reasonable/useful in general. Test Plan: Clicked "View Raw File" on a paste, got redirected to the raw file via a stable URI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9312 Differential Revision: https://secure.phabricator.com/D14167 --- src/__phutil_library_map__.php | 2 + .../PhabricatorPasteApplication.php | 1 + .../PhabricatorPasteRawController.php | 39 +++++++++++++++++++ .../PhabricatorPasteViewController.php | 20 +++------- 4 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 src/applications/paste/controller/PhabricatorPasteRawController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 50d0a57d57..064d80740a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2510,6 +2510,7 @@ phutil_register_library_map(array( 'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php', 'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php', 'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php', + 'PhabricatorPasteRawController' => 'applications/paste/controller/PhabricatorPasteRawController.php', 'PhabricatorPasteRemarkupRule' => 'applications/paste/remarkup/PhabricatorPasteRemarkupRule.php', 'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php', 'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php', @@ -6545,6 +6546,7 @@ phutil_register_library_map(array( 'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType', 'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPasteRawController' => 'PhabricatorPasteController', 'PhabricatorPasteRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/paste/application/PhabricatorPasteApplication.php b/src/applications/paste/application/PhabricatorPasteApplication.php index a3f11bb9a8..7fe3ec41e7 100644 --- a/src/applications/paste/application/PhabricatorPasteApplication.php +++ b/src/applications/paste/application/PhabricatorPasteApplication.php @@ -40,6 +40,7 @@ final class PhabricatorPasteApplication extends PhabricatorApplication { '(query/(?P[^/]+)/)?' => 'PhabricatorPasteListController', 'create/' => 'PhabricatorPasteEditController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorPasteEditController', + 'raw/(?P[1-9]\d*)/' => 'PhabricatorPasteRawController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorPasteCommentController', ), ); diff --git a/src/applications/paste/controller/PhabricatorPasteRawController.php b/src/applications/paste/controller/PhabricatorPasteRawController.php new file mode 100644 index 0000000000..080c5dc8b5 --- /dev/null +++ b/src/applications/paste/controller/PhabricatorPasteRawController.php @@ -0,0 +1,39 @@ +getViewer(); + $id = $request->getURIData('id'); + + $paste = id(new PhabricatorPasteQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$paste) { + return new Aphront404Response(); + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($paste->getFilePHID())) + ->executeOne(); + if (!$file) { + return new Aphront400Response(); + } + + return $file->getRedirectResponse(); + } + +} diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 382d683f37..f8176892bc 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -42,14 +42,6 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { return new Aphront404Response(); } - $file = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($paste->getFilePHID())) - ->executeOne(); - if (!$file) { - return new Aphront400Response(); - } - $forks = id(new PhabricatorPasteQuery()) ->setViewer($viewer) ->withParentPHIDs(array($paste->getPHID())) @@ -57,7 +49,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $fork_phids = mpull($forks, 'getPHID'); $header = $this->buildHeaderView($paste); - $actions = $this->buildActionView($viewer, $paste, $file); + $actions = $this->buildActionView($viewer, $paste); $properties = $this->buildPropertyView($paste, $fork_phids, $actions); $object_box = id(new PHUIObjectBoxView()) @@ -139,8 +131,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { private function buildActionView( PhabricatorUser $viewer, - PhabricatorPaste $paste, - PhabricatorFile $file) { + PhabricatorPaste $paste) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -148,7 +139,8 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { PhabricatorPolicyCapability::CAN_EDIT); $can_fork = $viewer->isLoggedIn(); - $fork_uri = $this->getApplicationURI('/create/?parent='.$paste->getID()); + $id = $paste->getID(); + $fork_uri = $this->getApplicationURI('/create/?parent='.$id); return id(new PhabricatorActionListView()) ->setUser($viewer) @@ -160,7 +152,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) - ->setHref($this->getApplicationURI('/edit/'.$paste->getID().'/'))) + ->setHref($this->getApplicationURI("edit/{$id}/"))) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Fork This Paste')) @@ -172,7 +164,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { id(new PhabricatorActionView()) ->setName(pht('View Raw File')) ->setIcon('fa-file-text-o') - ->setHref($file->getBestURI())); + ->setHref($this->getApplicationURI("raw/{$id}/"))); } private function buildPropertyView( From 3e60740c7cbffbe8e5422cda10e71225edaa9dbc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 11:15:57 -0700 Subject: [PATCH 42/43] Slightly modernize transaction diff controller Summary: Ref T9272. This doesn't fix anything, just a little cleanup while I was looking at it. Test Plan: Clicked "Show Details" on a couple description changes, got the same effect for less code. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9272 Differential Revision: https://secure.phabricator.com/D14168 --- ...ApplicationTransactionDetailController.php | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php index ea4bc5d77b..af6b93bcf1 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php @@ -3,22 +3,16 @@ final class PhabricatorApplicationTransactionDetailController extends PhabricatorApplicationTransactionController { - private $phid; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $phid = $request->getURIData('phid'); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($this->phid)) + ->withPHIDs(array($phid)) ->setViewer($viewer) ->executeOne(); if (!$xaction) { @@ -26,17 +20,14 @@ final class PhabricatorApplicationTransactionDetailController } $details = $xaction->renderChangeDetails($viewer); - $cancel_uri = $this->guessCancelURI($viewer, $xaction); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + + return $this->newDialog() ->setTitle(pht('Change Details')) ->setWidth(AphrontDialogView::WIDTH_FULL) ->setFlush(true) ->appendChild($details) ->addCancelButton($cancel_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); } } From 99d972fc8187cdae61b5f5ddc04d6cfe50508e21 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 25 Sep 2015 15:00:55 -0700 Subject: [PATCH 43/43] Fix Herald rule actions on empty custom PHID fields Summary: Fixes T9260. That task has a good description of the issue. Test Plan: Followed steps in T9260 to reproduce the issue. Applied patch; issue went away. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9260 Differential Revision: https://secure.phabricator.com/D14169 --- .../standard/PhabricatorStandardCustomFieldPHIDs.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php index 842d662904..d900d41eb3 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -166,4 +166,14 @@ abstract class PhabricatorStandardCustomFieldPHIDs ); } + public function getHeraldFieldValue() { + // If the field has a `null` value, make sure we hand an `array()` to + // Herald. + $value = parent::getHeraldFieldValue(); + if ($value) { + return $value; + } + return array(); + } + }