@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+883 -253
+10
resources/sql/autopatches/20150922.drydock.commands.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_drydock.drydock_command ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + authorPHID VARBINARY(64) NOT NULL, 4 + targetPHID VARBINARY(64) NOT NULL, 5 + command VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, 6 + isConsumed BOOL NOT NULL, 7 + dateCreated INT UNSIGNED NOT NULL, 8 + dateModified INT UNSIGNED NOT NULL, 9 + KEY `key_target` (targetPHID, isConsumed) 10 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+21 -6
src/__phutil_library_map__.php
··· 817 817 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 818 818 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', 819 819 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', 820 + 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', 820 821 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', 822 + 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 821 823 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', 822 824 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 823 825 'DrydockController' => 'applications/drydock/controller/DrydockController.php', ··· 837 839 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 838 840 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 839 841 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 842 + 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', 840 843 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', 841 844 'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php', 842 845 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', ··· 845 848 'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php', 846 849 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 847 850 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 848 - 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', 849 851 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 850 852 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 851 - 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 853 + 'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php', 854 + 'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php', 855 + 'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php', 856 + 'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php', 852 857 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', 853 858 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 854 859 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 855 - 'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php', 856 860 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 857 861 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 858 862 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 859 863 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 860 864 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', 861 865 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', 866 + 'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php', 862 867 'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php', 863 868 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', 869 + 'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php', 864 870 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 865 871 'DrydockResourceWorker' => 'applications/drydock/worker/DrydockResourceWorker.php', 866 872 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', ··· 4535 4541 'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction', 4536 4542 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4537 4543 'DrydockBlueprintViewController' => 'DrydockBlueprintController', 4544 + 'DrydockCommand' => array( 4545 + 'DrydockDAO', 4546 + 'PhabricatorPolicyInterface', 4547 + ), 4538 4548 'DrydockCommandInterface' => 'DrydockInterface', 4549 + 'DrydockCommandQuery' => 'DrydockQuery', 4539 4550 'DrydockConsoleController' => 'DrydockController', 4540 4551 'DrydockConstants' => 'Phobject', 4541 4552 'DrydockController' => 'PhabricatorController', ··· 4558 4569 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 4559 4570 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 4560 4571 'DrydockLeaseStatus' => 'DrydockConstants', 4572 + 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 4561 4573 'DrydockLeaseViewController' => 'DrydockLeaseController', 4562 4574 'DrydockLeaseWorker' => 'DrydockWorker', 4563 4575 'DrydockLog' => array( ··· 4569 4581 'DrydockLogListView' => 'AphrontView', 4570 4582 'DrydockLogQuery' => 'DrydockQuery', 4571 4583 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 4572 - 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', 4573 4584 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 4574 4585 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 4575 - 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 4586 + 'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow', 4587 + 'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow', 4588 + 'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow', 4589 + 'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow', 4576 4590 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', 4577 4591 'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4578 4592 'DrydockResource' => array( 4579 4593 'DrydockDAO', 4580 4594 'PhabricatorPolicyInterface', 4581 4595 ), 4582 - 'DrydockResourceCloseController' => 'DrydockResourceController', 4583 4596 'DrydockResourceController' => 'DrydockController', 4584 4597 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 4585 4598 'DrydockResourceListController' => 'DrydockResourceController', 4586 4599 'DrydockResourceListView' => 'AphrontView', 4587 4600 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', 4588 4601 'DrydockResourceQuery' => 'DrydockQuery', 4602 + 'DrydockResourceReleaseController' => 'DrydockResourceController', 4589 4603 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 4590 4604 'DrydockResourceStatus' => 'DrydockConstants', 4605 + 'DrydockResourceUpdateWorker' => 'DrydockWorker', 4591 4606 'DrydockResourceViewController' => 'DrydockResourceController', 4592 4607 'DrydockResourceWorker' => 'DrydockWorker', 4593 4608 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
+4 -2
src/applications/drydock/application/PhabricatorDrydockApplication.php
··· 55 55 ), 56 56 'resource/' => array( 57 57 '(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockResourceListController', 58 - '(?P<id>[1-9]\d*)/' => 'DrydockResourceViewController', 59 - '(?P<id>[1-9]\d*)/close/' => 'DrydockResourceCloseController', 58 + '(?P<id>[1-9]\d*)/' => array( 59 + '' => 'DrydockResourceViewController', 60 + 'release/' => 'DrydockResourceReleaseController', 61 + ), 60 62 ), 61 63 'lease/' => array( 62 64 '(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockLeaseListController',
+49
src/applications/drydock/controller/DrydockController.php
··· 36 36 ->addRawContent($table); 37 37 } 38 38 39 + protected function buildCommandsTab($target_phid) { 40 + $viewer = $this->getViewer(); 41 + 42 + $commands = id(new DrydockCommandQuery()) 43 + ->setViewer($viewer) 44 + ->withTargetPHIDs(array($target_phid)) 45 + ->execute(); 46 + 47 + $consumed_yes = id(new PHUIIconView()) 48 + ->setIconFont('fa-check green'); 49 + $consumed_no = id(new PHUIIconView()) 50 + ->setIconFont('fa-clock-o grey'); 51 + 52 + $rows = array(); 53 + foreach ($commands as $command) { 54 + $rows[] = array( 55 + $command->getID(), 56 + $viewer->renderHandle($command->getAuthorPHID()), 57 + $command->getCommand(), 58 + ($command->getIsConsumed() 59 + ? $consumed_yes 60 + : $consumed_no), 61 + phabricator_datetime($command->getDateCreated(), $viewer), 62 + ); 63 + } 64 + 65 + $table = id(new AphrontTableView($rows)) 66 + ->setNoDataString(pht('No commands issued.')) 67 + ->setHeaders( 68 + array( 69 + pht('ID'), 70 + pht('From'), 71 + pht('Command'), 72 + null, 73 + pht('Date'), 74 + )) 75 + ->setColumnClasses( 76 + array( 77 + null, 78 + null, 79 + 'wide', 80 + null, 81 + null, 82 + )); 83 + 84 + return id(new PHUIPropertyListView()) 85 + ->addRawContent($table); 86 + } 87 + 39 88 }
+28 -31
src/applications/drydock/controller/DrydockLeaseReleaseController.php
··· 9 9 $lease = id(new DrydockLeaseQuery()) 10 10 ->setViewer($viewer) 11 11 ->withIDs(array($id)) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 12 17 ->executeOne(); 13 18 if (!$lease) { 14 19 return new Aphront404Response(); ··· 17 22 $lease_uri = '/lease/'.$lease->getID().'/'; 18 23 $lease_uri = $this->getApplicationURI($lease_uri); 19 24 20 - if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { 21 - $dialog = id(new AphrontDialogView()) 22 - ->setUser($viewer) 23 - ->setTitle(pht('Lease Not Active')) 24 - ->appendChild( 25 - phutil_tag( 26 - 'p', 27 - array(), 28 - pht('You can only release "active" leases.'))) 25 + if (!$lease->canRelease()) { 26 + return $this->newDialog() 27 + ->setTitle(pht('Lease Not Releasable')) 28 + ->appendParagraph( 29 + pht( 30 + 'Leases can not be released after they are destroyed.')) 29 31 ->addCancelButton($lease_uri); 32 + } 30 33 31 - return id(new AphrontDialogResponse())->setDialog($dialog); 32 - } 34 + if ($request->isFormPost()) { 35 + $command = DrydockCommand::initializeNewCommand($viewer) 36 + ->setTargetPHID($lease->getPHID()) 37 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 38 + ->save(); 33 39 34 - if (!$request->isDialogFormPost()) { 35 - $dialog = id(new AphrontDialogView()) 36 - ->setUser($viewer) 37 - ->setTitle(pht('Really release lease?')) 38 - ->appendChild( 39 - phutil_tag( 40 - 'p', 41 - array(), 42 - pht( 43 - 'Releasing a lease may cause trouble for the lease holder and '. 44 - 'trigger cleanup of the underlying resource. It can not be '. 45 - 'undone. Continue?'))) 46 - ->addSubmitButton(pht('Release Lease')) 47 - ->addCancelButton($lease_uri); 40 + $lease->scheduleUpdate(); 48 41 49 - return id(new AphrontDialogResponse())->setDialog($dialog); 42 + return id(new AphrontRedirectResponse())->setURI($lease_uri); 50 43 } 51 44 52 - $resource = $lease->getResource(); 53 - $blueprint = $resource->getBlueprint(); 54 - $blueprint->releaseLease($resource, $lease); 55 - 56 - return id(new AphrontReloadResponse())->setURI($lease_uri); 45 + return $this->newDialog() 46 + ->setTitle(pht('Release Lease?')) 47 + ->appendParagraph( 48 + pht( 49 + 'Forcefully releasing a lease may interfere with the operation '. 50 + 'of the lease holder and trigger destruction of the underlying '. 51 + 'resource. It can not be undone.')) 52 + ->addSubmitButton(pht('Release Lease')) 53 + ->addCancelButton($lease_uri); 57 54 } 58 55 59 56 }
+12 -4
src/applications/drydock/controller/DrydockLeaseViewController.php
··· 43 43 $crumbs->addTextCrumb($title, $lease_uri); 44 44 45 45 $locks = $this->buildLocksTab($lease->getPHID()); 46 + $commands = $this->buildCommandsTab($lease->getPHID()); 46 47 47 48 $object_box = id(new PHUIObjectBoxView()) 48 49 ->setHeader($header) 49 50 ->addPropertyList($properties, pht('Properties')) 50 - ->addPropertyList($locks, pht('Slot Locks')); 51 + ->addPropertyList($locks, pht('Slot Locks')) 52 + ->addPropertyList($commands, pht('Commands')); 51 53 52 54 $log_box = id(new PHUIObjectBoxView()) 53 55 ->setHeaderText(pht('Lease Logs')) ··· 66 68 } 67 69 68 70 private function buildActionListView(DrydockLease $lease) { 71 + $viewer = $this->getViewer(); 72 + 69 73 $view = id(new PhabricatorActionListView()) 70 - ->setUser($this->getRequest()->getUser()) 74 + ->setUser($viewer) 71 75 ->setObjectURI($this->getRequest()->getRequestURI()) 72 76 ->setObject($lease); 73 77 74 78 $id = $lease->getID(); 75 79 76 - $can_release = ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE); 80 + $can_release = $lease->canRelease(); 81 + $can_edit = PhabricatorPolicyFilter::hasCapability( 82 + $viewer, 83 + $lease, 84 + PhabricatorPolicyCapability::CAN_EDIT); 77 85 78 86 $view->addAction( 79 87 id(new PhabricatorActionView()) ··· 81 89 ->setIcon('fa-times') 82 90 ->setHref($this->getApplicationURI("/lease/{$id}/release/")) 83 91 ->setWorkflow(true) 84 - ->setDisabled(!$can_release)); 92 + ->setDisabled(!$can_release || !$can_edit)); 85 93 86 94 return $view; 87 95 }
-49
src/applications/drydock/controller/DrydockResourceCloseController.php
··· 1 - <?php 2 - 3 - final class DrydockResourceCloseController extends DrydockResourceController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $id = $request->getURIData('id'); 8 - 9 - $resource = id(new DrydockResourceQuery()) 10 - ->setViewer($viewer) 11 - ->withIDs(array($id)) 12 - ->executeOne(); 13 - if (!$resource) { 14 - return new Aphront404Response(); 15 - } 16 - 17 - $resource_uri = '/resource/'.$resource->getID().'/'; 18 - $resource_uri = $this->getApplicationURI($resource_uri); 19 - 20 - if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { 21 - $dialog = id(new AphrontDialogView()) 22 - ->setUser($viewer) 23 - ->setTitle(pht('Resource Not Open')) 24 - ->appendChild(phutil_tag('p', array(), pht( 25 - 'You can only close "open" resources.'))) 26 - ->addCancelButton($resource_uri); 27 - 28 - return id(new AphrontDialogResponse())->setDialog($dialog); 29 - } 30 - 31 - if ($request->isFormPost()) { 32 - $resource->closeResource(); 33 - return id(new AphrontReloadResponse())->setURI($resource_uri); 34 - } 35 - 36 - $dialog = id(new AphrontDialogView()) 37 - ->setUser($viewer) 38 - ->setTitle(pht('Really close resource?')) 39 - ->appendChild( 40 - pht( 41 - 'Closing a resource releases all leases and destroys the '. 42 - 'resource. It can not be undone. Continue?')) 43 - ->addSubmitButton(pht('Close Resource')) 44 - ->addCancelButton($resource_uri); 45 - 46 - return id(new AphrontDialogResponse())->setDialog($dialog); 47 - } 48 - 49 - }
+56
src/applications/drydock/controller/DrydockResourceReleaseController.php
··· 1 + <?php 2 + 3 + final class DrydockResourceReleaseController extends DrydockResourceController { 4 + 5 + public function handleRequest(AphrontRequest $request) { 6 + $viewer = $request->getViewer(); 7 + $id = $request->getURIData('id'); 8 + 9 + $resource = id(new DrydockResourceQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($id)) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->executeOne(); 18 + if (!$resource) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + $resource_uri = '/resource/'.$resource->getID().'/'; 23 + $resource_uri = $this->getApplicationURI($resource_uri); 24 + 25 + if (!$resource->canRelease()) { 26 + return $this->newDialog() 27 + ->setTitle(pht('Resource Not Releasable')) 28 + ->appendParagraph( 29 + pht( 30 + 'Resources can not be released after they are destroyed.')) 31 + ->addCancelButton($resource_uri); 32 + } 33 + 34 + if ($request->isFormPost()) { 35 + $command = DrydockCommand::initializeNewCommand($viewer) 36 + ->setTargetPHID($resource->getPHID()) 37 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 38 + ->save(); 39 + 40 + $resource->scheduleUpdate(); 41 + 42 + return id(new AphrontRedirectResponse())->setURI($resource_uri); 43 + } 44 + 45 + 46 + return $this->newDialog() 47 + ->setTitle(pht('Really release resource?')) 48 + ->appendChild( 49 + pht( 50 + 'Releasing a resource releases all leases and destroys the '. 51 + 'resource. It can not be undone.')) 52 + ->addSubmitButton(pht('Release Resource')) 53 + ->addCancelButton($resource_uri); 54 + } 55 + 56 + }
+15 -6
src/applications/drydock/controller/DrydockResourceViewController.php
··· 55 55 $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); 56 56 57 57 $locks = $this->buildLocksTab($resource->getPHID()); 58 + $commands = $this->buildCommandsTab($resource->getPHID()); 58 59 59 60 $object_box = id(new PHUIObjectBoxView()) 60 61 ->setHeader($header) 61 62 ->addPropertyList($properties, pht('Properties')) 62 - ->addPropertyList($locks, pht('Slot Locks')); 63 + ->addPropertyList($locks, pht('Slot Locks')) 64 + ->addPropertyList($commands, pht('Commands')); 63 65 64 66 $lease_box = id(new PHUIObjectBoxView()) 65 67 ->setHeaderText(pht('Leases')) ··· 83 85 } 84 86 85 87 private function buildActionListView(DrydockResource $resource) { 88 + $viewer = $this->getViewer(); 89 + 86 90 $view = id(new PhabricatorActionListView()) 87 - ->setUser($this->getRequest()->getUser()) 91 + ->setUser($viewer) 88 92 ->setObjectURI($this->getRequest()->getRequestURI()) 89 93 ->setObject($resource); 90 94 91 - $can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN); 92 - $uri = '/resource/'.$resource->getID().'/close/'; 95 + $can_release = $resource->canRelease(); 96 + $can_edit = PhabricatorPolicyFilter::hasCapability( 97 + $viewer, 98 + $resource, 99 + PhabricatorPolicyCapability::CAN_EDIT); 100 + 101 + $uri = '/resource/'.$resource->getID().'/release/'; 93 102 $uri = $this->getApplicationURI($uri); 94 103 95 104 $view->addAction( 96 105 id(new PhabricatorActionView()) 97 106 ->setHref($uri) 98 - ->setName(pht('Close Resource')) 107 + ->setName(pht('Release Resource')) 99 108 ->setIcon('fa-times') 100 109 ->setWorkflow(true) 101 - ->setDisabled(!$can_close)); 110 + ->setDisabled(!$can_release || !$can_edit)); 102 111 103 112 return $view; 104 113 }
-49
src/applications/drydock/management/DrydockManagementCloseWorkflow.php
··· 1 - <?php 2 - 3 - final class DrydockManagementCloseWorkflow 4 - extends DrydockManagementWorkflow { 5 - 6 - protected function didConstruct() { 7 - $this 8 - ->setName('close') 9 - ->setSynopsis(pht('Close a resource.')) 10 - ->setArguments( 11 - array( 12 - array( 13 - 'name' => 'ids', 14 - 'wildcard' => true, 15 - ), 16 - )); 17 - } 18 - 19 - public function execute(PhutilArgumentParser $args) { 20 - $console = PhutilConsole::getConsole(); 21 - 22 - $ids = $args->getArg('ids'); 23 - if (!$ids) { 24 - throw new PhutilArgumentUsageException( 25 - pht('Specify one or more resource IDs to close.')); 26 - } 27 - 28 - $viewer = $this->getViewer(); 29 - 30 - $resources = id(new DrydockResourceQuery()) 31 - ->setViewer($viewer) 32 - ->withIDs($ids) 33 - ->execute(); 34 - 35 - foreach ($ids as $id) { 36 - $resource = idx($resources, $id); 37 - if (!$resource) { 38 - $console->writeErr("%s\n", pht('Resource %d does not exist!', $id)); 39 - } else if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { 40 - $console->writeErr("%s\n", pht("Resource %d is not 'open'!", $id)); 41 - } else { 42 - $resource->closeResource(); 43 - $console->writeErr("%s\n", pht('Closed resource %d.', $id)); 44 - } 45 - } 46 - 47 - } 48 - 49 - }
+70
src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php
··· 1 + <?php 2 + 3 + final class DrydockManagementReleaseLeaseWorkflow 4 + extends DrydockManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('release-lease') 9 + ->setSynopsis(pht('Release a lease.')) 10 + ->setArguments( 11 + array( 12 + array( 13 + 'name' => 'id', 14 + 'param' => 'id', 15 + 'repeat' => true, 16 + 'help' => pht('Lease ID to release.'), 17 + ), 18 + )); 19 + } 20 + 21 + public function execute(PhutilArgumentParser $args) { 22 + $ids = $args->getArg('id'); 23 + if (!$ids) { 24 + throw new PhutilArgumentUsageException( 25 + pht( 26 + 'Specify one or more lease IDs to release with "%s".', 27 + '--id')); 28 + } 29 + 30 + $viewer = $this->getViewer(); 31 + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 32 + 33 + $leases = id(new DrydockLeaseQuery()) 34 + ->setViewer($viewer) 35 + ->withIDs($ids) 36 + ->execute(); 37 + 38 + PhabricatorWorker::setRunAllTasksInProcess(true); 39 + foreach ($ids as $id) { 40 + $lease = idx($leases, $id); 41 + if (!$lease) { 42 + echo tsprintf( 43 + "%s\n", 44 + pht('Lease "%s" does not exist.', $id)); 45 + continue; 46 + } 47 + 48 + if (!$lease->canRelease()) { 49 + echo tsprintf( 50 + "%s\n", 51 + pht('Lease "%s" is not releasable.', $id)); 52 + continue; 53 + } 54 + 55 + $command = DrydockCommand::initializeNewCommand($viewer) 56 + ->setTargetPHID($lease->getPHID()) 57 + ->setAuthorPHID($drydock_phid) 58 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 59 + ->save(); 60 + 61 + $lease->scheduleUpdate(); 62 + 63 + echo tsprintf( 64 + "%s\n", 65 + pht('Scheduled release of lease "%s".', $id)); 66 + } 67 + 68 + } 69 + 70 + }
+71
src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php
··· 1 + <?php 2 + 3 + final class DrydockManagementReleaseResourceWorkflow 4 + extends DrydockManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('release-resource') 9 + ->setSynopsis(pht('Release a resource.')) 10 + ->setArguments( 11 + array( 12 + array( 13 + 'name' => 'id', 14 + 'param' => 'id', 15 + 'repeat' => true, 16 + 'help' => pht('Resource ID to release.'), 17 + ), 18 + )); 19 + } 20 + 21 + public function execute(PhutilArgumentParser $args) { 22 + $ids = $args->getArg('id'); 23 + if (!$ids) { 24 + throw new PhutilArgumentUsageException( 25 + pht( 26 + 'Specify one or more resource IDs to release with "%s".', 27 + '--id')); 28 + } 29 + 30 + $viewer = $this->getViewer(); 31 + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 32 + 33 + $resources = id(new DrydockResourceQuery()) 34 + ->setViewer($viewer) 35 + ->withIDs($ids) 36 + ->execute(); 37 + 38 + PhabricatorWorker::setRunAllTasksInProcess(true); 39 + foreach ($ids as $id) { 40 + $resource = idx($resources, $id); 41 + 42 + if (!$resource) { 43 + echo tsprintf( 44 + "%s\n", 45 + pht('Resource "%s" does not exist.', $id)); 46 + continue; 47 + } 48 + 49 + if (!$resource->canRelease()) { 50 + echo tsprintf( 51 + "%s\n", 52 + pht('Resource "%s" is not releasable.', $id)); 53 + continue; 54 + } 55 + 56 + $command = DrydockCommand::initializeNewCommand($viewer) 57 + ->setTargetPHID($resource->getPHID()) 58 + ->setAuthorPHID($drydock_phid) 59 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 60 + ->save(); 61 + 62 + $resource->scheduleUpdate(); 63 + 64 + echo tsprintf( 65 + "%s\n", 66 + pht('Scheduled release of resource "%s".', $id)); 67 + } 68 + 69 + } 70 + 71 + }
-52
src/applications/drydock/management/DrydockManagementReleaseWorkflow.php
··· 1 - <?php 2 - 3 - final class DrydockManagementReleaseWorkflow 4 - extends DrydockManagementWorkflow { 5 - 6 - protected function didConstruct() { 7 - $this 8 - ->setName('release') 9 - ->setSynopsis(pht('Release a lease.')) 10 - ->setArguments( 11 - array( 12 - array( 13 - 'name' => 'ids', 14 - 'wildcard' => true, 15 - ), 16 - )); 17 - } 18 - 19 - public function execute(PhutilArgumentParser $args) { 20 - $console = PhutilConsole::getConsole(); 21 - 22 - $ids = $args->getArg('ids'); 23 - if (!$ids) { 24 - throw new PhutilArgumentUsageException( 25 - pht('Specify one or more lease IDs to release.')); 26 - } 27 - 28 - $viewer = $this->getViewer(); 29 - 30 - $leases = id(new DrydockLeaseQuery()) 31 - ->setViewer($viewer) 32 - ->withIDs($ids) 33 - ->execute(); 34 - 35 - foreach ($ids as $id) { 36 - $lease = idx($leases, $id); 37 - if (!$lease) { 38 - $console->writeErr("%s\n", pht('Lease %d does not exist!', $id)); 39 - } else if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { 40 - $console->writeErr("%s\n", pht("Lease %d is not 'active'!", $id)); 41 - } else { 42 - $resource = $lease->getResource(); 43 - $blueprint = $resource->getBlueprint(); 44 - $blueprint->releaseLease($resource, $lease); 45 - 46 - $console->writeErr("%s\n", pht('Released lease %d.', $id)); 47 - } 48 - } 49 - 50 - } 51 - 52 - }
+57
src/applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php
··· 1 + <?php 2 + 3 + final class DrydockManagementUpdateLeaseWorkflow 4 + extends DrydockManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('update-lease') 9 + ->setSynopsis(pht('Update a lease.')) 10 + ->setArguments( 11 + array( 12 + array( 13 + 'name' => 'id', 14 + 'param' => 'id', 15 + 'repeat' => true, 16 + 'help' => pht('Lease ID to update.'), 17 + ), 18 + )); 19 + } 20 + 21 + public function execute(PhutilArgumentParser $args) { 22 + $viewer = $this->getViewer(); 23 + 24 + $ids = $args->getArg('id'); 25 + if (!$ids) { 26 + throw new PhutilArgumentUsageException( 27 + pht( 28 + 'Specify one or more lease IDs to update with "%s".', 29 + '--id')); 30 + } 31 + 32 + $leases = id(new DrydockLeaseQuery()) 33 + ->setViewer($viewer) 34 + ->withIDs($ids) 35 + ->execute(); 36 + 37 + PhabricatorWorker::setRunAllTasksInProcess(true); 38 + 39 + foreach ($ids as $id) { 40 + $lease = idx($leases, $id); 41 + 42 + if (!$lease) { 43 + echo tsprintf( 44 + "%s\n", 45 + pht('Lease "%s" does not exist.', $id)); 46 + continue; 47 + } 48 + 49 + echo tsprintf( 50 + "%s\n", 51 + pht('Updating lease "%s".', $id)); 52 + 53 + $lease->scheduleUpdate(); 54 + } 55 + } 56 + 57 + }
+58
src/applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php
··· 1 + <?php 2 + 3 + final class DrydockManagementUpdateResourceWorkflow 4 + extends DrydockManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('update-resource') 9 + ->setSynopsis(pht('Update a resource.')) 10 + ->setArguments( 11 + array( 12 + array( 13 + 'name' => 'id', 14 + 'param' => 'id', 15 + 'repeat' => true, 16 + 'help' => pht('Resource ID to update.'), 17 + ), 18 + )); 19 + } 20 + 21 + public function execute(PhutilArgumentParser $args) { 22 + $viewer = $this->getViewer(); 23 + 24 + $ids = $args->getArg('id'); 25 + if (!$ids) { 26 + throw new PhutilArgumentUsageException( 27 + pht( 28 + 'Specify one or more resource IDs to update with "%s".', 29 + '--id')); 30 + } 31 + 32 + $resources = id(new DrydockResourceQuery()) 33 + ->setViewer($viewer) 34 + ->withIDs($ids) 35 + ->execute(); 36 + 37 + PhabricatorWorker::setRunAllTasksInProcess(true); 38 + 39 + foreach ($ids as $id) { 40 + $resource = idx($resources, $id); 41 + 42 + if (!$resource) { 43 + echo tsprintf( 44 + "%s\n", 45 + pht('Resource "%s" does not exist.', $id)); 46 + continue; 47 + } 48 + 49 + echo tsprintf( 50 + "%s\n", 51 + pht('Updating resource "%s".', $id)); 52 + 53 + $resource->scheduleUpdate(); 54 + } 55 + 56 + } 57 + 58 + }
+82
src/applications/drydock/query/DrydockCommandQuery.php
··· 1 + <?php 2 + 3 + final class DrydockCommandQuery extends DrydockQuery { 4 + 5 + private $ids; 6 + private $targetPHIDs; 7 + private $consumed; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function withTargetPHIDs(array $phids) { 15 + $this->targetPHIDs = $phids; 16 + return $this; 17 + } 18 + 19 + public function withConsumed($consumed) { 20 + $this->consumed = $consumed; 21 + return $this; 22 + } 23 + 24 + public function newResultObject() { 25 + return new DrydockCommand(); 26 + } 27 + 28 + protected function loadPage() { 29 + return $this->loadStandardPage($this->newResultObject()); 30 + } 31 + 32 + protected function willFilterPage(array $commands) { 33 + $target_phids = mpull($commands, 'getTargetPHID'); 34 + 35 + $targets = id(new PhabricatorObjectQuery()) 36 + ->setViewer($this->getViewer()) 37 + ->setParentQuery($this) 38 + ->withPHIDs($target_phids) 39 + ->execute(); 40 + $targets = mpull($targets, null, 'getPHID'); 41 + 42 + foreach ($commands as $key => $command) { 43 + $target = idx($targets, $command->getTargetPHID()); 44 + if (!$target) { 45 + $this->didRejectResult($command); 46 + unset($commands[$key]); 47 + continue; 48 + } 49 + $command->attachCommandTarget($target); 50 + } 51 + 52 + return $commands; 53 + } 54 + 55 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 56 + $where = parent::buildWhereClauseParts($conn); 57 + 58 + if ($this->ids !== null) { 59 + $where[] = qsprintf( 60 + $conn, 61 + 'id IN (%Ld)', 62 + $this->ids); 63 + } 64 + 65 + if ($this->targetPHIDs !== null) { 66 + $where[] = qsprintf( 67 + $conn, 68 + 'targetPHID IN (%Ls)', 69 + $this->targetPHIDs); 70 + } 71 + 72 + if ($this->consumed !== null) { 73 + $where[] = qsprintf( 74 + $conn, 75 + 'isConsumed = %d', 76 + (int)$this->consumed); 77 + } 78 + 79 + return $where; 80 + } 81 + 82 + }
+1
src/applications/drydock/query/DrydockLeaseQuery.php
··· 7 7 private $resourceIDs; 8 8 private $statuses; 9 9 private $datasourceQuery; 10 + private $needCommands; 10 11 11 12 public function withIDs(array $ids) { 12 13 $this->ids = $ids;
+69
src/applications/drydock/storage/DrydockCommand.php
··· 1 + <?php 2 + 3 + final class DrydockCommand 4 + extends DrydockDAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + const COMMAND_RELEASE = 'release'; 8 + 9 + protected $authorPHID; 10 + protected $targetPHID; 11 + protected $command; 12 + protected $isConsumed; 13 + 14 + private $commandTarget = self::ATTACHABLE; 15 + 16 + public static function initializeNewCommand(PhabricatorUser $author) { 17 + return id(new DrydockCommand()) 18 + ->setAuthorPHID($author->getPHID()) 19 + ->setIsConsumed(0); 20 + } 21 + 22 + protected function getConfiguration() { 23 + return array( 24 + self::CONFIG_COLUMN_SCHEMA => array( 25 + 'command' => 'text32', 26 + 'isConsumed' => 'bool', 27 + ), 28 + self::CONFIG_KEY_SCHEMA => array( 29 + 'key_target' => array( 30 + 'columns' => array('targetPHID', 'isConsumed'), 31 + ), 32 + ), 33 + ) + parent::getConfiguration(); 34 + } 35 + 36 + public function attachCommandTarget($target) { 37 + $this->commandTarget = $target; 38 + return $this; 39 + } 40 + 41 + public function getCommandTarget() { 42 + return $this->assertAttached($this->commandTarget); 43 + } 44 + 45 + 46 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 47 + 48 + 49 + public function getCapabilities() { 50 + return array( 51 + PhabricatorPolicyCapability::CAN_VIEW, 52 + ); 53 + } 54 + 55 + public function getPolicy($capability) { 56 + return $this->getCommandTarget()->getPolicy($capability); 57 + } 58 + 59 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 60 + return $this->getCommandTarget()->hasAutomaticCapability( 61 + $capability, 62 + $viewer); 63 + } 64 + 65 + public function describeAutomaticCapability($capability) { 66 + return pht('Drydock commands have the same policies as their targets.'); 67 + } 68 + 69 + }
+69 -16
src/applications/drydock/storage/DrydockLease.php
··· 30 30 } 31 31 32 32 public function __destruct() { 33 - if ($this->releaseOnDestruction) { 34 - if ($this->isActive()) { 35 - $this->release(); 36 - } 33 + if (!$this->releaseOnDestruction) { 34 + return; 35 + } 36 + 37 + if (!$this->canRelease()) { 38 + return; 37 39 } 40 + 41 + $actor = PhabricatorUser::getOmnipotentUser(); 42 + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 43 + 44 + $command = DrydockCommand::initializeNewCommand($actor) 45 + ->setTargetPHID($this->getPHID()) 46 + ->setAuthorPHID($drydock_phid) 47 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 48 + ->save(); 49 + 50 + $this->scheduleUpdate(); 38 51 } 39 52 40 53 public function getLeaseName() { ··· 130 143 return $this; 131 144 } 132 145 133 - public function release() { 134 - $this->assertActive(); 135 - $this->setStatus(DrydockLeaseStatus::STATUS_RELEASED); 136 - $this->save(); 137 - 138 - DrydockSlotLock::releaseLocks($this->getPHID()); 139 - 140 - $this->resource = null; 141 - 142 - return $this; 143 - } 144 - 145 146 public function isActive() { 146 147 switch ($this->status) { 147 148 case DrydockLeaseStatus::STATUS_ACQUIRED: ··· 262 263 263 264 $this->isAcquired = true; 264 265 266 + if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) { 267 + $this->didActivate(); 268 + } 269 + 265 270 return $this; 266 271 } 267 272 ··· 301 306 302 307 $this->isActivated = true; 303 308 309 + $this->didActivate(); 310 + 304 311 return $this; 305 312 } 306 313 ··· 308 315 return $this->isActivated; 309 316 } 310 317 318 + public function canRelease() { 319 + if (!$this->getID()) { 320 + return false; 321 + } 322 + 323 + switch ($this->getStatus()) { 324 + case DrydockLeaseStatus::STATUS_RELEASED: 325 + return false; 326 + default: 327 + return true; 328 + } 329 + } 330 + 331 + public function scheduleUpdate() { 332 + PhabricatorWorker::scheduleTask( 333 + 'DrydockLeaseUpdateWorker', 334 + array( 335 + 'leasePHID' => $this->getPHID(), 336 + ), 337 + array( 338 + 'objectPHID' => $this->getPHID(), 339 + )); 340 + } 341 + 342 + private function didActivate() { 343 + $viewer = PhabricatorUser::getOmnipotentUser(); 344 + $need_update = false; 345 + 346 + $commands = id(new DrydockCommandQuery()) 347 + ->setViewer($viewer) 348 + ->withTargetPHIDs(array($this->getPHID())) 349 + ->withConsumed(false) 350 + ->execute(); 351 + if ($commands) { 352 + $need_update = true; 353 + } 354 + 355 + if ($need_update) { 356 + $this->scheduleUpdate(); 357 + } 358 + } 359 + 311 360 312 361 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 313 362 ··· 315 364 public function getCapabilities() { 316 365 return array( 317 366 PhabricatorPolicyCapability::CAN_VIEW, 367 + PhabricatorPolicyCapability::CAN_EDIT, 318 368 ); 319 369 } 320 370 ··· 322 372 if ($this->getResource()) { 323 373 return $this->getResource()->getPolicy($capability); 324 374 } 375 + 376 + // TODO: Implement reasonable policies. 377 + 325 378 return PhabricatorPolicies::getMostOpenPolicy(); 326 379 } 327 380
+36 -32
src/applications/drydock/storage/DrydockResource.php
··· 170 170 return $this->isActivated; 171 171 } 172 172 173 - public function closeResource() { 174 - 175 - // TODO: This is super broken and will race other lease writers! 176 - 177 - $this->openTransaction(); 178 - $statuses = array( 179 - DrydockLeaseStatus::STATUS_PENDING, 180 - DrydockLeaseStatus::STATUS_ACTIVE, 181 - ); 173 + public function canRelease() { 174 + switch ($this->getStatus()) { 175 + case DrydockResourceStatus::STATUS_CLOSED: 176 + case DrydockResourceStatus::STATUS_DESTROYED: 177 + return false; 178 + default: 179 + return true; 180 + } 181 + } 182 182 183 - $leases = id(new DrydockLeaseQuery()) 184 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 185 - ->withResourceIDs(array($this->getID())) 186 - ->withStatuses($statuses) 187 - ->execute(); 183 + public function scheduleUpdate() { 184 + PhabricatorWorker::scheduleTask( 185 + 'DrydockResourceUpdateWorker', 186 + array( 187 + 'resourcePHID' => $this->getPHID(), 188 + ), 189 + array( 190 + 'objectPHID' => $this->getPHID(), 191 + )); 192 + } 188 193 189 - foreach ($leases as $lease) { 190 - switch ($lease->getStatus()) { 191 - case DrydockLeaseStatus::STATUS_PENDING: 192 - $message = pht('Breaking pending lease (resource closing).'); 193 - $lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN); 194 - break; 195 - case DrydockLeaseStatus::STATUS_ACTIVE: 196 - $message = pht('Releasing active lease (resource closing).'); 197 - $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); 198 - break; 199 - } 200 - DrydockBlueprintImplementation::writeLog($this, $lease, $message); 201 - $lease->save(); 202 - } 194 + private function didActivate() { 195 + $viewer = PhabricatorUser::getOmnipotentUser(); 203 196 204 - $this->setStatus(DrydockResourceStatus::STATUS_CLOSED); 205 - $this->save(); 197 + $need_update = false; 206 198 207 - DrydockSlotLock::releaseLocks($this->getPHID()); 199 + $commands = id(new DrydockCommandQuery()) 200 + ->setViewer($viewer) 201 + ->withTargetPHIDs(array($this->getPHID())) 202 + ->withConsumed(false) 203 + ->execute(); 204 + if ($commands) { 205 + $need_update = true; 206 + } 208 207 209 - $this->saveTransaction(); 208 + if ($need_update) { 209 + $this->scheduleUpdate(); 210 + } 210 211 } 211 212 212 213 ··· 216 217 public function getCapabilities() { 217 218 return array( 218 219 PhabricatorPolicyCapability::CAN_VIEW, 220 + PhabricatorPolicyCapability::CAN_EDIT, 219 221 ); 220 222 } 221 223 222 224 public function getPolicy($capability) { 223 225 switch ($capability) { 224 226 case PhabricatorPolicyCapability::CAN_VIEW: 227 + case PhabricatorPolicyCapability::CAN_EDIT: 228 + // TODO: Implement reasonable policies. 225 229 return PhabricatorPolicies::getMostOpenPolicy(); 226 230 } 227 231 }
+60
src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
··· 1 + <?php 2 + 3 + final class DrydockLeaseUpdateWorker extends DrydockWorker { 4 + 5 + protected function doWork() { 6 + $lease_phid = $this->getTaskDataValue('leasePHID'); 7 + 8 + $hash = PhabricatorHash::digestForIndex($lease_phid); 9 + $lock_key = 'drydock.lease:'.$hash; 10 + 11 + $lock = PhabricatorGlobalLock::newLock($lock_key) 12 + ->lock(1); 13 + 14 + $lease = $this->loadLease($lease_phid); 15 + $this->updateLease($lease); 16 + 17 + $lock->unlock(); 18 + } 19 + 20 + private function updateLease(DrydockLease $lease) { 21 + $commands = $this->loadCommands($lease->getPHID()); 22 + foreach ($commands as $command) { 23 + if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { 24 + // Leases can't receive commands before they activate or after they 25 + // release. 26 + break; 27 + } 28 + 29 + $this->processCommand($lease, $command); 30 + $command 31 + ->setIsConsumed(true) 32 + ->save(); 33 + } 34 + } 35 + 36 + private function processCommand( 37 + DrydockLease $lease, 38 + DrydockCommand $command) { 39 + switch ($command->getCommand()) { 40 + case DrydockCommand::COMMAND_RELEASE: 41 + $this->releaseLease($lease); 42 + break; 43 + } 44 + } 45 + 46 + private function releaseLease(DrydockLease $lease) { 47 + $lease->openTransaction(); 48 + $lease 49 + ->setStatus(DrydockLeaseStatus::STATUS_RELEASED) 50 + ->save(); 51 + 52 + // TODO: Hold slot locks until destruction? 53 + DrydockSlotLock::releaseLocks($lease->getPHID()); 54 + $lease->saveTransaction(); 55 + 56 + // TODO: Hook for resource release behaviors. 57 + // TODO: Schedule lease destruction. 58 + } 59 + 60 + }
+92
src/applications/drydock/worker/DrydockResourceUpdateWorker.php
··· 1 + <?php 2 + 3 + final class DrydockResourceUpdateWorker extends DrydockWorker { 4 + 5 + protected function doWork() { 6 + $resource_phid = $this->getTaskDataValue('resourcePHID'); 7 + 8 + $hash = PhabricatorHash::digestForIndex($resource_phid); 9 + $lock_key = 'drydock.resource:'.$hash; 10 + 11 + $lock = PhabricatorGlobalLock::newLock($lock_key) 12 + ->lock(1); 13 + 14 + $resource = $this->loadResource($resource_phid); 15 + $this->updateResource($resource); 16 + 17 + $lock->unlock(); 18 + } 19 + 20 + private function updateResource(DrydockResource $resource) { 21 + $commands = $this->loadCommands($resource->getPHID()); 22 + foreach ($commands as $command) { 23 + if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { 24 + // Resources can't receive commands before they activate or after they 25 + // release. 26 + break; 27 + } 28 + 29 + $this->processCommand($resource, $command); 30 + 31 + $command 32 + ->setIsConsumed(true) 33 + ->save(); 34 + } 35 + } 36 + 37 + private function processCommand( 38 + DrydockResource $resource, 39 + DrydockCommand $command) { 40 + 41 + switch ($command->getCommand()) { 42 + case DrydockCommand::COMMAND_RELEASE: 43 + $this->releaseResource($resource); 44 + break; 45 + } 46 + } 47 + 48 + private function releaseResource(DrydockResource $resource) { 49 + if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) { 50 + // If we had multiple release commands 51 + // This command is only meaningful to resources in the "Open" state. 52 + return; 53 + } 54 + 55 + $viewer = $this->getViewer(); 56 + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); 57 + 58 + $resource->openTransaction(); 59 + $resource 60 + ->setStatus(DrydockResourceStatus::STATUS_CLOSED) 61 + ->save(); 62 + 63 + // TODO: Hold slot locks until destruction? 64 + DrydockSlotLock::releaseLocks($resource->getPHID()); 65 + $resource->saveTransaction(); 66 + 67 + $statuses = array( 68 + DrydockLeaseStatus::STATUS_PENDING, 69 + DrydockLeaseStatus::STATUS_ACQUIRED, 70 + DrydockLeaseStatus::STATUS_ACTIVE, 71 + ); 72 + 73 + $leases = id(new DrydockLeaseQuery()) 74 + ->setViewer($viewer) 75 + ->withResourceIDs(array($resource->getID())) 76 + ->withStatuses($statuses) 77 + ->execute(); 78 + 79 + foreach ($leases as $lease) { 80 + $command = DrydockCommand::initializeNewCommand($viewer) 81 + ->setTargetPHID($lease->getPHID()) 82 + ->setAuthorPHID($drydock_phid) 83 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 84 + ->save(); 85 + 86 + $lease->scheduleUpdate(); 87 + } 88 + 89 + // TODO: Schedule resource destruction. 90 + } 91 + 92 + }
+14
src/applications/drydock/worker/DrydockWorker.php
··· 36 36 return $resource; 37 37 } 38 38 39 + protected function loadCommands($target_phid) { 40 + $viewer = $this->getViewer(); 41 + 42 + $commands = id(new DrydockCommandQuery()) 43 + ->setViewer($viewer) 44 + ->withTargetPHIDs(array($target_phid)) 45 + ->withConsumed(false) 46 + ->execute(); 47 + 48 + $commands = msort($commands, 'getID'); 49 + 50 + return $commands; 51 + } 52 + 39 53 }
+9 -6
src/applications/harbormaster/artifact/HarbormasterHostArtifact.php
··· 62 62 63 63 public function releaseArtifact(PhabricatorUser $actor) { 64 64 $lease = $this->loadArtifactLease($actor); 65 - $resource = $lease->getResource(); 66 - $blueprint = $resource->getBlueprint(); 65 + if (!$lease->canRelease()) { 66 + return; 67 + } 68 + 69 + $command = DrydockCommand::initializeNewCommand($actor) 70 + ->setTargetPHID($lease->getPHID()) 71 + ->setCommand(DrydockCommand::COMMAND_RELEASE) 72 + ->save(); 67 73 68 - if ($lease->isActive()) { 69 - $blueprint->releaseLease($resource, $lease); 70 - } 74 + $lease->scheduleUpdate(); 71 75 } 72 - 73 76 74 77 }