Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893621
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
21 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/resources/sql/autopatches/20180827.drydock.01.acquired.sql b/resources/sql/autopatches/20180827.drydock.01.acquired.sql
new file mode 100644
index 0000000000..55948391c9
--- /dev/null
+++ b/resources/sql/autopatches/20180827.drydock.01.acquired.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
+ ADD acquiredEpoch INT UNSIGNED;
diff --git a/resources/sql/autopatches/20180827.drydock.02.activated.sql b/resources/sql/autopatches/20180827.drydock.02.activated.sql
new file mode 100644
index 0000000000..552f7b6b24
--- /dev/null
+++ b/resources/sql/autopatches/20180827.drydock.02.activated.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
+ ADD activatedEpoch INT UNSIGNED;
diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php
index 91a911277f..18e1d0088d 100644
--- a/src/applications/drydock/controller/DrydockLeaseViewController.php
+++ b/src/applications/drydock/controller/DrydockLeaseViewController.php
@@ -1,178 +1,202 @@
<?php
final class DrydockLeaseViewController extends DrydockLeaseController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs(array($id))
->needUnconsumedCommands(true)
->executeOne();
if (!$lease) {
return new Aphront404Response();
}
$id = $lease->getID();
$lease_uri = $this->getApplicationURI("lease/{$id}/");
$title = pht('Lease %d', $lease->getID());
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-link')
->setStatus(
$lease->getStatusIcon(),
$lease->getStatusColor(),
$lease->getStatusDisplayName());
if ($lease->isReleasing()) {
$header->addTag(
id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setIcon('fa-exclamation-triangle')
->setColor('red')
->setName('Releasing'));
}
$curtain = $this->buildCurtain($lease);
$properties = $this->buildPropertyListView($lease);
$log_query = id(new DrydockLogQuery())
->withLeasePHIDs(array($lease->getPHID()));
$logs = $this->buildLogBox(
$log_query,
$this->getApplicationURI("lease/{$id}/logs/query/all/"));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title, $lease_uri);
$crumbs->setBorder(true);
$locks = $this->buildLocksTab($lease->getPHID());
$commands = $this->buildCommandsTab($lease->getPHID());
$tab_group = id(new PHUITabGroupView())
->addTab(
id(new PHUITabView())
->setName(pht('Properties'))
->setKey('properties')
->appendChild($properties))
->addTab(
id(new PHUITabView())
->setName(pht('Slot Locks'))
->setKey('locks')
->appendChild($locks))
->addTab(
id(new PHUITabView())
->setName(pht('Commands'))
->setKey('commands')
->appendChild($commands));
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addTabGroup($tab_group);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$object_box,
$logs,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
}
private function buildCurtain(DrydockLease $lease) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($lease);
$id = $lease->getID();
$can_release = $lease->canRelease();
if ($lease->isReleasing()) {
$can_release = false;
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$lease,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Release Lease'))
->setIcon('fa-times')
->setHref($this->getApplicationURI("/lease/{$id}/release/"))
->setWorkflow(true)
->setDisabled(!$can_release || !$can_edit));
return $curtain;
}
private function buildPropertyListView(
DrydockLease $lease) {
$viewer = $this->getViewer();
$view = new PHUIPropertyListView();
$view->addProperty(
pht('Resource Type'),
$lease->getResourceType());
$owner_phid = $lease->getOwnerPHID();
if ($owner_phid) {
$owner_display = $viewer->renderHandle($owner_phid);
} else {
$owner_display = phutil_tag('em', array(), pht('No Owner'));
}
$view->addProperty(pht('Owner'), $owner_display);
$authorizing_phid = $lease->getAuthorizingPHID();
if ($authorizing_phid) {
$authorizing_display = $viewer->renderHandle($authorizing_phid);
} else {
$authorizing_display = phutil_tag('em', array(), pht('None'));
}
$view->addProperty(pht('Authorized By'), $authorizing_display);
$resource_phid = $lease->getResourcePHID();
if ($resource_phid) {
$resource_display = $viewer->renderHandle($resource_phid);
} else {
$resource_display = phutil_tag('em', array(), pht('No Resource'));
}
$view->addProperty(pht('Resource'), $resource_display);
$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);
+ $acquired_epoch = $lease->getAcquiredEpoch();
+ $activated_epoch = $lease->getActivatedEpoch();
+
+ if ($acquired_epoch) {
+ $acquired_display = phabricator_datetime($acquired_epoch, $viewer);
+ } else {
+ if ($activated_epoch) {
+ $acquired_display = phutil_tag(
+ 'em',
+ array(),
+ pht('Activated on Acquisition'));
+ } else {
+ $acquired_display = phutil_tag('em', array(), pht('Not Acquired'));
+ }
+ }
+ $view->addProperty(pht('Acquired'), $acquired_display);
+
+ if ($activated_epoch) {
+ $activated_display = phabricator_datetime($activated_epoch, $viewer);
+ } else {
+ $activated_display = phutil_tag('em', array(), pht('Not Activated'));
+ }
+ $view->addProperty(pht('Activated'), $activated_display);
+
$attributes = $lease->getAttributes();
if ($attributes) {
$view->addSectionHeader(
pht('Attributes'), 'fa-list-ul');
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}
}
return $view;
}
}
diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php
index 4cee7a5f17..866bb21b37 100644
--- a/src/applications/drydock/storage/DrydockLease.php
+++ b/src/applications/drydock/storage/DrydockLease.php
@@ -1,518 +1,538 @@
<?php
final class DrydockLease extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourcePHID;
protected $resourceType;
protected $until;
protected $ownerPHID;
protected $authorizingPHID;
protected $attributes = array();
protected $status = DrydockLeaseStatus::STATUS_PENDING;
+ protected $acquiredEpoch;
+ protected $activatedEpoch;
private $resource = self::ATTACHABLE;
private $unconsumedCommands = self::ATTACHABLE;
private $releaseOnDestruction;
private $isAcquired = false;
private $isActivated = false;
private $activateWhenAcquired = false;
private $slotLocks = array();
public static function initializeNewLease() {
$lease = new DrydockLease();
// Pregenerate a PHID so that the caller can set something up to release
// this lease before queueing it for activation.
$lease->setPHID($lease->generatePHID());
return $lease;
}
/**
* 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 setReleaseOnDestruction($release) {
$this->releaseOnDestruction = $release;
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 setStatus($status) {
+ if ($status == DrydockLeaseStatus::STATUS_ACQUIRED) {
+ if (!$this->getAcquiredEpoch()) {
+ $this->setAcquiredEpoch(PhabricatorTime::getNow());
+ }
+ }
+
+ if ($status == DrydockLeaseStatus::STATUS_ACTIVE) {
+ if (!$this->getActivatedEpoch()) {
+ $this->setActivatedEpoch(PhabricatorTime::getNow());
+ }
+ }
+
+ return parent::setStatus($status);
+ }
+
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' => 'text32',
'until' => 'epoch?',
'resourceType' => 'text128',
'ownerPHID' => 'phid?',
'resourcePHID' => 'phid?',
+ 'acquiredEpoch' => 'epoch?',
+ 'activatedEpoch' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_resource' => array(
'columns' => array('resourcePHID', 'status'),
),
'key_status' => array(
'columns' => array('status'),
),
),
) + 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 getUnconsumedCommands() {
return $this->assertAttached($this->unconsumedCommands);
}
public function attachUnconsumedCommands(array $commands) {
$this->unconsumedCommands = $commands;
return $this;
}
public function isReleasing() {
foreach ($this->getUnconsumedCommands() as $command) {
if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {
return true;
}
}
return false;
}
public function queueForActivation() {
if ($this->getID()) {
throw new Exception(
pht('Only new leases may be queued for activation!'));
}
if (!$this->getAuthorizingPHID()) {
throw new Exception(
pht(
'Trying to queue a lease for activation without an authorizing '.
'object. Use "%s" to specify the PHID of the authorizing object. '.
'The authorizing object must be approved to use the allowed '.
'blueprints.',
'setAuthorizingPHID()'));
}
if (!$this->getAllowedBlueprintPHIDs()) {
throw new Exception(
pht(
'Trying to queue a lease for activation without any allowed '.
'Blueprints. Use "%s" to specify allowed blueprints. The '.
'authorizing object must be approved to use the allowed blueprints.',
'setAllowedBlueprintPHIDs()'));
}
$this
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
->save();
$this->scheduleUpdate();
$this->logEvent(DrydockLeaseQueuedLogType::LOGCONST);
return $this;
}
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.'));
}
}
// Before we associate the lease with the resource, we lock the resource
// and reload it to make sure it is still pending or active. If we don't
// do this, the resource may have just been reclaimed. (Once we acquire
// the resource that stops it from being released, so we're nearly safe.)
$resource_phid = $resource->getPHID();
$hash = PhabricatorHash::digestForIndex($resource_phid);
$lock_key = 'drydock.resource:'.$hash;
$lock = PhabricatorGlobalLock::newLock($lock_key);
try {
$lock->lock(15);
} catch (Exception $ex) {
throw new DrydockResourceLockException(
pht(
'Failed to acquire lock for resource ("%s") while trying to '.
'acquire lease ("%s").',
$resource->getPHID(),
$this->getPHID()));
}
$resource->reload();
if (($resource->getStatus() !== DrydockResourceStatus::STATUS_ACTIVE) &&
($resource->getStatus() !== DrydockResourceStatus::STATUS_PENDING)) {
throw new DrydockAcquiredBrokenResourceException(
pht(
'Trying to acquire lease ("%s") on a resource ("%s") in the '.
'wrong status ("%s").',
$this->getPHID(),
$resource->getPHID(),
$resource->getStatus()));
}
$caught = null;
try {
$this->openTransaction();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setResourcePHID($resource->getPHID())
->attachResource($resource)
->setStatus($new_status)
->save();
$this->saveTransaction();
} catch (Exception $ex) {
$caught = $ex;
}
$lock->unlock();
if ($caught) {
throw $caught;
}
$this->isAcquired = true;
$this->logEvent(DrydockLeaseAcquiredLogType::LOGCONST);
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();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction();
$this->isActivated = true;
$this->didActivate();
return $this;
}
public function isActivatedLease() {
return $this->isActivated;
}
public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask(
'DrydockLeaseUpdateWorker',
array(
'leasePHID' => $this->getPHID(),
'isExpireTask' => ($epoch !== null),
),
array(
'objectPHID' => $this->getPHID(),
'delayUntil' => ($epoch ? (int)$epoch : null),
));
}
public function setAwakenTaskIDs(array $ids) {
$this->setAttribute('internal.awakenTaskIDs', $ids);
return $this;
}
public function setAllowedBlueprintPHIDs(array $phids) {
$this->setAttribute('internal.blueprintPHIDs', $phids);
return $this;
}
public function getAllowedBlueprintPHIDs() {
return $this->getAttribute('internal.blueprintPHIDs', array());
}
private function didActivate() {
$viewer = PhabricatorUser::getOmnipotentUser();
$need_update = false;
$this->logEvent(DrydockLeaseActivatedLogType::LOGCONST);
$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);
}
$this->awakenTasks();
}
public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow())
->setType($type)
->setData($data);
$log->setLeasePHID($this->getPHID());
$resource_phid = $this->getResourcePHID();
if ($resource_phid) {
$resource = $this->getResource();
$log->setResourcePHID($resource->getPHID());
$log->setBlueprintPHID($resource->getBlueprintPHID());
}
return $log->save();
}
/**
* Awaken yielded tasks after a state change.
*
* @return this
*/
public function awakenTasks() {
$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');
if (is_array($awaken_ids) && $awaken_ids) {
PhabricatorWorker::awakenTaskIDs($awaken_ids);
}
return $this;
}
public function getURI() {
$id = $this->getID();
return "/drydock/lease/{$id}/";
}
/* -( Status )------------------------------------------------------------- */
public function getStatusObject() {
return DrydockLeaseStatus::newStatusObject($this->getStatus());
}
public function getStatusIcon() {
return $this->getStatusObject()->getIcon();
}
public function getStatusColor() {
return $this->getStatusObject()->getColor();
}
public function getStatusDisplayName() {
return $this->getStatusObject()->getDisplayName();
}
public function isActivating() {
return $this->getStatusObject()->isActivating();
}
public function isActive() {
return $this->getStatusObject()->isActive();
}
public function canRelease() {
if (!$this->getID()) {
return false;
}
return $this->getStatusObject()->canRelease();
}
public function canReceiveCommands() {
return $this->getStatusObject()->canReceiveCommands();
}
/* -( 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.');
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 18:47 (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127542
Default Alt Text
(21 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment