Path: blob/master/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
12256 views
<?php12/**3* @task update Updating Leases4* @task command Processing Commands5* @task allocator Drydock Allocator6* @task acquire Acquiring Leases7* @task activate Activating Leases8* @task release Releasing Leases9* @task break Breaking Leases10* @task destroy Destroying Leases11*/12final class DrydockLeaseUpdateWorker extends DrydockWorker {1314protected function doWork() {15$lease_phid = $this->getTaskDataValue('leasePHID');1617$hash = PhabricatorHash::digestForIndex($lease_phid);18$lock_key = 'drydock.lease:'.$hash;1920$lock = PhabricatorGlobalLock::newLock($lock_key)21->lock(1);2223try {24$lease = $this->loadLease($lease_phid);25$this->handleUpdate($lease);26} catch (Exception $ex) {27$lock->unlock();28$this->flushDrydockTaskQueue();29throw $ex;30}3132$lock->unlock();33}343536/* -( Updating Leases )---------------------------------------------------- */373839/**40* @task update41*/42private function handleUpdate(DrydockLease $lease) {43try {44$this->updateLease($lease);45} catch (DrydockAcquiredBrokenResourceException $ex) {46// If this lease acquired a resource but failed to activate, we don't47// need to break the lease. We can throw it back in the pool and let48// it take another shot at acquiring a new resource.4950// Before we throw it back, release any locks the lease is holding.51DrydockSlotLock::releaseLocks($lease->getPHID());5253$lease54->setStatus(DrydockLeaseStatus::STATUS_PENDING)55->setResourcePHID(null)56->save();5758$lease->logEvent(59DrydockLeaseReacquireLogType::LOGCONST,60array(61'class' => get_class($ex),62'message' => $ex->getMessage(),63));6465$this->yieldLease($lease, $ex);66} catch (Exception $ex) {67if ($this->isTemporaryException($ex)) {68$this->yieldLease($lease, $ex);69} else {70$this->breakLease($lease, $ex);71}72}73}747576/**77* @task update78*/79private function updateLease(DrydockLease $lease) {80$this->processLeaseCommands($lease);8182$lease_status = $lease->getStatus();83switch ($lease_status) {84case DrydockLeaseStatus::STATUS_PENDING:85$this->executeAllocator($lease);86break;87case DrydockLeaseStatus::STATUS_ACQUIRED:88$this->activateLease($lease);89break;90case DrydockLeaseStatus::STATUS_ACTIVE:91// Nothing to do.92break;93case DrydockLeaseStatus::STATUS_RELEASED:94case DrydockLeaseStatus::STATUS_BROKEN:95$this->destroyLease($lease);96break;97case DrydockLeaseStatus::STATUS_DESTROYED:98break;99}100101$this->yieldIfExpiringLease($lease);102}103104105/**106* @task update107*/108private function yieldLease(DrydockLease $lease, Exception $ex) {109$duration = $this->getYieldDurationFromException($ex);110111$lease->logEvent(112DrydockLeaseActivationYieldLogType::LOGCONST,113array(114'duration' => $duration,115));116117throw new PhabricatorWorkerYieldException($duration);118}119120121/* -( Processing Commands )------------------------------------------------ */122123124/**125* @task command126*/127private function processLeaseCommands(DrydockLease $lease) {128if (!$lease->canReceiveCommands()) {129return;130}131132$this->checkLeaseExpiration($lease);133134$commands = $this->loadCommands($lease->getPHID());135foreach ($commands as $command) {136if (!$lease->canReceiveCommands()) {137break;138}139140$this->processLeaseCommand($lease, $command);141142$command143->setIsConsumed(true)144->save();145}146}147148149/**150* @task command151*/152private function processLeaseCommand(153DrydockLease $lease,154DrydockCommand $command) {155switch ($command->getCommand()) {156case DrydockCommand::COMMAND_RELEASE:157$this->releaseLease($lease);158break;159}160}161162163/* -( Drydock Allocator )-------------------------------------------------- */164165166/**167* Find or build a resource which can satisfy a given lease request, then168* acquire the lease.169*170* @param DrydockLease Requested lease.171* @return void172* @task allocator173*/174private function executeAllocator(DrydockLease $lease) {175$blueprints = $this->loadBlueprintsForAllocatingLease($lease);176177// If we get nothing back, that means no blueprint is defined which can178// ever build the requested resource. This is a permanent failure, since179// we don't expect to succeed no matter how many times we try.180if (!$blueprints) {181throw new PhabricatorWorkerPermanentFailureException(182pht(183'No active Drydock blueprint exists which can ever allocate a '.184'resource for lease "%s".',185$lease->getPHID()));186}187188// First, try to find a suitable open resource which we can acquire a new189// lease on.190$resources = $this->loadAcquirableResourcesForLease($blueprints, $lease);191192list($free_resources, $used_resources) = $this->partitionResources(193$lease,194$resources);195196$resource = $this->leaseAnyResource($lease, $free_resources);197if ($resource) {198return $resource;199}200201// We're about to try creating a resource. If we're already creating202// something, just yield until that resolves.203204$this->yieldForPendingResources($lease);205206// We haven't been able to lease an existing resource yet, so now we try to207// create one. We may still have some less-desirable "used" resources that208// we'll sometimes try to lease later if we fail to allocate a new resource.209210$resource = $this->newLeasedResource($lease, $blueprints);211if ($resource) {212return $resource;213}214215// We haven't been able to lease a desirable "free" resource or create a216// new resource. Try to lease a "used" resource.217218$resource = $this->leaseAnyResource($lease, $used_resources);219if ($resource) {220return $resource;221}222223// If this lease has already triggered a reclaim, just yield and wait for224// it to resolve.225$this->yieldForReclaimingResources($lease);226227// Try to reclaim a resource. This will yield if it reclaims something.228$this->reclaimAnyResource($lease, $blueprints);229230// We weren't able to lease, create, or reclaim any resources. We just have231// to wait for resources to become available.232233$lease->logEvent(234DrydockLeaseWaitingForResourcesLogType::LOGCONST,235array(236'blueprintPHIDs' => mpull($blueprints, 'getPHID'),237));238239throw new PhabricatorWorkerYieldException(15);240}241242private function reclaimAnyResource(DrydockLease $lease, array $blueprints) {243assert_instances_of($blueprints, 'DrydockBlueprint');244245$blueprints = $this->rankBlueprints($blueprints, $lease);246247// Try to actively reclaim unused resources. If we succeed, jump back248// into the queue in an effort to claim it.249250foreach ($blueprints as $blueprint) {251$reclaimed = $this->reclaimResources($blueprint, $lease);252if ($reclaimed) {253254$lease->logEvent(255DrydockLeaseReclaimLogType::LOGCONST,256array(257'resourcePHIDs' => array($reclaimed->getPHID()),258));259260// Yield explicitly here: we'll be awakened when the resource is261// reclaimed.262263throw new PhabricatorWorkerYieldException(15);264}265}266}267268private function yieldForPendingResources(DrydockLease $lease) {269// See T13677. If this lease has already triggered the allocation of270// one or more resources and they are still pending, just yield and271// wait for them.272273$viewer = $this->getViewer();274275$phids = $lease->getAllocatedResourcePHIDs();276if (!$phids) {277return null;278}279280$resources = id(new DrydockResourceQuery())281->setViewer($viewer)282->withPHIDs($phids)283->withStatuses(284array(285DrydockResourceStatus::STATUS_PENDING,286))287->setLimit(1)288->execute();289if (!$resources) {290return;291}292293$lease->logEvent(294DrydockLeaseWaitingForActivationLogType::LOGCONST,295array(296'resourcePHIDs' => mpull($resources, 'getPHID'),297));298299throw new PhabricatorWorkerYieldException(15);300}301302private function yieldForReclaimingResources(DrydockLease $lease) {303$viewer = $this->getViewer();304305$phids = $lease->getReclaimedResourcePHIDs();306if (!$phids) {307return;308}309310$resources = id(new DrydockResourceQuery())311->setViewer($viewer)312->withPHIDs($phids)313->withStatuses(314array(315DrydockResourceStatus::STATUS_ACTIVE,316DrydockResourceStatus::STATUS_RELEASED,317))318->setLimit(1)319->execute();320if (!$resources) {321return;322}323324$lease->logEvent(325DrydockLeaseWaitingForReclamationLogType::LOGCONST,326array(327'resourcePHIDs' => mpull($resources, 'getPHID'),328));329330throw new PhabricatorWorkerYieldException(15);331}332333private function newLeasedResource(334DrydockLease $lease,335array $blueprints) {336assert_instances_of($blueprints, 'DrydockBlueprint');337338$usable_blueprints = $this->removeOverallocatedBlueprints(339$blueprints,340$lease);341342// If we get nothing back here, some blueprint claims it can eventually343// satisfy the lease, just not right now. This is a temporary failure,344// and we expect allocation to succeed eventually.345346// Return, try to lease a "used" resource, and continue from there.347348if (!$usable_blueprints) {349return null;350}351352$usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease);353354$new_resources = $this->newResources($lease, $usable_blueprints);355if (!$new_resources) {356// If we were unable to create any new resources, return and357// try to lease a "used" resource.358return null;359}360361$new_resources = $this->removeUnacquirableResources(362$new_resources,363$lease);364if (!$new_resources) {365// If we make it here, we just built a resource but aren't allowed366// to acquire it. We expect this to happen if the resource prevents367// acquisition until it activates, which is common when a resource368// needs to perform setup steps.369370// Explicitly yield and wait for activation, since we don't want to371// lease a "used" resource.372373throw new PhabricatorWorkerYieldException(15);374}375376$resource = $this->leaseAnyResource($lease, $new_resources);377if ($resource) {378return $resource;379}380381// We may not be able to lease a resource even if we just built it:382// another process may snatch it up before we can lease it. This should383// be rare, but is not concerning. Just try to build another resource.384385// We likely could try to build the next resource immediately, but err on386// the side of caution and yield for now, at least until this code is387// better vetted.388389throw new PhabricatorWorkerYieldException(15);390}391392private function partitionResources(393DrydockLease $lease,394array $resources) {395396assert_instances_of($resources, 'DrydockResource');397$viewer = $this->getViewer();398399$lease_statuses = array(400DrydockLeaseStatus::STATUS_PENDING,401DrydockLeaseStatus::STATUS_ACQUIRED,402DrydockLeaseStatus::STATUS_ACTIVE,403);404405// Partition resources into "free" resources (which we can try to lease406// immediately) and "used" resources, which we can only to lease after we407// fail to allocate a new resource.408409// "Free" resources are unleased and/or prefer reuse over allocation.410// "Used" resources are leased and prefer allocation over reuse.411412$free_resources = array();413$used_resources = array();414415foreach ($resources as $resource) {416$blueprint = $resource->getBlueprint();417418if (!$blueprint->shouldAllocateSupplementalResource($resource, $lease)) {419$free_resources[] = $resource;420continue;421}422423$leases = id(new DrydockLeaseQuery())424->setViewer($viewer)425->withResourcePHIDs(array($resource->getPHID()))426->withStatuses($lease_statuses)427->setLimit(1)428->execute();429if (!$leases) {430$free_resources[] = $resource;431continue;432}433434$used_resources[] = $resource;435}436437return array($free_resources, $used_resources);438}439440private function newResources(441DrydockLease $lease,442array $blueprints) {443assert_instances_of($blueprints, 'DrydockBlueprint');444445$resources = array();446$exceptions = array();447foreach ($blueprints as $blueprint) {448$caught = null;449try {450$resources[] = $this->allocateResource($blueprint, $lease);451452// Bail after allocating one resource, we don't need any more than453// this.454break;455} catch (Exception $ex) {456$caught = $ex;457} catch (Throwable $ex) {458$caught = $ex;459}460461if ($caught) {462// This failure is not normally expected, so log it. It can be463// caused by something mundane and recoverable, however (see below464// for discussion).465466// We log to the blueprint separately from the log to the lease:467// the lease is not attached to a blueprint yet so the lease log468// will not show up on the blueprint; more than one blueprint may469// fail; and the lease is not really impacted (and won't log) if at470// least one blueprint actually works.471472$blueprint->logEvent(473DrydockResourceAllocationFailureLogType::LOGCONST,474array(475'class' => get_class($caught),476'message' => $caught->getMessage(),477));478479$exceptions[] = $caught;480}481}482483if (!$resources) {484// If one or more blueprints claimed that they would be able to allocate485// resources but none are actually able to allocate resources, log the486// failure and yield so we try again soon.487488// This can happen if some unexpected issue occurs during allocation489// (for example, a call to build a VM fails for some reason) or if we490// raced another allocator and the blueprint is now full.491492$ex = new PhutilAggregateException(493pht(494'All blueprints failed to allocate a suitable new resource when '.495'trying to allocate lease ("%s").',496$lease->getPHID()),497$exceptions);498499$lease->logEvent(500DrydockLeaseAllocationFailureLogType::LOGCONST,501array(502'class' => get_class($ex),503'message' => $ex->getMessage(),504));505506return null;507}508509return $resources;510}511512513private function leaseAnyResource(514DrydockLease $lease,515array $resources) {516assert_instances_of($resources, 'DrydockResource');517518if (!$resources) {519return null;520}521522$resources = $this->rankResources($resources, $lease);523524$exceptions = array();525$yields = array();526527$allocated = null;528foreach ($resources as $resource) {529try {530$this->acquireLease($resource, $lease);531$allocated = $resource;532break;533} catch (DrydockResourceLockException $ex) {534// We need to lock the resource to actually acquire it. If we aren't535// able to acquire the lock quickly enough, we can yield and try again536// later.537$yields[] = $ex;538} catch (DrydockSlotLockException $ex) {539// This also just indicates we ran into some kind of contention,540// probably from another lease. Just yield.541$yields[] = $ex;542} catch (DrydockAcquiredBrokenResourceException $ex) {543// If a resource was reclaimed or destroyed by the time we actually544// got around to acquiring it, we just got unlucky.545$yields[] = $ex;546} catch (PhabricatorWorkerYieldException $ex) {547// We can be told to yield, particularly by the supplemental allocator548// trying to give us a supplemental resource.549$yields[] = $ex;550} catch (Exception $ex) {551$exceptions[] = $ex;552}553}554555if ($allocated) {556return $allocated;557}558559if ($yields) {560throw new PhabricatorWorkerYieldException(15);561}562563throw new PhutilAggregateException(564pht(565'Unable to acquire lease "%s" on any resource.',566$lease->getPHID()),567$exceptions);568}569570571/**572* Get all the concrete @{class:DrydockBlueprint}s which can possibly573* build a resource to satisfy a lease.574*575* @param DrydockLease Requested lease.576* @return list<DrydockBlueprint> List of qualifying blueprints.577* @task allocator578*/579private function loadBlueprintsForAllocatingLease(580DrydockLease $lease) {581$viewer = $this->getViewer();582583$impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease);584if (!$impls) {585return array();586}587588$blueprint_phids = $lease->getAllowedBlueprintPHIDs();589if (!$blueprint_phids) {590$lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);591return array();592}593594$query = id(new DrydockBlueprintQuery())595->setViewer($viewer)596->withPHIDs($blueprint_phids)597->withBlueprintClasses(array_keys($impls))598->withDisabled(false);599600// The Drydock application itself is allowed to authorize anything. This601// is primarily used for leases generated by CLI administrative tools.602$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();603604$authorizing_phid = $lease->getAuthorizingPHID();605if ($authorizing_phid != $drydock_phid) {606$blueprints = id(clone $query)607->withAuthorizedPHIDs(array($authorizing_phid))608->execute();609if (!$blueprints) {610// If we didn't hit any blueprints, check if this is an authorization611// problem: re-execute the query without the authorization constraint.612// If the second query hits blueprints, the overall configuration is613// fine but this is an authorization problem. If the second query also614// comes up blank, this is some other kind of configuration issue so615// we fall through to the default pathway.616$all_blueprints = $query->execute();617if ($all_blueprints) {618$lease->logEvent(619DrydockLeaseNoAuthorizationsLogType::LOGCONST,620array(621'authorizingPHID' => $authorizing_phid,622));623return array();624}625}626} else {627$blueprints = $query->execute();628}629630$keep = array();631foreach ($blueprints as $key => $blueprint) {632if (!$blueprint->canEverAllocateResourceForLease($lease)) {633continue;634}635636$keep[$key] = $blueprint;637}638639return $keep;640}641642643/**644* Load a list of all resources which a given lease can possibly be645* allocated against.646*647* @param list<DrydockBlueprint> Blueprints which may produce suitable648* resources.649* @param DrydockLease Requested lease.650* @return list<DrydockResource> Resources which may be able to allocate651* the lease.652* @task allocator653*/654private function loadAcquirableResourcesForLease(655array $blueprints,656DrydockLease $lease) {657assert_instances_of($blueprints, 'DrydockBlueprint');658$viewer = $this->getViewer();659660$resources = id(new DrydockResourceQuery())661->setViewer($viewer)662->withBlueprintPHIDs(mpull($blueprints, 'getPHID'))663->withTypes(array($lease->getResourceType()))664->withStatuses(665array(666DrydockResourceStatus::STATUS_ACTIVE,667))668->execute();669670return $this->removeUnacquirableResources($resources, $lease);671}672673674/**675* Remove resources which can not be acquired by a given lease from a list.676*677* @param list<DrydockResource> Candidate resources.678* @param DrydockLease Acquiring lease.679* @return list<DrydockResource> Resources which the lease may be able to680* acquire.681* @task allocator682*/683private function removeUnacquirableResources(684array $resources,685DrydockLease $lease) {686$keep = array();687foreach ($resources as $key => $resource) {688$blueprint = $resource->getBlueprint();689690if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {691continue;692}693694$keep[$key] = $resource;695}696697return $keep;698}699700701/**702* Remove blueprints which are too heavily allocated to build a resource for703* a lease from a list of blueprints.704*705* @param list<DrydockBlueprint> List of blueprints.706* @return list<DrydockBlueprint> List with blueprints that can not allocate707* a resource for the lease right now removed.708* @task allocator709*/710private function removeOverallocatedBlueprints(711array $blueprints,712DrydockLease $lease) {713assert_instances_of($blueprints, 'DrydockBlueprint');714715$keep = array();716717foreach ($blueprints as $key => $blueprint) {718if (!$blueprint->canAllocateResourceForLease($lease)) {719continue;720}721722$keep[$key] = $blueprint;723}724725return $keep;726}727728729/**730* Rank blueprints by suitability for building a new resource for a731* particular lease.732*733* @param list<DrydockBlueprint> List of blueprints.734* @param DrydockLease Requested lease.735* @return list<DrydockBlueprint> Ranked list of blueprints.736* @task allocator737*/738private function rankBlueprints(array $blueprints, DrydockLease $lease) {739assert_instances_of($blueprints, 'DrydockBlueprint');740741// TODO: Implement improvements to this ranking algorithm if they become742// available.743shuffle($blueprints);744745return $blueprints;746}747748749/**750* Rank resources by suitability for allocating a particular lease.751*752* @param list<DrydockResource> List of resources.753* @param DrydockLease Requested lease.754* @return list<DrydockResource> Ranked list of resources.755* @task allocator756*/757private function rankResources(array $resources, DrydockLease $lease) {758assert_instances_of($resources, 'DrydockResource');759760// TODO: Implement improvements to this ranking algorithm if they become761// available.762shuffle($resources);763764return $resources;765}766767768/**769* Perform an actual resource allocation with a particular blueprint.770*771* @param DrydockBlueprint The blueprint to allocate a resource from.772* @param DrydockLease Requested lease.773* @return DrydockResource Allocated resource.774* @task allocator775*/776private function allocateResource(777DrydockBlueprint $blueprint,778DrydockLease $lease) {779$resource = $blueprint->allocateResource($lease);780$this->validateAllocatedResource($blueprint, $resource, $lease);781782// If this resource was allocated as a pending resource, queue a task to783// activate it.784if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {785786$lease->addAllocatedResourcePHIDs(787array(788$resource->getPHID(),789));790$lease->save();791792PhabricatorWorker::scheduleTask(793'DrydockResourceUpdateWorker',794array(795'resourcePHID' => $resource->getPHID(),796797// This task will generally yield while the resource activates, so798// wake it back up once the resource comes online. Most of the time,799// we'll be able to lease the newly activated resource.800'awakenOnActivation' => array(801$this->getCurrentWorkerTaskID(),802),803),804array(805'objectPHID' => $resource->getPHID(),806));807}808809return $resource;810}811812813/**814* Check that the resource a blueprint allocated is roughly the sort of815* object we expect.816*817* @param DrydockBlueprint Blueprint which built the resource.818* @param wild Thing which the blueprint claims is a valid resource.819* @param DrydockLease Lease the resource was allocated for.820* @return void821* @task allocator822*/823private function validateAllocatedResource(824DrydockBlueprint $blueprint,825$resource,826DrydockLease $lease) {827828if (!($resource instanceof DrydockResource)) {829throw new Exception(830pht(831'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.832'return an object of type %s or throw, but returned something else.',833$blueprint->getBlueprintName(),834$blueprint->getClassName(),835'allocateResource()',836'DrydockResource'));837}838839if (!$resource->isAllocatedResource()) {840throw new Exception(841pht(842'Blueprint "%s" (of type "%s") is not properly implemented: %s '.843'must actually allocate the resource it returns.',844$blueprint->getBlueprintName(),845$blueprint->getClassName(),846'allocateResource()'));847}848849$resource_type = $resource->getType();850$lease_type = $lease->getResourceType();851852if ($resource_type !== $lease_type) {853throw new Exception(854pht(855'Blueprint "%s" (of type "%s") is not properly implemented: it '.856'built a resource of type "%s" to satisfy a lease requesting a '.857'resource of type "%s".',858$blueprint->getBlueprintName(),859$blueprint->getClassName(),860$resource_type,861$lease_type));862}863}864865private function reclaimResources(866DrydockBlueprint $blueprint,867DrydockLease $lease) {868$viewer = $this->getViewer();869870$resources = id(new DrydockResourceQuery())871->setViewer($viewer)872->withBlueprintPHIDs(array($blueprint->getPHID()))873->withStatuses(874array(875DrydockResourceStatus::STATUS_ACTIVE,876))877->execute();878879// TODO: We could be much smarter about this and try to release long-unused880// resources, resources with many similar copies, old resources, resources881// that are cheap to rebuild, etc.882shuffle($resources);883884foreach ($resources as $resource) {885if ($this->canReclaimResource($resource)) {886$this->reclaimResource($resource, $lease);887return $resource;888}889}890891return null;892}893894895/* -( Acquiring Leases )--------------------------------------------------- */896897898/**899* Perform an actual lease acquisition on a particular resource.900*901* @param DrydockResource Resource to acquire a lease on.902* @param DrydockLease Lease to acquire.903* @return void904* @task acquire905*/906private function acquireLease(907DrydockResource $resource,908DrydockLease $lease) {909910$blueprint = $resource->getBlueprint();911$blueprint->acquireLease($resource, $lease);912913$this->validateAcquiredLease($blueprint, $resource, $lease);914915// If this lease has been acquired but not activated, queue a task to916// activate it.917if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {918$this->queueTask(919__CLASS__,920array(921'leasePHID' => $lease->getPHID(),922),923array(924'objectPHID' => $lease->getPHID(),925));926}927}928929930/**931* Make sure that a lease was really acquired properly.932*933* @param DrydockBlueprint Blueprint which created the resource.934* @param DrydockResource Resource which was acquired.935* @param DrydockLease The lease which was supposedly acquired.936* @return void937* @task acquire938*/939private function validateAcquiredLease(940DrydockBlueprint $blueprint,941DrydockResource $resource,942DrydockLease $lease) {943944if (!$lease->isAcquiredLease()) {945throw new Exception(946pht(947'Blueprint "%s" (of type "%s") is not properly implemented: it '.948'returned from "%s" without acquiring a lease.',949$blueprint->getBlueprintName(),950$blueprint->getClassName(),951'acquireLease()'));952}953954$lease_phid = $lease->getResourcePHID();955$resource_phid = $resource->getPHID();956957if ($lease_phid !== $resource_phid) {958throw new Exception(959pht(960'Blueprint "%s" (of type "%s") is not properly implemented: it '.961'returned from "%s" with a lease acquired on the wrong resource.',962$blueprint->getBlueprintName(),963$blueprint->getClassName(),964'acquireLease()'));965}966}967968969/* -( Activating Leases )-------------------------------------------------- */970971972/**973* @task activate974*/975private function activateLease(DrydockLease $lease) {976$resource = $lease->getResource();977if (!$resource) {978throw new Exception(979pht('Trying to activate lease with no resource.'));980}981982$resource_status = $resource->getStatus();983984if ($resource_status == DrydockResourceStatus::STATUS_PENDING) {985throw new PhabricatorWorkerYieldException(15);986}987988if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) {989throw new DrydockAcquiredBrokenResourceException(990pht(991'Trying to activate lease ("%s") on a resource ("%s") in '.992'the wrong status ("%s").',993$lease->getPHID(),994$resource->getPHID(),995$resource_status));996}997998// NOTE: We can race resource destruction here. Between the time we999// performed the read above and now, the resource might have closed, so1000// we may activate leases on dead resources. At least for now, this seems1001// fine: a resource dying right before we activate a lease on it should not1002// be distinguishable from a resource dying right after we activate a lease1003// on it. We end up with an active lease on a dead resource either way, and1004// can not prevent resources dying from lightning strikes.10051006$blueprint = $resource->getBlueprint();1007$blueprint->activateLease($resource, $lease);1008$this->validateActivatedLease($blueprint, $resource, $lease);1009}10101011/**1012* @task activate1013*/1014private function validateActivatedLease(1015DrydockBlueprint $blueprint,1016DrydockResource $resource,1017DrydockLease $lease) {10181019if (!$lease->isActivatedLease()) {1020throw new Exception(1021pht(1022'Blueprint "%s" (of type "%s") is not properly implemented: it '.1023'returned from "%s" without activating a lease.',1024$blueprint->getBlueprintName(),1025$blueprint->getClassName(),1026'acquireLease()'));1027}10281029}103010311032/* -( Releasing Leases )--------------------------------------------------- */103310341035/**1036* @task release1037*/1038private function releaseLease(DrydockLease $lease) {1039$lease1040->setStatus(DrydockLeaseStatus::STATUS_RELEASED)1041->save();10421043$lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST);10441045$resource = $lease->getResource();1046if ($resource) {1047$blueprint = $resource->getBlueprint();1048$blueprint->didReleaseLease($resource, $lease);1049}10501051$this->destroyLease($lease);1052}105310541055/* -( Breaking Leases )---------------------------------------------------- */105610571058/**1059* @task break1060*/1061protected function breakLease(DrydockLease $lease, Exception $ex) {1062switch ($lease->getStatus()) {1063case DrydockLeaseStatus::STATUS_BROKEN:1064case DrydockLeaseStatus::STATUS_RELEASED:1065case DrydockLeaseStatus::STATUS_DESTROYED:1066throw new PhutilProxyException(1067pht(1068'Unexpected failure while destroying lease ("%s").',1069$lease->getPHID()),1070$ex);1071}10721073$lease1074->setStatus(DrydockLeaseStatus::STATUS_BROKEN)1075->save();10761077$lease->logEvent(1078DrydockLeaseActivationFailureLogType::LOGCONST,1079array(1080'class' => get_class($ex),1081'message' => $ex->getMessage(),1082));10831084$lease->awakenTasks();10851086$this->queueTask(1087__CLASS__,1088array(1089'leasePHID' => $lease->getPHID(),1090),1091array(1092'objectPHID' => $lease->getPHID(),1093));10941095throw new PhabricatorWorkerPermanentFailureException(1096pht(1097'Permanent failure while activating lease ("%s"): %s',1098$lease->getPHID(),1099$ex->getMessage()));1100}110111021103/* -( Destroying Leases )-------------------------------------------------- */110411051106/**1107* @task destroy1108*/1109private function destroyLease(DrydockLease $lease) {1110$resource = $lease->getResource();11111112if ($resource) {1113$blueprint = $resource->getBlueprint();1114$blueprint->destroyLease($resource, $lease);1115}11161117DrydockSlotLock::releaseLocks($lease->getPHID());11181119$lease1120->setStatus(DrydockLeaseStatus::STATUS_DESTROYED)1121->save();11221123$lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);11241125$lease->awakenTasks();1126}11271128}112911301131