mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-14 08:41:07 +01:00
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
This commit is contained in:
parent
8ded0927aa
commit
789df89c84
24 changed files with 883 additions and 253 deletions
10
resources/sql/autopatches/20150922.drydock.commands.1.sql
Normal file
10
resources/sql/autopatches/20150922.drydock.commands.1.sql
Normal file
|
@ -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};
|
|
@ -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',
|
||||
|
|
|
@ -55,8 +55,10 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
|
|||
),
|
||||
'resource/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockResourceListController',
|
||||
'(?P<id>[1-9]\d*)/' => 'DrydockResourceViewController',
|
||||
'(?P<id>[1-9]\d*)/close/' => 'DrydockResourceCloseController',
|
||||
'(?P<id>[1-9]\d*)/' => array(
|
||||
'' => 'DrydockResourceViewController',
|
||||
'release/' => 'DrydockResourceReleaseController',
|
||||
),
|
||||
),
|
||||
'lease/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockLeaseListController',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DrydockResourceCloseController extends DrydockResourceController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
final class DrydockResourceReleaseController extends DrydockResourceController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DrydockManagementCloseWorkflow
|
||||
extends DrydockManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
final class DrydockManagementReleaseLeaseWorkflow
|
||||
extends DrydockManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
final class DrydockManagementReleaseResourceWorkflow
|
||||
extends DrydockManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class DrydockManagementReleaseWorkflow
|
||||
extends DrydockManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
final class DrydockManagementUpdateLeaseWorkflow
|
||||
extends DrydockManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
final class DrydockManagementUpdateResourceWorkflow
|
||||
extends DrydockManagementWorkflow {
|
||||
|
||||
protected function didConstruct() {
|
||||
$this
|
||||
->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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
82
src/applications/drydock/query/DrydockCommandQuery.php
Normal file
82
src/applications/drydock/query/DrydockCommandQuery.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
final class DrydockCommandQuery extends DrydockQuery {
|
||||
|
||||
private $ids;
|
||||
private $targetPHIDs;
|
||||
private $consumed;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
69
src/applications/drydock/storage/DrydockCommand.php
Normal file
69
src/applications/drydock/storage/DrydockCommand.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
final class DrydockCommand
|
||||
extends DrydockDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
const COMMAND_RELEASE = 'release';
|
||||
|
||||
protected $authorPHID;
|
||||
protected $targetPHID;
|
||||
protected $command;
|
||||
protected $isConsumed;
|
||||
|
||||
private $commandTarget = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewCommand(PhabricatorUser $author) {
|
||||
return id(new DrydockCommand())
|
||||
->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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
60
src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
Normal file
60
src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
final class DrydockLeaseUpdateWorker extends DrydockWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$lease_phid = $this->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.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class DrydockResourceUpdateWorker extends DrydockWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$resource_phid = $this->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.
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue