1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Give Drydock resources a proper expiry mechanism

Summary:
Fixes T6569. This implements an expiry mechanism for Drydock resources which parallels the mechanism for leases.

A few things are missing that we'll probably need in the future:

  - An "EXPIRES" command to update the expiration time. This would let resources be permanent while leased, then expire after, say, 24 hours without any leases.
  - A callback like `shouldActuallyExpireRightNow()` for resources and leases that lets them decide not to expire at the last second.
  - A callback like `didAcquireLease()` for resource blueprints, to parallel `didReleaseLease()`, letting them clear or extend their timer.

However, this stuff would mostly just let us tune behaviors, not really open up new capabilities.

Test Plan: Changed host resources to expire after 60 seconds, leased one, saw it vanish 60 seconds later.

Reviewers: hach-que, chad

Reviewed By: chad

Maniphest Tasks: T6569

Differential Revision: https://secure.phabricator.com/D14176
This commit is contained in:
epriestley 2015-09-28 09:35:14 -07:00
parent a3b49053c0
commit ec6d69e74d
10 changed files with 142 additions and 42 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
ADD until INT UNSIGNED;

View file

@ -8,4 +8,8 @@ final class DrydockDefaultViewCapability extends PhabricatorPolicyCapability {
return pht('Default Blueprint View Policy'); return pht('Default Blueprint View Policy');
} }
public function shouldAllowPublicPolicySetting() {
return true;
}
} }

View file

@ -15,7 +15,6 @@ final class DrydockConsoleController extends DrydockController {
$nav->addFilter('blueprint', pht('Blueprints')); $nav->addFilter('blueprint', pht('Blueprints'));
$nav->addFilter('resource', pht('Resources')); $nav->addFilter('resource', pht('Resources'));
$nav->addFilter('lease', pht('Leases')); $nav->addFilter('lease', pht('Leases'));
$nav->addFilter('log', pht('Logs'));
$nav->selectFilter(null); $nav->selectFilter(null);
@ -31,6 +30,7 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem( $menu->addItem(
id(new PHUIObjectItemView()) id(new PHUIObjectItemView())
->setHeader(pht('Blueprints')) ->setHeader(pht('Blueprints'))
->setFontIcon('fa-map-o')
->setHref($this->getApplicationURI('blueprint/')) ->setHref($this->getApplicationURI('blueprint/'))
->addAttribute( ->addAttribute(
pht( pht(
@ -40,6 +40,7 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem( $menu->addItem(
id(new PHUIObjectItemView()) id(new PHUIObjectItemView())
->setHeader(pht('Resources')) ->setHeader(pht('Resources'))
->setFontIcon('fa-map')
->setHref($this->getApplicationURI('resource/')) ->setHref($this->getApplicationURI('resource/'))
->addAttribute( ->addAttribute(
pht('View and manage resources Drydock has built, like hosts.'))); pht('View and manage resources Drydock has built, like hosts.')));
@ -47,16 +48,10 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem( $menu->addItem(
id(new PHUIObjectItemView()) id(new PHUIObjectItemView())
->setHeader(pht('Leases')) ->setHeader(pht('Leases'))
->setFontIcon('fa-link')
->setHref($this->getApplicationURI('lease/')) ->setHref($this->getApplicationURI('lease/'))
->addAttribute(pht('Manage leases on resources.'))); ->addAttribute(pht('Manage leases on resources.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Logs'))
->setHref($this->getApplicationURI('log/'))
->addAttribute(pht('View logs.')));
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Console')); $crumbs->addTextCrumb(pht('Console'));

View file

@ -116,6 +116,14 @@ final class DrydockResourceViewController extends DrydockResourceController {
pht('Status'), pht('Status'),
$status); $status);
$until = $resource->getUntil();
if ($until) {
$until_display = phabricator_datetime($until, $viewer);
} else {
$until_display = phutil_tag('em', array(), pht('Never'));
}
$view->addProperty(pht('Expires'), $until_display);
$view->addProperty( $view->addProperty(
pht('Resource Type'), pht('Resource Type'),
$resource->getType()); $resource->getType());

View file

@ -295,6 +295,15 @@ final class DrydockLease extends DrydockDAO
} }
} }
public function canUpdate() {
switch ($this->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
return true;
default:
return false;
}
}
public function scheduleUpdate($epoch = null) { public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask( PhabricatorWorker::scheduleTask(
'DrydockLeaseUpdateWorker', 'DrydockLeaseUpdateWorker',

View file

@ -7,6 +7,7 @@ final class DrydockResource extends DrydockDAO
protected $phid; protected $phid;
protected $blueprintPHID; protected $blueprintPHID;
protected $status; protected $status;
protected $until;
protected $type; protected $type;
protected $name; protected $name;
@ -32,6 +33,7 @@ final class DrydockResource extends DrydockDAO
'ownerPHID' => 'phid?', 'ownerPHID' => 'phid?',
'status' => 'text32', 'status' => 'text32',
'type' => 'text64', 'type' => 'text64',
'until' => 'epoch?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_type' => array( 'key_type' => array(
@ -126,6 +128,10 @@ final class DrydockResource extends DrydockDAO
$this->isAllocated = true; $this->isAllocated = true;
if ($new_status == DrydockResourceStatus::STATUS_ACTIVE) {
$this->didActivate();
}
return $this; return $this;
} }
@ -164,6 +170,8 @@ final class DrydockResource extends DrydockDAO
$this->isActivated = true; $this->isActivated = true;
$this->didActivate();
return $this; return $this;
} }
@ -181,14 +189,16 @@ final class DrydockResource extends DrydockDAO
} }
} }
public function scheduleUpdate() { public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask( PhabricatorWorker::scheduleTask(
'DrydockResourceUpdateWorker', 'DrydockResourceUpdateWorker',
array( array(
'resourcePHID' => $this->getPHID(), 'resourcePHID' => $this->getPHID(),
'isExpireTask' => ($epoch !== null),
), ),
array( array(
'objectPHID' => $this->getPHID(), 'objectPHID' => $this->getPHID(),
'delayUntil' => $epoch,
)); ));
} }
@ -209,6 +219,20 @@ final class DrydockResource extends DrydockDAO
if ($need_update) { if ($need_update) {
$this->scheduleUpdate(); $this->scheduleUpdate();
} }
$expires = $this->getUntil();
if ($expires) {
$this->scheduleUpdate($expires);
}
}
public function canUpdate() {
switch ($this->getStatus()) {
case DrydockResourceStatus::STATUS_ACTIVE:
return true;
default:
return false;
}
} }

