diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -97,6 +97,7 @@ private function buildPropertyListView( DrydockLease $lease, PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); $view->setActionList($actions); @@ -145,6 +146,14 @@ pht('No Resource')); } + $until = $lease->getUntil(); + if ($until) { + $until_display = phabricator_datetime($until, $viewer); + } else { + $until_display = phutil_tag('em', array(), pht('Never')); + } + $view->addProperty(pht('Expires'), $until_display); + $attributes = $lease->getAttributes(); if ($attributes) { $view->addSectionHeader( diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -15,6 +15,11 @@ 'help' => pht('Resource type.'), ), array( + 'name' => 'until', + 'param' => 'time', + 'help' => pht('Set lease expiration time.'), + ), + array( 'name' => 'attributes', 'param' => 'name=value,...', 'help' => pht('Resource specficiation.'), @@ -33,6 +38,17 @@ '--type')); } + $until = $args->getArg('until'); + if (strlen($until)) { + $until = strtotime($until); + if ($until <= 0) { + throw new PhutilArgumentUsageException( + pht( + 'Unable to parse argument to "%s".', + '--until')); + } + } + $attributes = $args->getArg('attributes'); if ($attributes) { $options = new PhutilSimpleOptions(); @@ -42,9 +58,15 @@ $lease = id(new DrydockLease()) ->setResourceType($resource_type); + if ($attributes) { $lease->setAttributes($attributes); } + + if ($until) { + $lease->setUntil($until); + } + $lease->queueForActivation(); echo tsprintf( diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -295,14 +295,16 @@ } } - public function scheduleUpdate() { + public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockLeaseUpdateWorker', array( 'leasePHID' => $this->getPHID(), + 'isExpireTask' => ($epoch !== null), ), array( 'objectPHID' => $this->getPHID(), + 'delayUntil' => $epoch, )); } @@ -322,6 +324,11 @@ if ($need_update) { $this->scheduleUpdate(); } + + $expires = $this->getUntil(); + if ($expires) { + $this->scheduleUpdate($expires); + } } diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -11,13 +11,38 @@ $lock = PhabricatorGlobalLock::newLock($lock_key) ->lock(1); - $lease = $this->loadLease($lease_phid); - $this->updateLease($lease); + try { + $lease = $this->loadLease($lease_phid); + $this->updateLease($lease); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } $lock->unlock(); } private function updateLease(DrydockLease $lease) { + if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { + return; + } + + $viewer = $this->getViewer(); + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + // Check if the lease has expired. If it is, we're going to send it a + // release command. This command will be handled immediately below, it + // just generates a command log and improves consistency. + $now = PhabricatorTime::getNow(); + $expires = $lease->getUntil(); + if ($expires && ($expires <= $now)) { + $command = DrydockCommand::initializeNewCommand($viewer) + ->setTargetPHID($lease->getPHID()) + ->setAuthorPHID($drydock_phid) + ->setCommand(DrydockCommand::COMMAND_RELEASE) + ->save(); + } + $commands = $this->loadCommands($lease->getPHID()); foreach ($commands as $command) { if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { @@ -27,10 +52,21 @@ } $this->processCommand($lease, $command); + $command ->setIsConsumed(true) ->save(); } + + // If this is the task which will eventually release the lease after it + // expires but it is still active, reschedule the task to run after the + // lease expires. This can happen if the lease's expiration was pushed + // forward. + if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { + if ($this->getTaskDataValue('isExpireTask') && $expires) { + throw new PhabricatorWorkerYieldException($expires - $now); + } + } } private function processCommand( diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -117,6 +117,11 @@ ->setPriority($priority) ->setObjectPHID($object_phid); + $delay = idx($options, 'delayUntil'); + if ($delay) { + $task->setLeaseExpires($delay); + } + if (self::$runAllTasksInProcess) { // Do the work in-process. $worker = newv($task_class, array($data));