Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / drydock / worker / DrydockLeaseUpdateWorker.php
blobb83022f72063c494a8892f5fa370d89ec7b1a046
1 <?php
3 /**
4 * @task update Updating Leases
5 * @task command Processing Commands
6 * @task allocator Drydock Allocator
7 * @task acquire Acquiring Leases
8 * @task activate Activating Leases
9 * @task release Releasing Leases
10 * @task break Breaking Leases
11 * @task destroy Destroying Leases
13 final class DrydockLeaseUpdateWorker extends DrydockWorker {
15 protected function doWork() {
16 $lease_phid = $this->getTaskDataValue('leasePHID');
18 $hash = PhabricatorHash::digestForIndex($lease_phid);
19 $lock_key = 'drydock.lease:'.$hash;
21 $lock = PhabricatorGlobalLock::newLock($lock_key)
22 ->lock(1);
24 try {
25 $lease = $this->loadLease($lease_phid);
26 $this->handleUpdate($lease);
27 } catch (Exception $ex) {
28 $lock->unlock();
29 $this->flushDrydockTaskQueue();
30 throw $ex;
33 $lock->unlock();
37 /* -( Updating Leases )---------------------------------------------------- */
40 /**
41 * @task update
43 private function handleUpdate(DrydockLease $lease) {
44 try {
45 $this->updateLease($lease);
46 } catch (DrydockAcquiredBrokenResourceException $ex) {
47 // If this lease acquired a resource but failed to activate, we don't
48 // need to break the lease. We can throw it back in the pool and let
49 // it take another shot at acquiring a new resource.
51 // Before we throw it back, release any locks the lease is holding.
52 DrydockSlotLock::releaseLocks($lease->getPHID());
54 $lease
55 ->setStatus(DrydockLeaseStatus::STATUS_PENDING)
56 ->setResourcePHID(null)
57 ->save();
59 $lease->logEvent(
60 DrydockLeaseReacquireLogType::LOGCONST,
61 array(
62 'class' => get_class($ex),
63 'message' => $ex->getMessage(),
64 ));
66 $this->yieldLease($lease, $ex);
67 } catch (Exception $ex) {
68 if ($this->isTemporaryException($ex)) {
69 $this->yieldLease($lease, $ex);
70 } else {
71 $this->breakLease($lease, $ex);
77 /**
78 * @task update
80 private function updateLease(DrydockLease $lease) {
81 $this->processLeaseCommands($lease);
83 $lease_status = $lease->getStatus();
84 switch ($lease_status) {
85 case DrydockLeaseStatus::STATUS_PENDING:
86 $this->executeAllocator($lease);
87 break;
88 case DrydockLeaseStatus::STATUS_ACQUIRED:
89 $this->activateLease($lease);
90 break;
91 case DrydockLeaseStatus::STATUS_ACTIVE:
92 // Nothing to do.
93 break;
94 case DrydockLeaseStatus::STATUS_RELEASED:
95 case DrydockLeaseStatus::STATUS_BROKEN:
96 $this->destroyLease($lease);
97 break;
98 case DrydockLeaseStatus::STATUS_DESTROYED:
99 break;
102 $this->yieldIfExpiringLease($lease);
107 * @task update
109 private function yieldLease(DrydockLease $lease, Exception $ex) {
110 $duration = $this->getYieldDurationFromException($ex);
112 $lease->logEvent(
113 DrydockLeaseActivationYieldLogType::LOGCONST,
114 array(
115 'duration' => $duration,
118 throw new PhabricatorWorkerYieldException($duration);
122 /* -( Processing Commands )------------------------------------------------ */
126 * @task command
128 private function processLeaseCommands(DrydockLease $lease) {
129 if (!$lease->canReceiveCommands()) {
130 return;
133 $this->checkLeaseExpiration($lease);
135 $commands = $this->loadCommands($lease->getPHID());
136 foreach ($commands as $command) {
137 if (!$lease->canReceiveCommands()) {
138 break;
141 $this->processLeaseCommand($lease, $command);
143 $command
144 ->setIsConsumed(true)
145 ->save();
151 * @task command
153 private function processLeaseCommand(
154 DrydockLease $lease,
155 DrydockCommand $command) {
156 switch ($command->getCommand()) {
157 case DrydockCommand::COMMAND_RELEASE:
158 $this->releaseLease($lease);
159 break;
164 /* -( Drydock Allocator )-------------------------------------------------- */
168 * Find or build a resource which can satisfy a given lease request, then
169 * acquire the lease.
171 * @param DrydockLease Requested lease.
172 * @return void
173 * @task allocator
175 private function executeAllocator(DrydockLease $lease) {
176 $blueprints = $this->loadBlueprintsForAllocatingLease($lease);
178 // If we get nothing back, that means no blueprint is defined which can
179 // ever build the requested resource. This is a permanent failure, since
180 // we don't expect to succeed no matter how many times we try.
181 if (!$blueprints) {
182 throw new PhabricatorWorkerPermanentFailureException(
183 pht(
184 'No active Drydock blueprint exists which can ever allocate a '.
185 'resource for lease "%s".',
186 $lease->getPHID()));
189 // First, try to find a suitable open resource which we can acquire a new
190 // lease on.
191 $resources = $this->loadResourcesForAllocatingLease($blueprints, $lease);
193 // If no resources exist yet, see if we can build one.
194 if (!$resources) {
195 $usable_blueprints = $this->removeOverallocatedBlueprints(
196 $blueprints,
197 $lease);
199 // If we get nothing back here, some blueprint claims it can eventually
200 // satisfy the lease, just not right now. This is a temporary failure,
201 // and we expect allocation to succeed eventually.
202 if (!$usable_blueprints) {
203 $blueprints = $this->rankBlueprints($blueprints, $lease);
205 // Try to actively reclaim unused resources. If we succeed, jump back
206 // into the queue in an effort to claim it.
207 foreach ($blueprints as $blueprint) {
208 $reclaimed = $this->reclaimResources($blueprint, $lease);
209 if ($reclaimed) {
210 $lease->logEvent(
211 DrydockLeaseReclaimLogType::LOGCONST,
212 array(
213 'resourcePHIDs' => array($reclaimed->getPHID()),
216 throw new PhabricatorWorkerYieldException(15);
220 $lease->logEvent(
221 DrydockLeaseWaitingForResourcesLogType::LOGCONST,
222 array(
223 'blueprintPHIDs' => mpull($blueprints, 'getPHID'),
226 throw new PhabricatorWorkerYieldException(15);
229 $usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease);
231 $exceptions = array();
232 foreach ($usable_blueprints as $blueprint) {
233 try {
234 $resources[] = $this->allocateResource($blueprint, $lease);
236 // Bail after allocating one resource, we don't need any more than
237 // this.
238 break;
239 } catch (Exception $ex) {
240 // This failure is not normally expected, so log it. It can be
241 // caused by something mundane and recoverable, however (see below
242 // for discussion).
244 // We log to the blueprint separately from the log to the lease:
245 // the lease is not attached to a blueprint yet so the lease log
246 // will not show up on the blueprint; more than one blueprint may
247 // fail; and the lease is not really impacted (and won't log) if at
248 // least one blueprint actually works.
250 $blueprint->logEvent(
251 DrydockResourceAllocationFailureLogType::LOGCONST,
252 array(
253 'class' => get_class($ex),
254 'message' => $ex->getMessage(),
257 $exceptions[] = $ex;
261 if (!$resources) {
262 // If one or more blueprints claimed that they would be able to
263 // allocate resources but none are actually able to allocate resources,
264 // log the failure and yield so we try again soon.
266 // This can happen if some unexpected issue occurs during allocation
267 // (for example, a call to build a VM fails for some reason) or if we
268 // raced another allocator and the blueprint is now full.
270 $ex = new PhutilAggregateException(
271 pht(
272 'All blueprints failed to allocate a suitable new resource when '.
273 'trying to allocate lease ("%s").',
274 $lease->getPHID()),
275 $exceptions);
277 $lease->logEvent(
278 DrydockLeaseAllocationFailureLogType::LOGCONST,
279 array(
280 'class' => get_class($ex),
281 'message' => $ex->getMessage(),
284 throw new PhabricatorWorkerYieldException(15);
287 $resources = $this->removeUnacquirableResources($resources, $lease);
288 if (!$resources) {
289 // If we make it here, we just built a resource but aren't allowed
290 // to acquire it. We expect this during routine operation if the
291 // resource prevents acquisition until it activates. Yield and wait
292 // for activation.
293 throw new PhabricatorWorkerYieldException(15);
296 // NOTE: We have not acquired the lease yet, so it is possible that the
297 // resource we just built will be snatched up by some other lease before
298 // we can acquire it. This is not problematic: we'll retry a little later
299 // and should succeed eventually.
302 $resources = $this->rankResources($resources, $lease);
304 $exceptions = array();
305 $yields = array();
306 $allocated = false;
307 foreach ($resources as $resource) {
308 try {
309 $resource = $this->newResourceForAcquisition($resource, $lease);
310 $this->acquireLease($resource, $lease);
311 $allocated = true;
312 break;
313 } catch (DrydockResourceLockException $ex) {
314 // We need to lock the resource to actually acquire it. If we aren't
315 // able to acquire the lock quickly enough, we can yield and try again
316 // later.
317 $yields[] = $ex;
318 } catch (DrydockAcquiredBrokenResourceException $ex) {
319 // If a resource was reclaimed or destroyed by the time we actually
320 // got around to acquiring it, we just got unlucky. We can yield and
321 // try again later.
322 $yields[] = $ex;
323 } catch (PhabricatorWorkerYieldException $ex) {
324 // We can be told to yield, particularly by the supplemental allocator
325 // trying to give us a supplemental resource.
326 $yields[] = $ex;
327 } catch (Exception $ex) {
328 $exceptions[] = $ex;
332 if (!$allocated) {
333 if ($yields) {
334 throw new PhabricatorWorkerYieldException(15);
335 } else {
336 throw new PhutilAggregateException(
337 pht(
338 'Unable to acquire lease "%s" on any resource.',
339 $lease->getPHID()),
340 $exceptions);
347 * Get all the @{class:DrydockBlueprintImplementation}s which can possibly
348 * build a resource to satisfy a lease.
350 * This method returns blueprints which might, at some time, be able to
351 * build a resource which can satisfy the lease. They may not be able to
352 * build that resource right now.
354 * @param DrydockLease Requested lease.
355 * @return list<DrydockBlueprintImplementation> List of qualifying blueprint
356 * implementations.
357 * @task allocator
359 private function loadBlueprintImplementationsForAllocatingLease(
360 DrydockLease $lease) {
362 $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
364 $keep = array();
365 foreach ($impls as $key => $impl) {
366 // Don't use disabled blueprint types.
367 if (!$impl->isEnabled()) {
368 continue;
371 // Don't use blueprint types which can't allocate the correct kind of
372 // resource.
373 if ($impl->getType() != $lease->getResourceType()) {
374 continue;
377 if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
378 continue;
381 $keep[$key] = $impl;
384 return $keep;
389 * Get all the concrete @{class:DrydockBlueprint}s which can possibly
390 * build a resource to satisfy a lease.
392 * @param DrydockLease Requested lease.
393 * @return list<DrydockBlueprint> List of qualifying blueprints.
394 * @task allocator
396 private function loadBlueprintsForAllocatingLease(
397 DrydockLease $lease) {
398 $viewer = $this->getViewer();
400 $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease);
401 if (!$impls) {
402 return array();
405 $blueprint_phids = $lease->getAllowedBlueprintPHIDs();
406 if (!$blueprint_phids) {
407 $lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);
408 return array();
411 $query = id(new DrydockBlueprintQuery())
412 ->setViewer($viewer)
413 ->withPHIDs($blueprint_phids)
414 ->withBlueprintClasses(array_keys($impls))
415 ->withDisabled(false);
417 // The Drydock application itself is allowed to authorize anything. This
418 // is primarily used for leases generated by CLI administrative tools.
419 $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
421 $authorizing_phid = $lease->getAuthorizingPHID();
422 if ($authorizing_phid != $drydock_phid) {
423 $blueprints = id(clone $query)
424 ->withAuthorizedPHIDs(array($authorizing_phid))
425 ->execute();
426 if (!$blueprints) {
427 // If we didn't hit any blueprints, check if this is an authorization
428 // problem: re-execute the query without the authorization constraint.
429 // If the second query hits blueprints, the overall configuration is
430 // fine but this is an authorization problem. If the second query also
431 // comes up blank, this is some other kind of configuration issue so
432 // we fall through to the default pathway.
433 $all_blueprints = $query->execute();
434 if ($all_blueprints) {
435 $lease->logEvent(
436 DrydockLeaseNoAuthorizationsLogType::LOGCONST,
437 array(
438 'authorizingPHID' => $authorizing_phid,
440 return array();
443 } else {
444 $blueprints = $query->execute();
447 $keep = array();
448 foreach ($blueprints as $key => $blueprint) {
449 if (!$blueprint->canEverAllocateResourceForLease($lease)) {
450 continue;
453 $keep[$key] = $blueprint;
456 return $keep;
461 * Load a list of all resources which a given lease can possibly be
462 * allocated against.
464 * @param list<DrydockBlueprint> Blueprints which may produce suitable
465 * resources.
466 * @param DrydockLease Requested lease.
467 * @return list<DrydockResource> Resources which may be able to allocate
468 * the lease.
469 * @task allocator
471 private function loadResourcesForAllocatingLease(
472 array $blueprints,
473 DrydockLease $lease) {
474 assert_instances_of($blueprints, 'DrydockBlueprint');
475 $viewer = $this->getViewer();
477 $resources = id(new DrydockResourceQuery())
478 ->setViewer($viewer)
479 ->withBlueprintPHIDs(mpull($blueprints, 'getPHID'))
480 ->withTypes(array($lease->getResourceType()))
481 ->withStatuses(
482 array(
483 DrydockResourceStatus::STATUS_PENDING,
484 DrydockResourceStatus::STATUS_ACTIVE,
486 ->execute();
488 return $this->removeUnacquirableResources($resources, $lease);
493 * Remove resources which can not be acquired by a given lease from a list.
495 * @param list<DrydockResource> Candidate resources.
496 * @param DrydockLease Acquiring lease.
497 * @return list<DrydockResource> Resources which the lease may be able to
498 * acquire.
499 * @task allocator
501 private function removeUnacquirableResources(
502 array $resources,
503 DrydockLease $lease) {
504 $keep = array();
505 foreach ($resources as $key => $resource) {
506 $blueprint = $resource->getBlueprint();
508 if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {
509 continue;
512 $keep[$key] = $resource;
515 return $keep;
520 * Remove blueprints which are too heavily allocated to build a resource for
521 * a lease from a list of blueprints.
523 * @param list<DrydockBlueprint> List of blueprints.
524 * @return list<DrydockBlueprint> List with blueprints that can not allocate
525 * a resource for the lease right now removed.
526 * @task allocator
528 private function removeOverallocatedBlueprints(
529 array $blueprints,
530 DrydockLease $lease) {
531 assert_instances_of($blueprints, 'DrydockBlueprint');
533 $keep = array();
535 foreach ($blueprints as $key => $blueprint) {
536 if (!$blueprint->canAllocateResourceForLease($lease)) {
537 continue;
540 $keep[$key] = $blueprint;
543 return $keep;
548 * Rank blueprints by suitability for building a new resource for a
549 * particular lease.
551 * @param list<DrydockBlueprint> List of blueprints.
552 * @param DrydockLease Requested lease.
553 * @return list<DrydockBlueprint> Ranked list of blueprints.
554 * @task allocator
556 private function rankBlueprints(array $blueprints, DrydockLease $lease) {
557 assert_instances_of($blueprints, 'DrydockBlueprint');
559 // TODO: Implement improvements to this ranking algorithm if they become
560 // available.
561 shuffle($blueprints);
563 return $blueprints;
568 * Rank resources by suitability for allocating a particular lease.
570 * @param list<DrydockResource> List of resources.
571 * @param DrydockLease Requested lease.
572 * @return list<DrydockResource> Ranked list of resources.
573 * @task allocator
575 private function rankResources(array $resources, DrydockLease $lease) {
576 assert_instances_of($resources, 'DrydockResource');
578 // TODO: Implement improvements to this ranking algorithm if they become
579 // available.
580 shuffle($resources);
582 return $resources;
587 * Perform an actual resource allocation with a particular blueprint.
589 * @param DrydockBlueprint The blueprint to allocate a resource from.
590 * @param DrydockLease Requested lease.
591 * @return DrydockResource Allocated resource.
592 * @task allocator
594 private function allocateResource(
595 DrydockBlueprint $blueprint,
596 DrydockLease $lease) {
597 $resource = $blueprint->allocateResource($lease);
598 $this->validateAllocatedResource($blueprint, $resource, $lease);
600 // If this resource was allocated as a pending resource, queue a task to
601 // activate it.
602 if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
603 PhabricatorWorker::scheduleTask(
604 'DrydockResourceUpdateWorker',
605 array(
606 'resourcePHID' => $resource->getPHID(),
608 // This task will generally yield while the resource activates, so
609 // wake it back up once the resource comes online. Most of the time,
610 // we'll be able to lease the newly activated resource.
611 'awakenOnActivation' => array(
612 $this->getCurrentWorkerTaskID(),
615 array(
616 'objectPHID' => $resource->getPHID(),
620 return $resource;
625 * Check that the resource a blueprint allocated is roughly the sort of
626 * object we expect.
628 * @param DrydockBlueprint Blueprint which built the resource.
629 * @param wild Thing which the blueprint claims is a valid resource.
630 * @param DrydockLease Lease the resource was allocated for.
631 * @return void
632 * @task allocator
634 private function validateAllocatedResource(
635 DrydockBlueprint $blueprint,
636 $resource,
637 DrydockLease $lease) {
639 if (!($resource instanceof DrydockResource)) {
640 throw new Exception(
641 pht(
642 'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.
643 'return an object of type %s or throw, but returned something else.',
644 $blueprint->getBlueprintName(),
645 $blueprint->getClassName(),
646 'allocateResource()',
647 'DrydockResource'));
650 if (!$resource->isAllocatedResource()) {
651 throw new Exception(
652 pht(
653 'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
654 'must actually allocate the resource it returns.',
655 $blueprint->getBlueprintName(),
656 $blueprint->getClassName(),
657 'allocateResource()'));
660 $resource_type = $resource->getType();
661 $lease_type = $lease->getResourceType();
663 if ($resource_type !== $lease_type) {
664 throw new Exception(
665 pht(
666 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
667 'built a resource of type "%s" to satisfy a lease requesting a '.
668 'resource of type "%s".',
669 $blueprint->getBlueprintName(),
670 $blueprint->getClassName(),
671 $resource_type,
672 $lease_type));
676 private function reclaimResources(
677 DrydockBlueprint $blueprint,
678 DrydockLease $lease) {
679 $viewer = $this->getViewer();
681 // If this lease is marked as already in the process of reclaiming a
682 // resource, don't let it reclaim another one until the first reclaim
683 // completes. This stops one lease from reclaiming a large number of
684 // resources if the reclaims take a while to complete.
685 $reclaiming_phid = $lease->getAttribute('drydock.reclaimingPHID');
686 if ($reclaiming_phid) {
687 $reclaiming_resource = id(new DrydockResourceQuery())
688 ->setViewer($viewer)
689 ->withPHIDs(array($reclaiming_phid))
690 ->withStatuses(
691 array(
692 DrydockResourceStatus::STATUS_ACTIVE,
693 DrydockResourceStatus::STATUS_RELEASED,
695 ->executeOne();
696 if ($reclaiming_resource) {
697 return null;
701 $resources = id(new DrydockResourceQuery())
702 ->setViewer($viewer)
703 ->withBlueprintPHIDs(array($blueprint->getPHID()))
704 ->withStatuses(
705 array(
706 DrydockResourceStatus::STATUS_ACTIVE,
708 ->execute();
710 // TODO: We could be much smarter about this and try to release long-unused
711 // resources, resources with many similar copies, old resources, resources
712 // that are cheap to rebuild, etc.
713 shuffle($resources);
715 foreach ($resources as $resource) {
716 if ($this->canReclaimResource($resource)) {
717 $this->reclaimResource($resource, $lease);
718 return $resource;
722 return null;
726 /* -( Acquiring Leases )--------------------------------------------------- */
730 * Perform an actual lease acquisition on a particular resource.
732 * @param DrydockResource Resource to acquire a lease on.
733 * @param DrydockLease Lease to acquire.
734 * @return void
735 * @task acquire
737 private function acquireLease(
738 DrydockResource $resource,
739 DrydockLease $lease) {
741 $blueprint = $resource->getBlueprint();
742 $blueprint->acquireLease($resource, $lease);
744 $this->validateAcquiredLease($blueprint, $resource, $lease);
746 // If this lease has been acquired but not activated, queue a task to
747 // activate it.
748 if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {
749 $this->queueTask(
750 __CLASS__,
751 array(
752 'leasePHID' => $lease->getPHID(),
754 array(
755 'objectPHID' => $lease->getPHID(),
762 * Make sure that a lease was really acquired properly.
764 * @param DrydockBlueprint Blueprint which created the resource.
765 * @param DrydockResource Resource which was acquired.
766 * @param DrydockLease The lease which was supposedly acquired.
767 * @return void
768 * @task acquire
770 private function validateAcquiredLease(
771 DrydockBlueprint $blueprint,
772 DrydockResource $resource,
773 DrydockLease $lease) {
775 if (!$lease->isAcquiredLease()) {
776 throw new Exception(
777 pht(
778 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
779 'returned from "%s" without acquiring a lease.',
780 $blueprint->getBlueprintName(),
781 $blueprint->getClassName(),
782 'acquireLease()'));
785 $lease_phid = $lease->getResourcePHID();
786 $resource_phid = $resource->getPHID();
788 if ($lease_phid !== $resource_phid) {
789 throw new Exception(
790 pht(
791 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
792 'returned from "%s" with a lease acquired on the wrong resource.',
793 $blueprint->getBlueprintName(),
794 $blueprint->getClassName(),
795 'acquireLease()'));
799 private function newResourceForAcquisition(
800 DrydockResource $resource,
801 DrydockLease $lease) {
803 // If the resource has no leases against it, never build a new one. This is
804 // likely already a new resource that just activated.
805 $viewer = $this->getViewer();
807 $statuses = array(
808 DrydockLeaseStatus::STATUS_PENDING,
809 DrydockLeaseStatus::STATUS_ACQUIRED,
810 DrydockLeaseStatus::STATUS_ACTIVE,
813 $leases = id(new DrydockLeaseQuery())
814 ->setViewer($viewer)
815 ->withResourcePHIDs(array($resource->getPHID()))
816 ->withStatuses($statuses)
817 ->setLimit(1)
818 ->execute();
819 if (!$leases) {
820 return $resource;
823 // If we're about to get a lease on a resource, check if the blueprint
824 // wants to allocate a supplemental resource. If it does, try to perform a
825 // new allocation instead.
826 $blueprint = $resource->getBlueprint();
827 if (!$blueprint->shouldAllocateSupplementalResource($resource, $lease)) {
828 return $resource;
831 // If the blueprint is already overallocated, we can't allocate a new
832 // resource. Just return the existing resource.
833 $remaining = $this->removeOverallocatedBlueprints(
834 array($blueprint),
835 $lease);
836 if (!$remaining) {
837 return $resource;
840 // Try to build a new resource.
841 try {
842 $new_resource = $this->allocateResource($blueprint, $lease);
843 } catch (Exception $ex) {
844 $blueprint->logEvent(
845 DrydockResourceAllocationFailureLogType::LOGCONST,
846 array(
847 'class' => get_class($ex),
848 'message' => $ex->getMessage(),
851 return $resource;
854 // If we can't actually acquire the new resource yet, just yield.
855 // (We could try to move forward with the original resource instead.)
856 $acquirable = $this->removeUnacquirableResources(
857 array($new_resource),
858 $lease);
859 if (!$acquirable) {
860 throw new PhabricatorWorkerYieldException(15);
863 return $new_resource;
867 /* -( Activating Leases )-------------------------------------------------- */
871 * @task activate
873 private function activateLease(DrydockLease $lease) {
874 $resource = $lease->getResource();
875 if (!$resource) {
876 throw new Exception(
877 pht('Trying to activate lease with no resource.'));
880 $resource_status = $resource->getStatus();
882 if ($resource_status == DrydockResourceStatus::STATUS_PENDING) {
883 throw new PhabricatorWorkerYieldException(15);
886 if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) {
887 throw new DrydockAcquiredBrokenResourceException(
888 pht(
889 'Trying to activate lease ("%s") on a resource ("%s") in '.
890 'the wrong status ("%s").',
891 $lease->getPHID(),
892 $resource->getPHID(),
893 $resource_status));
896 // NOTE: We can race resource destruction here. Between the time we
897 // performed the read above and now, the resource might have closed, so
898 // we may activate leases on dead resources. At least for now, this seems
899 // fine: a resource dying right before we activate a lease on it should not
900 // be distinguishable from a resource dying right after we activate a lease
901 // on it. We end up with an active lease on a dead resource either way, and
902 // can not prevent resources dying from lightning strikes.
904 $blueprint = $resource->getBlueprint();
905 $blueprint->activateLease($resource, $lease);
906 $this->validateActivatedLease($blueprint, $resource, $lease);
910 * @task activate
912 private function validateActivatedLease(
913 DrydockBlueprint $blueprint,
914 DrydockResource $resource,
915 DrydockLease $lease) {
917 if (!$lease->isActivatedLease()) {
918 throw new Exception(
919 pht(
920 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
921 'returned from "%s" without activating a lease.',
922 $blueprint->getBlueprintName(),
923 $blueprint->getClassName(),
924 'acquireLease()'));
930 /* -( Releasing Leases )--------------------------------------------------- */
934 * @task release
936 private function releaseLease(DrydockLease $lease) {
937 $lease
938 ->setStatus(DrydockLeaseStatus::STATUS_RELEASED)
939 ->save();
941 $lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST);
943 $resource = $lease->getResource();
944 if ($resource) {
945 $blueprint = $resource->getBlueprint();
946 $blueprint->didReleaseLease($resource, $lease);
949 $this->destroyLease($lease);
953 /* -( Breaking Leases )---------------------------------------------------- */
957 * @task break
959 protected function breakLease(DrydockLease $lease, Exception $ex) {
960 switch ($lease->getStatus()) {
961 case DrydockLeaseStatus::STATUS_BROKEN:
962 case DrydockLeaseStatus::STATUS_RELEASED:
963 case DrydockLeaseStatus::STATUS_DESTROYED:
964 throw new PhutilProxyException(
965 pht(
966 'Unexpected failure while destroying lease ("%s").',
967 $lease->getPHID()),
968 $ex);
971 $lease
972 ->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
973 ->save();
975 $lease->logEvent(
976 DrydockLeaseActivationFailureLogType::LOGCONST,
977 array(
978 'class' => get_class($ex),
979 'message' => $ex->getMessage(),
982 $lease->awakenTasks();
984 $this->queueTask(
985 __CLASS__,
986 array(
987 'leasePHID' => $lease->getPHID(),
989 array(
990 'objectPHID' => $lease->getPHID(),
993 throw new PhabricatorWorkerPermanentFailureException(
994 pht(
995 'Permanent failure while activating lease ("%s"): %s',
996 $lease->getPHID(),
997 $ex->getMessage()));
1001 /* -( Destroying Leases )-------------------------------------------------- */
1005 * @task destroy
1007 private function destroyLease(DrydockLease $lease) {
1008 $resource = $lease->getResource();
1010 if ($resource) {
1011 $blueprint = $resource->getBlueprint();
1012 $blueprint->destroyLease($resource, $lease);
1015 DrydockSlotLock::releaseLocks($lease->getPHID());
1017 $lease
1018 ->setStatus(DrydockLeaseStatus::STATUS_DESTROYED)
1019 ->save();
1021 $lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);
1023 $lease->awakenTasks();