View file

@ -23,31 +23,15 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
} }
private function updateLease(DrydockLease $lease) { private function updateLease(DrydockLease $lease) {
if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { if (!$lease->canUpdate()) {
return; return;
} }
$viewer = $this->getViewer(); $this->checkLeaseExpiration($lease);
$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()); $commands = $this->loadCommands($lease->getPHID());
foreach ($commands as $command) { foreach ($commands as $command) {
if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) { if (!$lease->canUpdate()) {
// Leases can't receive commands before they activate or after they
// release.
break; break;
} }
@ -58,15 +42,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
->save(); ->save();
} }
// If this is the task which will eventually release the lease after it $this->yieldIfExpiringLease($lease);
// 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( private function processCommand(

View file

@ -11,18 +11,27 @@ final class DrydockResourceUpdateWorker extends DrydockWorker {
$lock = PhabricatorGlobalLock::newLock($lock_key) $lock = PhabricatorGlobalLock::newLock($lock_key)
->lock(1); ->lock(1);
$resource = $this->loadResource($resource_phid); try {
$this->updateResource($resource); $resource = $this->loadResource($resource_phid);
$this->updateResource($resource);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
}
$lock->unlock(); $lock->unlock();
} }
private function updateResource(DrydockResource $resource) { private function updateResource(DrydockResource $resource) {
if (!$resource->canUpdate()) {
return;
}
$this->checkResourceExpiration($resource);
$commands = $this->loadCommands($resource->getPHID()); $commands = $this->loadCommands($resource->getPHID());
foreach ($commands as $command) { foreach ($commands as $command) {
if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) { if (!$resource->canUpdate()) {
// Resources can't receive commands before they activate or after they
// release.
break; break;
} }
@ -32,6 +41,8 @@ final class DrydockResourceUpdateWorker extends DrydockWorker {
->setIsConsumed(true) ->setIsConsumed(true)
->save(); ->save();
} }
$this->yieldIfExpiringResource($resource);
} }
private function processCommand( private function processCommand(

View file

@ -50,4 +50,68 @@ abstract class DrydockWorker extends PhabricatorWorker {
return $commands; return $commands;
} }
protected function checkLeaseExpiration(DrydockLease $lease) {
$this->checkObjectExpiration($lease);
}
protected function checkResourceExpiration(DrydockResource $resource) {
$this->checkObjectExpiration($resource);
}
private function checkObjectExpiration($object) {
// Check if the resource or lease has expired. If it has, we're going to
// send it a release command.
// This command is sent from within the update worker so it is handled
// immediately, but doing this generates a log and improves consistency.
$expires = $object->getUntil();
if (!$expires) {
return;
}
$now = PhabricatorTime::getNow();
if ($expires > $now) {
return;
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($object->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
}
protected function yieldIfExpiringLease(DrydockLease $lease) {
if (!$lease->canUpdate()) {
return;
}
$this->yieldIfExpiring($lease->getUntil());
}
protected function yieldIfExpiringResource(DrydockResource $resource) {
if (!$resource->canUpdate()) {
return;
}
$this->yieldIfExpiring($resource->getUntil());
}
private function yieldIfExpiring($expires) {
if (!$expires) {
return;
}
if (!$this->getTaskDataValue('isExpireTask')) {
return;
}
$now = PhabricatorTime::getNow();
throw new PhabricatorWorkerYieldException($expires - $now);
}
} }

View file

@ -39,6 +39,13 @@ abstract class PhabricatorWorkerManagementWorkflow
} }
} }
// When we lock tasks properly, this gets populated as a side effect. Just
// fake it when doing manual CLI stuff. This makes sure CLI yields have
// their expires times set properly.
foreach ($tasks as $task) {
$task->setServerTime(PhabricatorTime::getNow());
}
return $tasks; return $tasks;
} }