diff --git a/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php index 20af18ec21..0628f57b38 100644 --- a/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php @@ -1,70 +1,118 @@ setName('release-lease') ->setSynopsis(pht('Release a lease.')) ->setArguments( array( array( 'name' => 'id', 'param' => 'id', 'repeat' => true, 'help' => pht('Lease ID to release.'), ), + array( + 'name' => 'all', + 'help' => pht('Release all leases. Dangerous!'), + ), )); } public function execute(PhutilArgumentParser $args) { + $is_all = $args->getArg('all'); $ids = $args->getArg('id'); - if (!$ids) { + + if (!$ids && !$is_all) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more lease IDs to release with "%s".', - '--id')); + 'Select which leases you want to release. See "--help" for '. + 'guidance.')); } $viewer = $this->getViewer(); - $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); - $leases = id(new DrydockLeaseQuery()) + $statuses = $this->getReleaseableLeaseStatuses(); + + $query = id(new DrydockLeaseQuery()) ->setViewer($viewer) - ->withIDs($ids) - ->execute(); + ->withStatuses(mpull($statuses, 'getKey')); - PhabricatorWorker::setRunAllTasksInProcess(true); - foreach ($ids as $id) { - $lease = idx($leases, $id); - if (!$lease) { - echo tsprintf( - "%s\n", - pht('Lease "%s" does not exist.', $id)); - continue; + if ($ids) { + $query->withIDs($ids); + } + + $leases = $query->execute(); + + if ($ids) { + $id_map = mpull($leases, null, 'getID'); + + foreach ($ids as $id) { + $lease = idx($id_map, $id); + if (!$lease) { + throw new PhutilArgumentUsageException( + pht('Lease "%s" does not exist.', $id)); + } } + $leases = array_select_keys($id_map, $ids); + } + + if (!$leases) { + echo tsprintf( + "%s\n", + pht('No leases selected for release.')); + + return 0; + } + + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + + foreach ($leases as $lease) { if (!$lease->canRelease()) { echo tsprintf( "%s\n", - pht('Lease "%s" is not releasable.', $id)); + pht( + 'Lease "%s" is not releasable.', + $lease->getDisplayName())); continue; } $command = DrydockCommand::initializeNewCommand($viewer) ->setTargetPHID($lease->getPHID()) ->setAuthorPHID($drydock_phid) ->setCommand(DrydockCommand::COMMAND_RELEASE) ->save(); $lease->scheduleUpdate(); echo tsprintf( "%s\n", - pht('Scheduled release of lease "%s".', $id)); + pht( + 'Scheduled release of lease "%s".', + $lease->getDisplayName())); + } + + } + + private function getReleaseableLeaseStatuses() { + $statuses = DrydockLeaseStatus::getAllStatuses(); + foreach ($statuses as $key => $status) { + $statuses[$key] = DrydockLeaseStatus::newStatusObject($status); + } + + foreach ($statuses as $key => $status) { + if (!$status->canRelease()) { + unset($statuses[$key]); + } } + return $statuses; } } diff --git a/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php index 01060a5325..afd826cbc0 100644 --- a/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php @@ -1,71 +1,117 @@ setName('release-resource') ->setSynopsis(pht('Release a resource.')) ->setArguments( array( array( 'name' => 'id', 'param' => 'id', 'repeat' => true, 'help' => pht('Resource ID to release.'), ), + array( + 'name' => 'all', + 'help' => pht('Release all resources. Dangerous!'), + ), )); } public function execute(PhutilArgumentParser $args) { + $is_all = $args->getArg('all'); $ids = $args->getArg('id'); - if (!$ids) { + if (!$ids && !$is_all) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more resource IDs to release with "%s".', - '--id')); + 'Specify which resources you want to release. See "--help" for '. + 'guidance.')); } $viewer = $this->getViewer(); - $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + $statuses = $this->getReleaseableResourceStatuses(); - $resources = id(new DrydockResourceQuery()) + $query = id(new DrydockResourceQuery()) ->setViewer($viewer) - ->withIDs($ids) - ->execute(); + ->withStatuses(mpull($statuses, 'getKey')); - PhabricatorWorker::setRunAllTasksInProcess(true); - foreach ($ids as $id) { - $resource = idx($resources, $id); + if ($ids) { + $query->withIDs($ids); + } - if (!$resource) { - echo tsprintf( - "%s\n", - pht('Resource "%s" does not exist.', $id)); - continue; + $resources = $query->execute(); + + if ($ids) { + $id_map = mpull($resources, null, 'getID'); + + foreach ($ids as $id) { + $resource = idx($resources, $id); + + if (!$resource) { + throw new PhutilArgumentUsageException( + pht('Resource "%s" does not exist.', $id)); + } } + $resources = array_select_keys($id_map, $ids); + } + + if (!$resources) { + echo tsprintf( + "%s\n", + pht('No resources selected for release.')); + + return 0; + } + + $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); + + PhabricatorWorker::setRunAllTasksInProcess(true); + + foreach ($resources as $resource) { if (!$resource->canRelease()) { echo tsprintf( "%s\n", - pht('Resource "%s" is not releasable.', $id)); + pht( + 'Resource "%s" is not releasable.', + $resource->getDisplayName())); continue; } $command = DrydockCommand::initializeNewCommand($viewer) ->setTargetPHID($resource->getPHID()) ->setAuthorPHID($drydock_phid) ->setCommand(DrydockCommand::COMMAND_RELEASE) ->save(); $resource->scheduleUpdate(); echo tsprintf( "%s\n", - pht('Scheduled release of resource "%s".', $id)); + pht( + 'Scheduled release of resource "%s".', + $resource->getDisplayName())); } + return 0; } + private function getReleaseableResourceStatuses() { + $statuses = DrydockResourceStatus::getAllStatuses(); + foreach ($statuses as $key => $status) { + $statuses[$key] = DrydockResourceStatus::newStatusObject($status); + } + + foreach ($statuses as $key => $status) { + if (!$status->canRelease()) { + unset($statuses[$key]); + } + } + + return $statuses; + } } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 5243aa1498..ba16aaf493 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -1,608 +1,612 @@ 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'), ), 'key_owner' => array( 'columns' => array('ownerPHID'), ), 'key_recent' => array( 'columns' => array('resourcePHID', 'dateModified'), ), ), ) + 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}/"; } + public function getDisplayName() { + return pht('Drydock Lease %d', $this->getID()); + } + /* -( 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.'); } /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { return array( id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('resourcePHID') ->setType('phid?') ->setDescription(pht('PHID of the leased resource, if any.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('resourceType') ->setType('string') ->setDescription(pht('Type of resource being leased.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('until') ->setType('int?') ->setDescription(pht('Epoch at which this lease expires, if any.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('ownerPHID') ->setType('phid?') ->setDescription(pht('The PHID of the object that owns this lease.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('authorizingPHID') ->setType('phid') ->setDescription(pht( 'The PHID of the object that authorized this lease.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('status') ->setType('map') ->setDescription(pht( "The string constant and name of this lease's status.")), ); } public function getFieldValuesForConduit() { $status = $this->getStatus(); $until = $this->getUntil(); if ($until) { $until = (int)$until; } else { $until = null; } return array( 'resourcePHID' => $this->getResourcePHID(), 'resourceType' => $this->getResourceType(), 'until' => $until, 'ownerPHID' => $this->getOwnerPHID(), 'authorizingPHID' => $this->getAuthorizingPHID(), 'status' => array( 'value' => $status, 'name' => DrydockLeaseStatus::getNameForStatus($status), ), ); } public function getConduitSearchAttachments() { return array(); } } diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index bc672dba3c..38e6660f7e 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -1,379 +1,383 @@ true, self::CONFIG_SERIALIZATION => array( 'attributes' => self::SERIALIZATION_JSON, 'capabilities' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'ownerPHID' => 'phid?', 'status' => 'text32', 'type' => 'text64', 'until' => 'epoch?', ), self::CONFIG_KEY_SCHEMA => array( 'key_type' => array( 'columns' => array('type', 'status'), ), 'key_blueprint' => array( 'columns' => array('blueprintPHID', 'status'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(DrydockResourcePHIDType::TYPECONST); } public function getResourceName() { return $this->getBlueprint()->getResourceName($this); } public function getAttribute($key, $default = null) { return idx($this->attributes, $key, $default); } public function getAttributesForTypeSpec(array $attribute_names) { return array_select_keys($this->attributes, $attribute_names); } public function setAttribute($key, $value) { $this->attributes[$key] = $value; return $this; } public function getCapability($key, $default = null) { return idx($this->capbilities, $key, $default); } public function getInterface(DrydockLease $lease, $type) { return $this->getBlueprint()->getInterface($this, $lease, $type); } public function getBlueprint() { return $this->assertAttached($this->blueprint); } public function attachBlueprint(DrydockBlueprint $blueprint) { $this->blueprint = $blueprint; return $this; } 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 setActivateWhenAllocated($activate) { $this->activateWhenAllocated = $activate; return $this; } public function needSlotLock($key) { $this->slotLocks[] = $key; return $this; } public function allocateResource() { // We expect resources to have a pregenerated PHID, as they should have // been created by a call to DrydockBlueprint->newResourceTemplate(). if (!$this->getPHID()) { throw new Exception( pht( 'Trying to allocate a resource with no generated PHID. Use "%s" to '. 'create new resource templates.', 'newResourceTemplate()')); } $expect_status = DrydockResourceStatus::STATUS_PENDING; $actual_status = $this->getStatus(); if ($actual_status != $expect_status) { throw new Exception( pht( 'Trying to allocate a resource from the wrong status. Status must '. 'be "%s", actually "%s".', $expect_status, $actual_status)); } if ($this->activateWhenAllocated) { $new_status = DrydockResourceStatus::STATUS_ACTIVE; } else { $new_status = DrydockResourceStatus::STATUS_PENDING; } $this->openTransaction(); try { DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); $this->slotLocks = array(); } catch (DrydockSlotLockException $ex) { $this->killTransaction(); if ($this->getID()) { $log_target = $this; } else { // If we don't have an ID, we have to log this on the blueprint, as the // resource is not going to be saved so the PHID will vanish. $log_target = $this->getBlueprint(); } $log_target->logEvent( DrydockSlotLockFailureLogType::LOGCONST, array( 'locks' => $ex->getLockMap(), )); throw $ex; } $this ->setStatus($new_status) ->save(); $this->saveTransaction(); $this->isAllocated = true; if ($new_status == DrydockResourceStatus::STATUS_ACTIVE) { $this->didActivate(); } return $this; } public function isAllocatedResource() { return $this->isAllocated; } public function activateResource() { if (!$this->getID()) { throw new Exception( pht( 'Trying to activate a resource which has not yet been persisted.')); } $expect_status = DrydockResourceStatus::STATUS_PENDING; $actual_status = $this->getStatus(); if ($actual_status != $expect_status) { throw new Exception( pht( 'Trying to activate a resource from the wrong status. Status must '. 'be "%s", actually "%s".', $expect_status, $actual_status)); } $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(DrydockResourceStatus::STATUS_ACTIVE) ->save(); $this->saveTransaction(); $this->isActivated = true; $this->didActivate(); return $this; } public function isActivatedResource() { return $this->isActivated; } public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockResourceUpdateWorker', array( 'resourcePHID' => $this->getPHID(), 'isExpireTask' => ($epoch !== null), ), array( 'objectPHID' => $this->getPHID(), 'delayUntil' => ($epoch ? (int)$epoch : null), )); } 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); } } public function logEvent($type, array $data = array()) { $log = id(new DrydockLog()) ->setEpoch(PhabricatorTime::getNow()) ->setType($type) ->setData($data); $log->setResourcePHID($this->getPHID()); $log->setBlueprintPHID($this->getBlueprintPHID()); return $log->save(); } + public function getDisplayName() { + return pht('Drydock Resource %d', $this->getID()); + } + /* -( Status )------------------------------------------------------------- */ public function getStatusObject() { return DrydockResourceStatus::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 canRelease() { return $this->getStatusObject()->canRelease(); } public function canReceiveCommands() { return $this->getStatusObject()->canReceiveCommands(); } public function isActive() { return $this->getStatusObject()->isActive(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBlueprint()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBlueprint()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('Resources inherit the policies of their blueprints.'); } /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { return array( id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('blueprintPHID') ->setType('phid') ->setDescription(pht('The blueprint which generated this resource.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('status') ->setType('map') ->setDescription(pht('Information about resource status.')), ); } public function getFieldValuesForConduit() { $status = $this->getStatus(); return array( 'blueprintPHID' => $this->getBlueprintPHID(), 'status' => array( 'value' => $status, 'name' => DrydockResourceStatus::getNameForStatus($status), ), ); } public function getConduitSearchAttachments() { return array(); } }