mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-14 10:52:41 +01:00
309aadc595
Summary: Ref T9252. This is now more consistent (same as the equivalent Resource state) and accurate (leases can end up in this state a bunch of ways, including by expiring). Test Plan: `grep`, browsed around web UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9252 Differential Revision: https://secure.phabricator.com/D14150
367 lines
9.2 KiB
PHP
367 lines
9.2 KiB
PHP
<?php
|
|
|
|
final class DrydockLease extends DrydockDAO
|
|
implements PhabricatorPolicyInterface {
|
|
|
|
protected $resourceID;
|
|
protected $resourceType;
|
|
protected $until;
|
|
protected $ownerPHID;
|
|
protected $attributes = array();
|
|
protected $status = DrydockLeaseStatus::STATUS_PENDING;
|
|
|
|
private $resource = self::ATTACHABLE;
|
|
private $releaseOnDestruction;
|
|
private $isAcquired = false;
|
|
private $isActivated = false;
|
|
private $activateWhenAcquired = false;
|
|
private $slotLocks = array();
|
|
|
|
/**
|
|
* Flag this lease to be released when its destructor is called. This is
|
|
* mostly useful if you have a script which acquires, uses, and then releases
|
|
* a lease, as you don't need to explicitly handle exceptions to properly
|
|
* release the lease.
|
|
*/
|
|
public function releaseOnDestruction() {
|
|
$this->releaseOnDestruction = true;
|
|
return $this;
|
|
}
|
|
|
|
public function __destruct() {
|
|
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() {
|
|
return pht('Lease %d', $this->getID());
|
|
}
|
|
|
|
protected function getConfiguration() {
|
|
return array(
|
|
self::CONFIG_AUX_PHID => true,
|
|
self::CONFIG_SERIALIZATION => array(
|
|
'attributes' => self::SERIALIZATION_JSON,
|
|
),
|
|
self::CONFIG_COLUMN_SCHEMA => array(
|
|
'status' => 'uint32',
|
|
'until' => 'epoch?',
|
|
'resourceType' => 'text128',
|
|
'ownerPHID' => 'phid?',
|
|
'resourceID' => 'id?',
|
|
),
|
|
self::CONFIG_KEY_SCHEMA => array(
|
|
'key_phid' => null,
|
|
'phid' => array(
|
|
'columns' => array('phid'),
|
|
'unique' => true,
|
|
),
|
|
),
|
|
) + parent::getConfiguration();
|
|
}
|
|
|
|
public function setAttribute($key, $value) {
|
|
$this->attributes[$key] = $value;
|
|
return $this;
|
|
}
|
|
|
|
public function getAttribute($key, $default = null) {
|
|
return idx($this->attributes, $key, $default);
|
|
}
|
|
|
|
public function generatePHID() {
|
|
return PhabricatorPHID::generateNewPHID(DrydockLeasePHIDType::TYPECONST);
|
|
}
|
|
|
|
public function getInterface($type) {
|
|
return $this->getResource()->getInterface($this, $type);
|
|
}
|
|
|
|
public function getResource() {
|
|
return $this->assertAttached($this->resource);
|
|
}
|
|
|
|
public function attachResource(DrydockResource $resource = null) {
|
|
$this->resource = $resource;
|
|
return $this;
|
|
}
|
|
|
|
public function hasAttachedResource() {
|
|
return ($this->resource !== null);
|
|
}
|
|
|
|
public function queueForActivation() {
|
|
if ($this->getID()) {
|
|
throw new Exception(
|
|
pht('Only new leases may be queued for activation!'));
|
|
}
|
|
|
|
$this
|
|
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
|
|
->save();
|
|
|
|
$task = PhabricatorWorker::scheduleTask(
|
|
'DrydockAllocatorWorker',
|
|
array(
|
|
'leasePHID' => $this->getPHID(),
|
|
),
|
|
array(
|
|
'objectPHID' => $this->getPHID(),
|
|
));
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isActive() {
|
|
switch ($this->status) {
|
|
case DrydockLeaseStatus::STATUS_ACQUIRED:
|
|
case DrydockLeaseStatus::STATUS_ACTIVE:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function assertActive() {
|
|
if (!$this->isActive()) {
|
|
throw new Exception(
|
|
pht(
|
|
'Lease is not active! You can not interact with resources through '.
|
|
'an inactive lease.'));
|
|
}
|
|
}
|
|
|
|
public function waitUntilActive() {
|
|
while (true) {
|
|
$lease = $this->reload();
|
|
if (!$lease) {
|
|
throw new Exception(pht('Failed to reload lease.'));
|
|
}
|
|
|
|
$status = $lease->getStatus();
|
|
|
|
switch ($status) {
|
|
case DrydockLeaseStatus::STATUS_ACTIVE:
|
|
return;
|
|
case DrydockLeaseStatus::STATUS_RELEASED:
|
|
throw new Exception(pht('Lease has already been released!'));
|
|
case DrydockLeaseStatus::STATUS_DESTROYED:
|
|
throw new Exception(pht('Lease has already been destroyed!'));
|
|
case DrydockLeaseStatus::STATUS_BROKEN:
|
|
throw new Exception(pht('Lease has been broken!'));
|
|
case DrydockLeaseStatus::STATUS_PENDING:
|
|
case DrydockLeaseStatus::STATUS_ACQUIRED:
|
|
break;
|
|
default:
|
|
throw new Exception(
|
|
pht(
|
|
'Lease has unknown status "%s".',
|
|
$status));
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
public function setActivateWhenAcquired($activate) {
|
|
$this->activateWhenAcquired = true;
|
|
return $this;
|
|
}
|
|
|
|
public function needSlotLock($key) {
|
|
$this->slotLocks[] = $key;
|
|
return $this;
|
|
}
|
|
|
|
public function acquireOnResource(DrydockResource $resource) {
|
|
$expect_status = DrydockLeaseStatus::STATUS_PENDING;
|
|
$actual_status = $this->getStatus();
|
|
if ($actual_status != $expect_status) {
|
|
throw new Exception(
|
|
pht(
|
|
'Trying to acquire a lease on a resource which is in the wrong '.
|
|
'state: status must be "%s", actually "%s".',
|
|
$expect_status,
|
|
$actual_status));
|
|
}
|
|
|
|
if ($this->activateWhenAcquired) {
|
|
$new_status = DrydockLeaseStatus::STATUS_ACTIVE;
|
|
} else {
|
|
$new_status = DrydockLeaseStatus::STATUS_ACQUIRED;
|
|
}
|
|
|
|
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
|
|
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
|
|
throw new Exception(
|
|
pht(
|
|
'Trying to acquire an active lease on a pending resource. '.
|
|
'You can not immediately activate leases on resources which '.
|
|
'need time to start up.'));
|
|
}
|
|
}
|
|
|
|
$this->openTransaction();
|
|
|
|
$this
|
|
->setResourceID($resource->getID())
|
|
->setStatus($new_status)
|
|
->save();
|
|
|
|
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
|
|
$this->slotLocks = array();
|
|
|
|
$this->saveTransaction();
|
|
|
|
$this->isAcquired = true;
|
|
|
|
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
|
|
$this->didActivate();
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isAcquiredLease() {
|
|
return $this->isAcquired;
|
|
}
|
|
|
|
public function activateOnResource(DrydockResource $resource) {
|
|
$expect_status = DrydockLeaseStatus::STATUS_ACQUIRED;
|
|
$actual_status = $this->getStatus();
|
|
if ($actual_status != $expect_status) {
|
|
throw new Exception(
|
|
pht(
|
|
'Trying to activate a lease which has the wrong status: status '.
|
|
'must be "%s", actually "%s".',
|
|
$expect_status,
|
|
$actual_status));
|
|
}
|
|
|
|
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
|
|
// TODO: Be stricter about this?
|
|
throw new Exception(
|
|
pht(
|
|
'Trying to activate a lease on a pending resource.'));
|
|
}
|
|
|
|
$this->openTransaction();
|
|
|
|
$this
|
|
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
|
|
->save();
|
|
|
|
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
|
|
$this->slotLocks = array();
|
|
|
|
$this->saveTransaction();
|
|
|
|
$this->isActivated = true;
|
|
|
|
$this->didActivate();
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isActivatedLease() {
|
|
return $this->isActivated;
|
|
}
|
|
|
|
public function canRelease() {
|
|
if (!$this->getID()) {
|
|
return false;
|
|
}
|
|
|
|
switch ($this->getStatus()) {
|
|
case DrydockLeaseStatus::STATUS_RELEASED:
|
|
case DrydockLeaseStatus::STATUS_DESTROYED:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function scheduleUpdate($epoch = null) {
|
|
PhabricatorWorker::scheduleTask(
|
|
'DrydockLeaseUpdateWorker',
|
|
array(
|
|
'leasePHID' => $this->getPHID(),
|
|
'isExpireTask' => ($epoch !== null),
|
|
),
|
|
array(
|
|
'objectPHID' => $this->getPHID(),
|
|
'delayUntil' => $epoch,
|
|
));
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
$expires = $this->getUntil();
|
|
if ($expires) {
|
|
$this->scheduleUpdate($expires);
|
|
}
|
|
}
|
|
|
|
|
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
|
|
|
|
|
public function getCapabilities() {
|
|
return array(
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
);
|
|
}
|
|
|
|
public function getPolicy($capability) {
|
|
if ($this->getResource()) {
|
|
return $this->getResource()->getPolicy($capability);
|
|
}
|
|
|
|
// TODO: Implement reasonable policies.
|
|
|
|
return PhabricatorPolicies::getMostOpenPolicy();
|
|
}
|
|
|
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
|
if ($this->getResource()) {
|
|
return $this->getResource()->hasAutomaticCapability($capability, $viewer);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function describeAutomaticCapability($capability) {
|
|
return pht('Leases inherit policies from the resources they lease.');
|
|
}
|
|
|
|
}
|