Path: blob/master/src/applications/drydock/storage/DrydockLease.php
12256 views
<?php12final class DrydockLease extends DrydockDAO3implements4PhabricatorPolicyInterface,5PhabricatorConduitResultInterface {67protected $resourcePHID;8protected $resourceType;9protected $until;10protected $ownerPHID;11protected $authorizingPHID;12protected $attributes = array();13protected $status = DrydockLeaseStatus::STATUS_PENDING;14protected $acquiredEpoch;15protected $activatedEpoch;1617private $resource = self::ATTACHABLE;18private $unconsumedCommands = self::ATTACHABLE;1920private $releaseOnDestruction;21private $isAcquired = false;22private $isActivated = false;23private $activateWhenAcquired = false;24private $slotLocks = array();2526public static function initializeNewLease() {27$lease = new DrydockLease();2829// Pregenerate a PHID so that the caller can set something up to release30// this lease before queueing it for activation.31$lease->setPHID($lease->generatePHID());3233return $lease;34}3536/**37* Flag this lease to be released when its destructor is called. This is38* mostly useful if you have a script which acquires, uses, and then releases39* a lease, as you don't need to explicitly handle exceptions to properly40* release the lease.41*/42public function setReleaseOnDestruction($release) {43$this->releaseOnDestruction = $release;44return $this;45}4647public function __destruct() {48if (!$this->releaseOnDestruction) {49return;50}5152if (!$this->canRelease()) {53return;54}5556$actor = PhabricatorUser::getOmnipotentUser();57$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();5859$command = DrydockCommand::initializeNewCommand($actor)60->setTargetPHID($this->getPHID())61->setAuthorPHID($drydock_phid)62->setCommand(DrydockCommand::COMMAND_RELEASE)63->save();6465$this->scheduleUpdate();66}6768public function setStatus($status) {69if ($status == DrydockLeaseStatus::STATUS_ACQUIRED) {70if (!$this->getAcquiredEpoch()) {71$this->setAcquiredEpoch(PhabricatorTime::getNow());72}73}7475if ($status == DrydockLeaseStatus::STATUS_ACTIVE) {76if (!$this->getActivatedEpoch()) {77$this->setActivatedEpoch(PhabricatorTime::getNow());78}79}8081return parent::setStatus($status);82}8384public function getLeaseName() {85return pht('Lease %d', $this->getID());86}8788protected function getConfiguration() {89return array(90self::CONFIG_AUX_PHID => true,91self::CONFIG_SERIALIZATION => array(92'attributes' => self::SERIALIZATION_JSON,93),94self::CONFIG_COLUMN_SCHEMA => array(95'status' => 'text32',96'until' => 'epoch?',97'resourceType' => 'text128',98'ownerPHID' => 'phid?',99'resourcePHID' => 'phid?',100'acquiredEpoch' => 'epoch?',101'activatedEpoch' => 'epoch?',102),103self::CONFIG_KEY_SCHEMA => array(104'key_resource' => array(105'columns' => array('resourcePHID', 'status'),106),107'key_status' => array(108'columns' => array('status'),109),110'key_owner' => array(111'columns' => array('ownerPHID'),112),113'key_recent' => array(114'columns' => array('resourcePHID', 'dateModified'),115),116),117) + parent::getConfiguration();118}119120public function setAttribute($key, $value) {121$this->attributes[$key] = $value;122return $this;123}124125public function getAttribute($key, $default = null) {126return idx($this->attributes, $key, $default);127}128129public function generatePHID() {130return PhabricatorPHID::generateNewPHID(DrydockLeasePHIDType::TYPECONST);131}132133public function getInterface($type) {134return $this->getResource()->getInterface($this, $type);135}136137public function getResource() {138return $this->assertAttached($this->resource);139}140141public function attachResource(DrydockResource $resource = null) {142$this->resource = $resource;143return $this;144}145146public function hasAttachedResource() {147return ($this->resource !== null);148}149150public function getUnconsumedCommands() {151return $this->assertAttached($this->unconsumedCommands);152}153154public function attachUnconsumedCommands(array $commands) {155$this->unconsumedCommands = $commands;156return $this;157}158159public function isReleasing() {160foreach ($this->getUnconsumedCommands() as $command) {161if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {162return true;163}164}165166return false;167}168169public function queueForActivation() {170if ($this->getID()) {171throw new Exception(172pht('Only new leases may be queued for activation!'));173}174175if (!$this->getAuthorizingPHID()) {176throw new Exception(177pht(178'Trying to queue a lease for activation without an authorizing '.179'object. Use "%s" to specify the PHID of the authorizing object. '.180'The authorizing object must be approved to use the allowed '.181'blueprints.',182'setAuthorizingPHID()'));183}184185if (!$this->getAllowedBlueprintPHIDs()) {186throw new Exception(187pht(188'Trying to queue a lease for activation without any allowed '.189'Blueprints. Use "%s" to specify allowed blueprints. The '.190'authorizing object must be approved to use the allowed blueprints.',191'setAllowedBlueprintPHIDs()'));192}193194$this195->setStatus(DrydockLeaseStatus::STATUS_PENDING)196->save();197198$this->scheduleUpdate();199200$this->logEvent(DrydockLeaseQueuedLogType::LOGCONST);201202return $this;203}204205public function setActivateWhenAcquired($activate) {206$this->activateWhenAcquired = true;207return $this;208}209210public function needSlotLock($key) {211$this->slotLocks[] = $key;212return $this;213}214215public function acquireOnResource(DrydockResource $resource) {216$expect_status = DrydockLeaseStatus::STATUS_PENDING;217$actual_status = $this->getStatus();218if ($actual_status != $expect_status) {219throw new Exception(220pht(221'Trying to acquire a lease on a resource which is in the wrong '.222'state: status must be "%s", actually "%s".',223$expect_status,224$actual_status));225}226227if ($this->activateWhenAcquired) {228$new_status = DrydockLeaseStatus::STATUS_ACTIVE;229} else {230$new_status = DrydockLeaseStatus::STATUS_ACQUIRED;231}232233if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {234if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {235throw new Exception(236pht(237'Trying to acquire an active lease on a pending resource. '.238'You can not immediately activate leases on resources which '.239'need time to start up.'));240}241}242243// Before we associate the lease with the resource, we lock the resource244// and reload it to make sure it is still pending or active. If we don't245// do this, the resource may have just been reclaimed. (Once we acquire246// the resource that stops it from being released, so we're nearly safe.)247248$resource_phid = $resource->getPHID();249$hash = PhabricatorHash::digestForIndex($resource_phid);250$lock_key = 'drydock.resource:'.$hash;251$lock = PhabricatorGlobalLock::newLock($lock_key);252253try {254$lock->lock(15);255} catch (Exception $ex) {256throw new DrydockResourceLockException(257pht(258'Failed to acquire lock for resource ("%s") while trying to '.259'acquire lease ("%s").',260$resource->getPHID(),261$this->getPHID()));262}263264$resource->reload();265266if (($resource->getStatus() !== DrydockResourceStatus::STATUS_ACTIVE) &&267($resource->getStatus() !== DrydockResourceStatus::STATUS_PENDING)) {268throw new DrydockAcquiredBrokenResourceException(269pht(270'Trying to acquire lease ("%s") on a resource ("%s") in the '.271'wrong status ("%s").',272$this->getPHID(),273$resource->getPHID(),274$resource->getStatus()));275}276277$caught = null;278try {279$this->openTransaction();280281try {282DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);283$this->slotLocks = array();284} catch (DrydockSlotLockException $ex) {285$this->killTransaction();286287$this->logEvent(288DrydockSlotLockFailureLogType::LOGCONST,289array(290'locks' => $ex->getLockMap(),291));292293throw $ex;294}295296$this297->setResourcePHID($resource->getPHID())298->attachResource($resource)299->setStatus($new_status)300->save();301302$this->saveTransaction();303} catch (Exception $ex) {304$caught = $ex;305}306307$lock->unlock();308309if ($caught) {310throw $caught;311}312313$this->isAcquired = true;314315$this->logEvent(DrydockLeaseAcquiredLogType::LOGCONST);316317if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {318$this->didActivate();319}320321return $this;322}323324public function isAcquiredLease() {325return $this->isAcquired;326}327328public function activateOnResource(DrydockResource $resource) {329$expect_status = DrydockLeaseStatus::STATUS_ACQUIRED;330$actual_status = $this->getStatus();331if ($actual_status != $expect_status) {332throw new Exception(333pht(334'Trying to activate a lease which has the wrong status: status '.335'must be "%s", actually "%s".',336$expect_status,337$actual_status));338}339340if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {341// TODO: Be stricter about this?342throw new Exception(343pht(344'Trying to activate a lease on a pending resource.'));345}346347$this->openTransaction();348349try {350DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);351$this->slotLocks = array();352} catch (DrydockSlotLockException $ex) {353$this->killTransaction();354355$this->logEvent(356DrydockSlotLockFailureLogType::LOGCONST,357array(358'locks' => $ex->getLockMap(),359));360361throw $ex;362}363364$this365->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)366->save();367368$this->saveTransaction();369370$this->isActivated = true;371372$this->didActivate();373374return $this;375}376377public function isActivatedLease() {378return $this->isActivated;379}380381public function scheduleUpdate($epoch = null) {382PhabricatorWorker::scheduleTask(383'DrydockLeaseUpdateWorker',384array(385'leasePHID' => $this->getPHID(),386'isExpireTask' => ($epoch !== null),387),388array(389'objectPHID' => $this->getPHID(),390'delayUntil' => ($epoch ? (int)$epoch : null),391));392}393394public function getAllocatedResourcePHIDs() {395return $this->getAttribute('internal.resourcePHIDs.allocated', array());396}397398public function setAllocatedResourcePHIDs(array $phids) {399return $this->setAttribute('internal.resourcePHIDs.allocated', $phids);400}401402public function addAllocatedResourcePHIDs(array $phids) {403$allocated_phids = $this->getAllocatedResourcePHIDs();404405foreach ($phids as $phid) {406$allocated_phids[$phid] = $phid;407}408409return $this->setAllocatedResourcePHIDs($allocated_phids);410}411412public function removeAllocatedResourcePHIDs(array $phids) {413$allocated_phids = $this->getAllocatedResourcePHIDs();414415foreach ($phids as $phid) {416unset($allocated_phids[$phid]);417}418419return $this->setAllocatedResourcePHIDs($allocated_phids);420}421422public function getReclaimedResourcePHIDs() {423return $this->getAttribute('internal.resourcePHIDs.reclaimed', array());424}425426public function setReclaimedResourcePHIDs(array $phids) {427return $this->setAttribute('internal.resourcePHIDs.reclaimed', $phids);428}429430public function addReclaimedResourcePHIDs(array $phids) {431$reclaimed_phids = $this->getReclaimedResourcePHIDs();432433foreach ($phids as $phid) {434$reclaimed_phids[$phid] = $phid;435}436437return $this->setReclaimedResourcePHIDs($reclaimed_phids);438}439440public function removeReclaimedResourcePHIDs(array $phids) {441$reclaimed_phids = $this->getReclaimedResourcePHIDs();442443foreach ($phids as $phid) {444unset($reclaimed_phids[$phid]);445}446447return $this->setReclaimedResourcePHIDs($reclaimed_phids);448}449450public function setAwakenTaskIDs(array $ids) {451$this->setAttribute('internal.awakenTaskIDs', $ids);452return $this;453}454455public function setAllowedBlueprintPHIDs(array $phids) {456$this->setAttribute('internal.blueprintPHIDs', $phids);457return $this;458}459460public function getAllowedBlueprintPHIDs() {461return $this->getAttribute('internal.blueprintPHIDs', array());462}463464private function didActivate() {465$viewer = PhabricatorUser::getOmnipotentUser();466$need_update = false;467468$this->logEvent(DrydockLeaseActivatedLogType::LOGCONST);469470$commands = id(new DrydockCommandQuery())471->setViewer($viewer)472->withTargetPHIDs(array($this->getPHID()))473->withConsumed(false)474->execute();475if ($commands) {476$need_update = true;477}478479if ($need_update) {480$this->scheduleUpdate();481}482483$expires = $this->getUntil();484if ($expires) {485$this->scheduleUpdate($expires);486}487488$this->awakenTasks();489}490491public function logEvent($type, array $data = array()) {492$log = id(new DrydockLog())493->setEpoch(PhabricatorTime::getNow())494->setType($type)495->setData($data);496497$log->setLeasePHID($this->getPHID());498499$resource_phid = $this->getResourcePHID();500if ($resource_phid) {501$resource = $this->getResource();502503$log->setResourcePHID($resource->getPHID());504$log->setBlueprintPHID($resource->getBlueprintPHID());505}506507return $log->save();508}509510/**511* Awaken yielded tasks after a state change.512*513* @return this514*/515public function awakenTasks() {516$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');517if (is_array($awaken_ids) && $awaken_ids) {518PhabricatorWorker::awakenTaskIDs($awaken_ids);519}520521return $this;522}523524public function getURI() {525$id = $this->getID();526return "/drydock/lease/{$id}/";527}528529public function getDisplayName() {530return pht('Drydock Lease %d', $this->getID());531}532533534/* -( Status )------------------------------------------------------------- */535536537public function getStatusObject() {538return DrydockLeaseStatus::newStatusObject($this->getStatus());539}540541public function getStatusIcon() {542return $this->getStatusObject()->getIcon();543}544545public function getStatusColor() {546return $this->getStatusObject()->getColor();547}548549public function getStatusDisplayName() {550return $this->getStatusObject()->getDisplayName();551}552553public function isActivating() {554return $this->getStatusObject()->isActivating();555}556557public function isActive() {558return $this->getStatusObject()->isActive();559}560561public function canRelease() {562if (!$this->getID()) {563return false;564}565566return $this->getStatusObject()->canRelease();567}568569public function canReceiveCommands() {570return $this->getStatusObject()->canReceiveCommands();571}572573574/* -( PhabricatorPolicyInterface )----------------------------------------- */575576577public function getCapabilities() {578return array(579PhabricatorPolicyCapability::CAN_VIEW,580PhabricatorPolicyCapability::CAN_EDIT,581);582}583584public function getPolicy($capability) {585if ($this->getResource()) {586return $this->getResource()->getPolicy($capability);587}588589// TODO: Implement reasonable policies.590591return PhabricatorPolicies::getMostOpenPolicy();592}593594public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {595if ($this->getResource()) {596return $this->getResource()->hasAutomaticCapability($capability, $viewer);597}598return false;599}600601public function describeAutomaticCapability($capability) {602return pht('Leases inherit policies from the resources they lease.');603}604605606/* -( PhabricatorConduitResultInterface )---------------------------------- */607608609public function getFieldSpecificationsForConduit() {610return array(611id(new PhabricatorConduitSearchFieldSpecification())612->setKey('resourcePHID')613->setType('phid?')614->setDescription(pht('PHID of the leased resource, if any.')),615id(new PhabricatorConduitSearchFieldSpecification())616->setKey('resourceType')617->setType('string')618->setDescription(pht('Type of resource being leased.')),619id(new PhabricatorConduitSearchFieldSpecification())620->setKey('until')621->setType('int?')622->setDescription(pht('Epoch at which this lease expires, if any.')),623id(new PhabricatorConduitSearchFieldSpecification())624->setKey('ownerPHID')625->setType('phid?')626->setDescription(pht('The PHID of the object that owns this lease.')),627id(new PhabricatorConduitSearchFieldSpecification())628->setKey('authorizingPHID')629->setType('phid')630->setDescription(pht(631'The PHID of the object that authorized this lease.')),632id(new PhabricatorConduitSearchFieldSpecification())633->setKey('status')634->setType('map<string, wild>')635->setDescription(pht(636"The string constant and name of this lease's status.")),637);638}639640public function getFieldValuesForConduit() {641$status = $this->getStatus();642643$until = $this->getUntil();644if ($until) {645$until = (int)$until;646} else {647$until = null;648}649650return array(651'resourcePHID' => $this->getResourcePHID(),652'resourceType' => $this->getResourceType(),653'until' => $until,654'ownerPHID' => $this->getOwnerPHID(),655'authorizingPHID' => $this->getAuthorizingPHID(),656'status' => array(657'value' => $status,658'name' => DrydockLeaseStatus::getNameForStatus($status),659),660);661}662663public function getConduitSearchAttachments() {664return array();665}666667}668669670