diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -832,6 +832,7 @@ 'DrydockLease' => 'applications/drydock/storage/DrydockLease.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', + 'DrydockLeaseDestroyWorker' => 'applications/drydock/worker/DrydockLeaseDestroyWorker.php', 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', 'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php', 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', @@ -859,6 +860,7 @@ 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', + 'DrydockResourceDestroyWorker' => 'applications/drydock/worker/DrydockResourceDestroyWorker.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', @@ -4562,6 +4564,7 @@ ), 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', + 'DrydockLeaseDestroyWorker' => 'DrydockWorker', 'DrydockLeaseListController' => 'DrydockLeaseController', 'DrydockLeaseListView' => 'AphrontView', 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', @@ -4595,6 +4598,7 @@ ), 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', + 'DrydockResourceDestroyWorker' => 'DrydockWorker', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -87,6 +87,14 @@ $exceptions); } + public function destroyResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + // We don't create anything when allocating hosts, so we don't need to do + // any cleanup here. + return; + } + public function canAcquireLeaseOnResource( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -110,6 +118,24 @@ ->acquireOnResource($resource); } + public function didReleaseLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // Almanac hosts stick around indefinitely so we don't need to recycle them + // if they don't have any leases. + return; + } + + public function destroyLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // We don't create anything when activating a lease, so we don't need to + // throw anything away. + return; + } + private function getLeaseSlotLock(DrydockResource $resource) { $resource_phid = $resource->getPHID(); return "almanac.host.lease({$resource_phid})"; diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -67,6 +67,11 @@ DrydockResource $resource, DrydockLease $lease); + + /** + * @return void + * @task lease + */ public function activateLease( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -74,37 +79,40 @@ throw new PhutilMethodNotImplementedException(); } - final public function releaseLease( + + /** + * React to a lease being released. + * + * This callback is primarily useful for automatically releasing resources + * once all leases are released. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource a lease was released on. + * @param DrydockLease Recently released lease. + * @return void + * @task lease + */ + abstract public function didReleaseLease( DrydockBlueprint $blueprint, DrydockResource $resource, - DrydockLease $lease) { - - // TODO: This is all broken nonsense. - - $scope = $this->pushActiveScope(null, $lease); - - $released = false; - - $lease->openTransaction(); - $lease->beginReadLocking(); - $lease->reload(); - - if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { - $lease->release(); - $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); - $lease->save(); - $released = true; - } - - $lease->endReadLocking(); - $lease->saveTransaction(); - - if (!$released) { - throw new Exception(pht('Unable to release lease: lease not active!')); - } + DrydockLease $lease); - } + /** + * Destroy any temporary data associated with a lease. + * + * If a lease creates temporary state while held, destroy it here. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource the lease is acquired on. + * @param DrydockLease The lease being destroyed. + * @return void + * @task lease + */ + abstract public function destroyLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease); /* -( Resource Allocation )------------------------------------------------ */ @@ -204,12 +212,34 @@ DrydockBlueprint $blueprint, DrydockLease $lease); + + /** + * @task resource + */ public function activateResource( DrydockBlueprint $blueprint, DrydockResource $resource) { throw new PhutilMethodNotImplementedException(); } + + /** + * Destroy any temporary data associated with a resource. + * + * If a resource creates temporary state when allocated, destroy that state + * here. For example, you might shut down a virtual host or destroy a working + * copy on disk. + * + * @param DrydockBlueprint Blueprint which built the resource. + * @param DrydockResource Resource being destroyed. + * @return void + * @task resource + */ + abstract public function destroyResource( + DrydockBlueprint $blueprint, + DrydockResource $resource); + + /* -( Resource Interfaces )------------------------------------------------ */ diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -126,6 +126,26 @@ ->activateResource(); } + public function destroyResource( + DrydockBlueprint $blueprint, + DrydockResource $resource) { + + $lease = $this->loadHostLease($resource); + + // Destroy the lease on the host. + $lease->releaseOnDestruction(); + + // Destroy the working copy on disk. + $command_type = DrydockCommandInterface::INTERFACE_TYPE; + $interface = $lease->getInterface($command_type); + + $root_key = 'workingcopy.root'; + $root = $resource->getAttribute($root_key); + if (strlen($root)) { + $interface->execx('rm -rf -- %s', $root); + } + } + public function activateLease( DrydockBlueprint $blueprint, DrydockResource $resource, @@ -162,6 +182,26 @@ $lease->activateOnResource($resource); } + public function didReleaseLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // We leave working copies around even if there are no leases on them, + // since the cost to maintain them is nearly zero but rebuilding them is + // moderately expensive and it's likely that they'll be reused. + return; + } + + public function destroyLease( + DrydockBlueprint $blueprint, + DrydockResource $resource, + DrydockLease $lease) { + // When we activate a lease we just reset the working copy state and do + // not create any new state, so we don't need to do anything special when + // destroying a lease. + return; + } + public function getType() { return 'working-copy'; } diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -45,11 +45,18 @@ if ($attributes) { $lease->setAttributes($attributes); } - $lease - ->queueForActivation() - ->waitUntilActive(); + $lease->queueForActivation(); + + echo tsprintf( + "%s\n", + pht('Waiting for daemons to activate lease...')); + + $lease->waitUntilActive(); + + echo tsprintf( + "%s\n", + pht('Activated lease "%s".', $lease->getID())); - $console->writeOut("%s\n", pht('Acquired Lease %s', $lease->getID())); return 0; } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -143,6 +143,18 @@ $resource); } + + /** + * @task resource + */ + public function destroyResource(DrydockResource $resource) { + $this->getImplementation()->destroyResource( + $this, + $resource); + return $this; + } + + /* -( Acquiring Leases )--------------------------------------------------- */ @@ -188,10 +200,27 @@ /** * @task lease */ - public function releaseLease( + public function didReleaseLease( DrydockResource $resource, DrydockLease $lease) { - $this->getImplementation()->releaseLease($this, $resource, $lease); + $this->getImplementation()->didReleaseLease( + $this, + $resource, + $lease); + return $this; + } + + + /** + * @task lease + */ + public function destroyLease( + DrydockResource $resource, + DrydockLease $lease) { + $this->getImplementation()->destroyLease( + $this, + $resource, + $lease); return $this; } diff --git a/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/worker/DrydockLeaseDestroyWorker.php @@ -0,0 +1,39 @@ +getTaskDataValue('leasePHID'); + $lease = $this->loadLease($lease_phid); + $this->destroyLease($lease); + } + + private function destroyLease(DrydockLease $lease) { + $status = $lease->getStatus(); + + switch ($status) { + case DrydockLeaseStatus::STATUS_RELEASED: + case DrydockLeaseStatus::STATUS_BROKEN: + break; + default: + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to destroy lease ("%s"), lease has the wrong '. + 'status ("%s").', + $lease->getPHID(), + $status)); + } + + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + $blueprint->destroyLease($resource, $lease); + + // TODO: Rename DrydockLeaseStatus::STATUS_EXPIRED to STATUS_DESTROYED. + + $lease + ->setStatus(DrydockLeaseStatus::STATUS_EXPIRED) + ->save(); + } + +} diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -53,8 +53,19 @@ DrydockSlotLock::releaseLocks($lease->getPHID()); $lease->saveTransaction(); - // TODO: Hook for resource release behaviors. - // TODO: Schedule lease destruction. + PhabricatorWorker::scheduleTask( + 'DrydockLeaseDestroyWorker', + array( + 'leasePHID' => $lease->getPHID(), + ), + array( + 'objectPHID' => $lease->getPHID(), + )); + + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + $blueprint->didReleaseLease($resource, $lease); } } diff --git a/src/applications/drydock/worker/DrydockResourceDestroyWorker.php b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/worker/DrydockResourceDestroyWorker.php @@ -0,0 +1,35 @@ +getTaskDataValue('resourcePHID'); + $resource = $this->loadResource($resource_phid); + $this->destroyResource($resource); + } + + private function destroyResource(DrydockResource $resource) { + $status = $resource->getStatus(); + + switch ($status) { + case DrydockResourceStatus::STATUS_CLOSED: + case DrydockResourceStatus::STATUS_BROKEN: + break; + default: + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Unable to destroy resource ("%s"), resource has the wrong '. + 'status ("%s").', + $resource->getPHID(), + $status)); + } + + $blueprint = $resource->getBlueprint(); + $blueprint->destroyResource($resource); + + $resource + ->setStatus(DrydockResourceStatus::STATUS_DESTROYED) + ->save(); + } + +} diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php --- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php @@ -86,7 +86,14 @@ $lease->scheduleUpdate(); } - // TODO: Schedule resource destruction. + PhabricatorWorker::scheduleTask( + 'DrydockResourceDestroyWorker', + array( + 'resourcePHID' => $resource->getPHID(), + ), + array( + 'objectPHID' => $resource->getPHID(), + )); } }