Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
12256 views
1
<?php
2
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
12
*/
13
final class DrydockLeaseUpdateWorker extends DrydockWorker {
14
15
protected function doWork() {
16
$lease_phid = $this->getTaskDataValue('leasePHID');
17
18
$hash = PhabricatorHash::digestForIndex($lease_phid);
19
$lock_key = 'drydock.lease:'.$hash;
20
21
$lock = PhabricatorGlobalLock::newLock($lock_key)
22
->lock(1);
23
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;
31
}
32
33
$lock->unlock();
34
}
35
36
37
/* -( Updating Leases )---------------------------------------------------- */
38
39
40
/**
41
* @task update
42
*/
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.
50
51
// Before we throw it back, release any locks the lease is holding.
52
DrydockSlotLock::releaseLocks($lease->getPHID());
53
54
$lease
55
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
56
->setResourcePHID(null)
57
->save();
58
59
$lease->logEvent(
60
DrydockLeaseReacquireLogType::LOGCONST,
61
array(
62
'class' => get_class($ex),
63
'message' => $ex->getMessage(),
64
));
65
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);
72
}
73
}
74
}
75
76
77
/**
78
* @task update
79
*/
80
private function updateLease(DrydockLease $lease) {
81
$this->processLeaseCommands($lease);
82
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;
100
}
101
102
$this->yieldIfExpiringLease($lease);
103
}
104
105
106
/**
107
* @task update
108
*/
109
private function yieldLease(DrydockLease $lease, Exception $ex) {
110
$duration = $this->getYieldDurationFromException($ex);
111
112
$lease->logEvent(
113
DrydockLeaseActivationYieldLogType::LOGCONST,
114
array(
115
'duration' => $duration,
116
));
117
118
throw new PhabricatorWorkerYieldException($duration);
119
}
120
121
122
/* -( Processing Commands )------------------------------------------------ */
123
124
125
/**
126
* @task command
127
*/
128
private function processLeaseCommands(DrydockLease $lease) {
129
if (!$lease->canReceiveCommands()) {
130
return;
131
}
132
133
$this->checkLeaseExpiration($lease);
134
135
$commands = $this->loadCommands($lease->getPHID());
136
foreach ($commands as $command) {
137
if (!$lease->canReceiveCommands()) {
138
break;
139
}
140
141
$this->processLeaseCommand($lease, $command);
142
143
$command
144
->setIsConsumed(true)
145
->save();
146
}
147
}
148
149
150
/**
151
* @task command
152
*/
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;
160
}
161
}
162
163
164
/* -( Drydock Allocator )-------------------------------------------------- */
165
166
167
/**
168
* Find or build a resource which can satisfy a given lease request, then
169
* acquire the lease.
170
*
171
* @param DrydockLease Requested lease.
172
* @return void
173
* @task allocator
174
*/
175
private function executeAllocator(DrydockLease $lease) {
176
$blueprints = $this->loadBlueprintsForAllocatingLease($lease);
177
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()));
187
}
188
189
// First, try to find a suitable open resource which we can acquire a new
190
// lease on.
191
$resources = $this->loadAcquirableResourcesForLease($blueprints, $lease);
192
193
list($free_resources, $used_resources) = $this->partitionResources(
194
$lease,
195
$resources);
196
197
$resource = $this->leaseAnyResource($lease, $free_resources);
198
if ($resource) {
199
return $resource;
200
}
201
202
// We're about to try creating a resource. If we're already creating
203
// something, just yield until that resolves.
204
205
$this->yieldForPendingResources($lease);
206
207
// We haven't been able to lease an existing resource yet, so now we try to
208
// create one. We may still have some less-desirable "used" resources that
209
// we'll sometimes try to lease later if we fail to allocate a new resource.
210
211
$resource = $this->newLeasedResource($lease, $blueprints);
212
if ($resource) {
213
return $resource;
214
}
215
216
// We haven't been able to lease a desirable "free" resource or create a
217
// new resource. Try to lease a "used" resource.
218
219
$resource = $this->leaseAnyResource($lease, $used_resources);
220
if ($resource) {
221
return $resource;
222
}
223
224
// If this lease has already triggered a reclaim, just yield and wait for
225
// it to resolve.
226
$this->yieldForReclaimingResources($lease);
227
228
// Try to reclaim a resource. This will yield if it reclaims something.
229
$this->reclaimAnyResource($lease, $blueprints);
230
231
// We weren't able to lease, create, or reclaim any resources. We just have
232
// to wait for resources to become available.
233
234
$lease->logEvent(
235
DrydockLeaseWaitingForResourcesLogType::LOGCONST,
236
array(
237
'blueprintPHIDs' => mpull($blueprints, 'getPHID'),
238
));
239
240
throw new PhabricatorWorkerYieldException(15);
241
}
242
243
private function reclaimAnyResource(DrydockLease $lease, array $blueprints) {
244
assert_instances_of($blueprints, 'DrydockBlueprint');
245
246
$blueprints = $this->rankBlueprints($blueprints, $lease);
247
248
// Try to actively reclaim unused resources. If we succeed, jump back
249
// into the queue in an effort to claim it.
250
251
foreach ($blueprints as $blueprint) {
252
$reclaimed = $this->reclaimResources($blueprint, $lease);
253
if ($reclaimed) {
254
255
$lease->logEvent(
256
DrydockLeaseReclaimLogType::LOGCONST,
257
array(
258
'resourcePHIDs' => array($reclaimed->getPHID()),
259
));
260
261
// Yield explicitly here: we'll be awakened when the resource is
262
// reclaimed.
263
264
throw new PhabricatorWorkerYieldException(15);
265
}
266
}
267
}
268
269
private function yieldForPendingResources(DrydockLease $lease) {
270
// See T13677. If this lease has already triggered the allocation of
271
// one or more resources and they are still pending, just yield and
272
// wait for them.
273
274
$viewer = $this->getViewer();
275
276
$phids = $lease->getAllocatedResourcePHIDs();
277
if (!$phids) {
278
return null;
279
}
280
281
$resources = id(new DrydockResourceQuery())
282
->setViewer($viewer)
283
->withPHIDs($phids)
284
->withStatuses(
285
array(
286
DrydockResourceStatus::STATUS_PENDING,
287
))
288
->setLimit(1)
289
->execute();
290
if (!$resources) {
291
return;
292
}
293
294
$lease->logEvent(
295
DrydockLeaseWaitingForActivationLogType::LOGCONST,
296
array(
297
'resourcePHIDs' => mpull($resources, 'getPHID'),
298
));
299
300
throw new PhabricatorWorkerYieldException(15);
301
}
302
303
private function yieldForReclaimingResources(DrydockLease $lease) {
304
$viewer = $this->getViewer();
305
306
$phids = $lease->getReclaimedResourcePHIDs();
307
if (!$phids) {
308
return;
309
}
310
311
$resources = id(new DrydockResourceQuery())
312
->setViewer($viewer)
313
->withPHIDs($phids)
314
->withStatuses(
315
array(
316
DrydockResourceStatus::STATUS_ACTIVE,
317
DrydockResourceStatus::STATUS_RELEASED,
318
))
319
->setLimit(1)
320
->execute();
321
if (!$resources) {
322
return;
323
}
324
325
$lease->logEvent(
326
DrydockLeaseWaitingForReclamationLogType::LOGCONST,
327
array(
328
'resourcePHIDs' => mpull($resources, 'getPHID'),
329
));
330
331
throw new PhabricatorWorkerYieldException(15);
332
}
333
334
private function newLeasedResource(
335
DrydockLease $lease,
336
array $blueprints) {
337
assert_instances_of($blueprints, 'DrydockBlueprint');
338
339
$usable_blueprints = $this->removeOverallocatedBlueprints(
340
$blueprints,
341
$lease);
342
343
// If we get nothing back here, some blueprint claims it can eventually
344
// satisfy the lease, just not right now. This is a temporary failure,
345
// and we expect allocation to succeed eventually.
346
347
// Return, try to lease a "used" resource, and continue from there.
348
349
if (!$usable_blueprints) {
350
return null;
351
}
352
353
$usable_blueprints = $this->rankBlueprints($usable_blueprints, $lease);
354
355
$new_resources = $this->newResources($lease, $usable_blueprints);
356
if (!$new_resources) {
357
// If we were unable to create any new resources, return and
358
// try to lease a "used" resource.
359
return null;
360
}
361
362
$new_resources = $this->removeUnacquirableResources(
363
$new_resources,
364
$lease);
365
if (!$new_resources) {
366
// If we make it here, we just built a resource but aren't allowed
367
// to acquire it. We expect this to happen if the resource prevents
368
// acquisition until it activates, which is common when a resource
369
// needs to perform setup steps.
370
371
// Explicitly yield and wait for activation, since we don't want to
372
// lease a "used" resource.
373
374
throw new PhabricatorWorkerYieldException(15);
375
}
376
377
$resource = $this->leaseAnyResource($lease, $new_resources);
378
if ($resource) {
379
return $resource;
380
}
381
382
// We may not be able to lease a resource even if we just built it:
383
// another process may snatch it up before we can lease it. This should
384
// be rare, but is not concerning. Just try to build another resource.
385
386
// We likely could try to build the next resource immediately, but err on
387
// the side of caution and yield for now, at least until this code is
388
// better vetted.
389
390
throw new PhabricatorWorkerYieldException(15);
391
}
392
393
private function partitionResources(
394
DrydockLease $lease,
395
array $resources) {
396
397
assert_instances_of($resources, 'DrydockResource');
398
$viewer = $this->getViewer();
399
400
$lease_statuses = array(
401
DrydockLeaseStatus::STATUS_PENDING,
402
DrydockLeaseStatus::STATUS_ACQUIRED,
403
DrydockLeaseStatus::STATUS_ACTIVE,
404
);
405
406
// Partition resources into "free" resources (which we can try to lease
407
// immediately) and "used" resources, which we can only to lease after we
408
// fail to allocate a new resource.
409
410
// "Free" resources are unleased and/or prefer reuse over allocation.
411
// "Used" resources are leased and prefer allocation over reuse.
412
413
$free_resources = array();
414
$used_resources = array();
415
416
foreach ($resources as $resource) {
417
$blueprint = $resource->getBlueprint();
418
419
if (!$blueprint->shouldAllocateSupplementalResource($resource, $lease)) {
420
$free_resources[] = $resource;
421
continue;
422
}
423
424
$leases = id(new DrydockLeaseQuery())
425
->setViewer($viewer)
426
->withResourcePHIDs(array($resource->getPHID()))
427
->withStatuses($lease_statuses)
428
->setLimit(1)
429
->execute();
430
if (!$leases) {
431
$free_resources[] = $resource;
432
continue;
433
}
434
435
$used_resources[] = $resource;
436
}
437
438
return array($free_resources, $used_resources);
439
}
440
441
private function newResources(
442
DrydockLease $lease,
443
array $blueprints) {
444
assert_instances_of($blueprints, 'DrydockBlueprint');
445
446
$resources = array();
447
$exceptions = array();
448
foreach ($blueprints as $blueprint) {
449
$caught = null;
450
try {
451
$resources[] = $this->allocateResource($blueprint, $lease);
452
453
// Bail after allocating one resource, we don't need any more than
454
// this.
455
break;
456
} catch (Exception $ex) {
457
$caught = $ex;
458
} catch (Throwable $ex) {
459
$caught = $ex;
460
}
461
462
if ($caught) {
463
// This failure is not normally expected, so log it. It can be
464
// caused by something mundane and recoverable, however (see below
465
// for discussion).
466
467
// We log to the blueprint separately from the log to the lease:
468
// the lease is not attached to a blueprint yet so the lease log
469
// will not show up on the blueprint; more than one blueprint may
470
// fail; and the lease is not really impacted (and won't log) if at
471
// least one blueprint actually works.
472
473
$blueprint->logEvent(
474
DrydockResourceAllocationFailureLogType::LOGCONST,
475
array(
476
'class' => get_class($caught),
477
'message' => $caught->getMessage(),
478
));
479
480
$exceptions[] = $caught;
481
}
482
}
483
484
if (!$resources) {
485
// If one or more blueprints claimed that they would be able to allocate
486
// resources but none are actually able to allocate resources, log the
487
// failure and yield so we try again soon.
488
489
// This can happen if some unexpected issue occurs during allocation
490
// (for example, a call to build a VM fails for some reason) or if we
491
// raced another allocator and the blueprint is now full.
492
493
$ex = new PhutilAggregateException(
494
pht(
495
'All blueprints failed to allocate a suitable new resource when '.
496
'trying to allocate lease ("%s").',
497
$lease->getPHID()),
498
$exceptions);
499
500
$lease->logEvent(
501
DrydockLeaseAllocationFailureLogType::LOGCONST,
502
array(
503
'class' => get_class($ex),
504
'message' => $ex->getMessage(),
505
));
506
507
return null;
508
}
509
510
return $resources;
511
}
512
513
514
private function leaseAnyResource(
515
DrydockLease $lease,
516
array $resources) {
517
assert_instances_of($resources, 'DrydockResource');
518
519
if (!$resources) {
520
return null;
521
}
522
523
$resources = $this->rankResources($resources, $lease);
524
525
$exceptions = array();
526
$yields = array();
527
528
$allocated = null;
529
foreach ($resources as $resource) {
530
try {
531
$this->acquireLease($resource, $lease);
532
$allocated = $resource;
533
break;
534
} catch (DrydockResourceLockException $ex) {
535
// We need to lock the resource to actually acquire it. If we aren't
536
// able to acquire the lock quickly enough, we can yield and try again
537
// later.
538
$yields[] = $ex;
539
} catch (DrydockSlotLockException $ex) {
540
// This also just indicates we ran into some kind of contention,
541
// probably from another lease. Just yield.
542
$yields[] = $ex;
543
} catch (DrydockAcquiredBrokenResourceException $ex) {
544
// If a resource was reclaimed or destroyed by the time we actually
545
// got around to acquiring it, we just got unlucky.
546
$yields[] = $ex;
547
} catch (PhabricatorWorkerYieldException $ex) {
548
// We can be told to yield, particularly by the supplemental allocator
549
// trying to give us a supplemental resource.
550
$yields[] = $ex;
551
} catch (Exception $ex) {
552
$exceptions[] = $ex;
553
}
554
}
555
556
if ($allocated) {
557
return $allocated;
558
}
559
560
if ($yields) {
561
throw new PhabricatorWorkerYieldException(15);
562
}
563
564
throw new PhutilAggregateException(
565
pht(
566
'Unable to acquire lease "%s" on any resource.',
567
$lease->getPHID()),
568
$exceptions);
569
}
570
571
572
/**
573
* Get all the concrete @{class:DrydockBlueprint}s which can possibly
574
* build a resource to satisfy a lease.
575
*
576
* @param DrydockLease Requested lease.
577
* @return list<DrydockBlueprint> List of qualifying blueprints.
578
* @task allocator
579
*/
580
private function loadBlueprintsForAllocatingLease(
581
DrydockLease $lease) {
582
$viewer = $this->getViewer();
583
584
$impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease);
585
if (!$impls) {
586
return array();
587
}
588
589
$blueprint_phids = $lease->getAllowedBlueprintPHIDs();
590
if (!$blueprint_phids) {
591
$lease->logEvent(DrydockLeaseNoBlueprintsLogType::LOGCONST);
592
return array();
593
}
594
595
$query = id(new DrydockBlueprintQuery())
596
->setViewer($viewer)
597
->withPHIDs($blueprint_phids)
598
->withBlueprintClasses(array_keys($impls))
599
->withDisabled(false);
600
601
// The Drydock application itself is allowed to authorize anything. This
602
// is primarily used for leases generated by CLI administrative tools.
603
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
604
605
$authorizing_phid = $lease->getAuthorizingPHID();
606
if ($authorizing_phid != $drydock_phid) {
607
$blueprints = id(clone $query)
608
->withAuthorizedPHIDs(array($authorizing_phid))
609
->execute();
610
if (!$blueprints) {
611
// If we didn't hit any blueprints, check if this is an authorization
612
// problem: re-execute the query without the authorization constraint.
613
// If the second query hits blueprints, the overall configuration is
614
// fine but this is an authorization problem. If the second query also
615
// comes up blank, this is some other kind of configuration issue so
616
// we fall through to the default pathway.
617
$all_blueprints = $query->execute();
618
if ($all_blueprints) {
619
$lease->logEvent(
620
DrydockLeaseNoAuthorizationsLogType::LOGCONST,
621
array(
622
'authorizingPHID' => $authorizing_phid,
623
));
624
return array();
625
}
626
}
627
} else {
628
$blueprints = $query->execute();
629
}
630
631
$keep = array();
632
foreach ($blueprints as $key => $blueprint) {
633
if (!$blueprint->canEverAllocateResourceForLease($lease)) {
634
continue;
635
}
636
637
$keep[$key] = $blueprint;
638
}
639
640
return $keep;
641
}
642
643
644
/**
645
* Load a list of all resources which a given lease can possibly be
646
* allocated against.
647
*
648
* @param list<DrydockBlueprint> Blueprints which may produce suitable
649
* resources.
650
* @param DrydockLease Requested lease.
651
* @return list<DrydockResource> Resources which may be able to allocate
652
* the lease.
653
* @task allocator
654
*/
655
private function loadAcquirableResourcesForLease(
656
array $blueprints,
657
DrydockLease $lease) {
658
assert_instances_of($blueprints, 'DrydockBlueprint');
659
$viewer = $this->getViewer();
660
661
$resources = id(new DrydockResourceQuery())
662
->setViewer($viewer)
663
->withBlueprintPHIDs(mpull($blueprints, 'getPHID'))
664
->withTypes(array($lease->getResourceType()))
665
->withStatuses(
666
array(
667
DrydockResourceStatus::STATUS_ACTIVE,
668
))
669
->execute();
670
671
return $this->removeUnacquirableResources($resources, $lease);
672
}
673
674
675
/**
676
* Remove resources which can not be acquired by a given lease from a list.
677
*
678
* @param list<DrydockResource> Candidate resources.
679
* @param DrydockLease Acquiring lease.
680
* @return list<DrydockResource> Resources which the lease may be able to
681
* acquire.
682
* @task allocator
683
*/
684
private function removeUnacquirableResources(
685
array $resources,
686
DrydockLease $lease) {
687
$keep = array();
688
foreach ($resources as $key => $resource) {
689
$blueprint = $resource->getBlueprint();
690
691
if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {
692
continue;
693
}
694
695
$keep[$key] = $resource;
696
}
697
698
return $keep;
699
}
700
701
702
/**
703
* Remove blueprints which are too heavily allocated to build a resource for
704
* a lease from a list of blueprints.
705
*
706
* @param list<DrydockBlueprint> List of blueprints.
707
* @return list<DrydockBlueprint> List with blueprints that can not allocate
708
* a resource for the lease right now removed.
709
* @task allocator
710
*/
711
private function removeOverallocatedBlueprints(
712
array $blueprints,
713
DrydockLease $lease) {
714
assert_instances_of($blueprints, 'DrydockBlueprint');
715
716
$keep = array();
717
718
foreach ($blueprints as $key => $blueprint) {
719
if (!$blueprint->canAllocateResourceForLease($lease)) {
720
continue;
721
}
722
723
$keep[$key] = $blueprint;
724
}
725
726
return $keep;
727
}
728
729
730
/**
731
* Rank blueprints by suitability for building a new resource for a
732
* particular lease.
733
*
734
* @param list<DrydockBlueprint> List of blueprints.
735
* @param DrydockLease Requested lease.
736
* @return list<DrydockBlueprint> Ranked list of blueprints.
737
* @task allocator
738
*/
739
private function rankBlueprints(array $blueprints, DrydockLease $lease) {
740
assert_instances_of($blueprints, 'DrydockBlueprint');
741
742
// TODO: Implement improvements to this ranking algorithm if they become
743
// available.
744
shuffle($blueprints);
745
746
return $blueprints;
747
}
748
749
750
/**
751
* Rank resources by suitability for allocating a particular lease.
752
*
753
* @param list<DrydockResource> List of resources.
754
* @param DrydockLease Requested lease.
755
* @return list<DrydockResource> Ranked list of resources.
756
* @task allocator
757
*/
758
private function rankResources(array $resources, DrydockLease $lease) {
759
assert_instances_of($resources, 'DrydockResource');
760
761
// TODO: Implement improvements to this ranking algorithm if they become
762
// available.
763
shuffle($resources);
764
765
return $resources;
766
}
767
768
769
/**
770
* Perform an actual resource allocation with a particular blueprint.
771
*
772
* @param DrydockBlueprint The blueprint to allocate a resource from.
773
* @param DrydockLease Requested lease.
774
* @return DrydockResource Allocated resource.
775
* @task allocator
776
*/
777
private function allocateResource(
778
DrydockBlueprint $blueprint,
779
DrydockLease $lease) {
780
$resource = $blueprint->allocateResource($lease);
781
$this->validateAllocatedResource($blueprint, $resource, $lease);
782
783
// If this resource was allocated as a pending resource, queue a task to
784
// activate it.
785
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
786
787
$lease->addAllocatedResourcePHIDs(
788
array(
789
$resource->getPHID(),
790
));
791
$lease->save();
792
793
PhabricatorWorker::scheduleTask(
794
'DrydockResourceUpdateWorker',
795
array(
796
'resourcePHID' => $resource->getPHID(),
797
798
// This task will generally yield while the resource activates, so
799
// wake it back up once the resource comes online. Most of the time,
800
// we'll be able to lease the newly activated resource.
801
'awakenOnActivation' => array(
802
$this->getCurrentWorkerTaskID(),
803
),
804
),
805
array(
806
'objectPHID' => $resource->getPHID(),
807
));
808
}
809
810
return $resource;
811
}
812
813
814
/**
815
* Check that the resource a blueprint allocated is roughly the sort of
816
* object we expect.
817
*
818
* @param DrydockBlueprint Blueprint which built the resource.
819
* @param wild Thing which the blueprint claims is a valid resource.
820
* @param DrydockLease Lease the resource was allocated for.
821
* @return void
822
* @task allocator
823
*/
824
private function validateAllocatedResource(
825
DrydockBlueprint $blueprint,
826
$resource,
827
DrydockLease $lease) {
828
829
if (!($resource instanceof DrydockResource)) {
830
throw new Exception(
831
pht(
832
'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.
833
'return an object of type %s or throw, but returned something else.',
834
$blueprint->getBlueprintName(),
835
$blueprint->getClassName(),
836
'allocateResource()',
837
'DrydockResource'));
838
}
839
840
if (!$resource->isAllocatedResource()) {
841
throw new Exception(
842
pht(
843
'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
844
'must actually allocate the resource it returns.',
845
$blueprint->getBlueprintName(),
846
$blueprint->getClassName(),
847
'allocateResource()'));
848
}
849
850
$resource_type = $resource->getType();
851
$lease_type = $lease->getResourceType();
852
853
if ($resource_type !== $lease_type) {
854
throw new Exception(
855
pht(
856
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
857
'built a resource of type "%s" to satisfy a lease requesting a '.
858
'resource of type "%s".',
859
$blueprint->getBlueprintName(),
860
$blueprint->getClassName(),
861
$resource_type,
862
$lease_type));
863
}
864
}
865
866
private function reclaimResources(
867
DrydockBlueprint $blueprint,
868
DrydockLease $lease) {
869
$viewer = $this->getViewer();
870
871
$resources = id(new DrydockResourceQuery())
872
->setViewer($viewer)
873
->withBlueprintPHIDs(array($blueprint->getPHID()))
874
->withStatuses(
875
array(
876
DrydockResourceStatus::STATUS_ACTIVE,
877
))
878
->execute();
879
880
// TODO: We could be much smarter about this and try to release long-unused
881
// resources, resources with many similar copies, old resources, resources
882
// that are cheap to rebuild, etc.
883
shuffle($resources);
884
885
foreach ($resources as $resource) {
886
if ($this->canReclaimResource($resource)) {
887
$this->reclaimResource($resource, $lease);
888
return $resource;
889
}
890
}
891
892
return null;
893
}
894
895
896
/* -( Acquiring Leases )--------------------------------------------------- */
897
898
899
/**
900
* Perform an actual lease acquisition on a particular resource.
901
*
902
* @param DrydockResource Resource to acquire a lease on.
903
* @param DrydockLease Lease to acquire.
904
* @return void
905
* @task acquire
906
*/
907
private function acquireLease(
908
DrydockResource $resource,
909
DrydockLease $lease) {
910
911
$blueprint = $resource->getBlueprint();
912
$blueprint->acquireLease($resource, $lease);
913
914
$this->validateAcquiredLease($blueprint, $resource, $lease);
915
916
// If this lease has been acquired but not activated, queue a task to
917
// activate it.
918
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {
919
$this->queueTask(
920
__CLASS__,
921
array(
922
'leasePHID' => $lease->getPHID(),
923
),
924
array(
925
'objectPHID' => $lease->getPHID(),
926
));
927
}
928
}
929
930
931
/**
932
* Make sure that a lease was really acquired properly.
933
*
934
* @param DrydockBlueprint Blueprint which created the resource.
935
* @param DrydockResource Resource which was acquired.
936
* @param DrydockLease The lease which was supposedly acquired.
937
* @return void
938
* @task acquire
939
*/
940
private function validateAcquiredLease(
941
DrydockBlueprint $blueprint,
942
DrydockResource $resource,
943
DrydockLease $lease) {
944
945
if (!$lease->isAcquiredLease()) {
946
throw new Exception(
947
pht(
948
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
949
'returned from "%s" without acquiring a lease.',
950
$blueprint->getBlueprintName(),
951
$blueprint->getClassName(),
952
'acquireLease()'));
953
}
954
955
$lease_phid = $lease->getResourcePHID();
956
$resource_phid = $resource->getPHID();
957
958
if ($lease_phid !== $resource_phid) {
959
throw new Exception(
960
pht(
961
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
962
'returned from "%s" with a lease acquired on the wrong resource.',
963
$blueprint->getBlueprintName(),
964
$blueprint->getClassName(),
965
'acquireLease()'));
966
}
967
}
968
969
970
/* -( Activating Leases )-------------------------------------------------- */
971
972
973
/**
974
* @task activate
975
*/
976
private function activateLease(DrydockLease $lease) {
977
$resource = $lease->getResource();
978
if (!$resource) {
979
throw new Exception(
980
pht('Trying to activate lease with no resource.'));
981
}
982
983
$resource_status = $resource->getStatus();
984
985
if ($resource_status == DrydockResourceStatus::STATUS_PENDING) {
986
throw new PhabricatorWorkerYieldException(15);
987
}
988
989
if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) {
990
throw new DrydockAcquiredBrokenResourceException(
991
pht(
992
'Trying to activate lease ("%s") on a resource ("%s") in '.
993
'the wrong status ("%s").',
994
$lease->getPHID(),
995
$resource->getPHID(),
996
$resource_status));
997
}
998
999
// NOTE: We can race resource destruction here. Between the time we
1000
// performed the read above and now, the resource might have closed, so
1001
// we may activate leases on dead resources. At least for now, this seems
1002
// fine: a resource dying right before we activate a lease on it should not
1003
// be distinguishable from a resource dying right after we activate a lease
1004
// on it. We end up with an active lease on a dead resource either way, and
1005
// can not prevent resources dying from lightning strikes.
1006
1007
$blueprint = $resource->getBlueprint();
1008
$blueprint->activateLease($resource, $lease);
1009
$this->validateActivatedLease($blueprint, $resource, $lease);
1010
}
1011
1012
/**
1013
* @task activate
1014
*/
1015
private function validateActivatedLease(
1016
DrydockBlueprint $blueprint,
1017
DrydockResource $resource,
1018
DrydockLease $lease) {
1019
1020
if (!$lease->isActivatedLease()) {
1021
throw new Exception(
1022
pht(
1023
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
1024
'returned from "%s" without activating a lease.',
1025
$blueprint->getBlueprintName(),
1026
$blueprint->getClassName(),
1027
'acquireLease()'));
1028
}
1029
1030
}
1031
1032
1033
/* -( Releasing Leases )--------------------------------------------------- */
1034
1035
1036
/**
1037
* @task release
1038
*/
1039
private function releaseLease(DrydockLease $lease) {
1040
$lease
1041
->setStatus(DrydockLeaseStatus::STATUS_RELEASED)
1042
->save();
1043
1044
$lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST);
1045
1046
$resource = $lease->getResource();
1047
if ($resource) {
1048
$blueprint = $resource->getBlueprint();
1049
$blueprint->didReleaseLease($resource, $lease);
1050
}
1051
1052
$this->destroyLease($lease);
1053
}
1054
1055
1056
/* -( Breaking Leases )---------------------------------------------------- */
1057
1058
1059
/**
1060
* @task break
1061
*/
1062
protected function breakLease(DrydockLease $lease, Exception $ex) {
1063
switch ($lease->getStatus()) {
1064
case DrydockLeaseStatus::STATUS_BROKEN:
1065
case DrydockLeaseStatus::STATUS_RELEASED:
1066
case DrydockLeaseStatus::STATUS_DESTROYED:
1067
throw new PhutilProxyException(
1068
pht(
1069
'Unexpected failure while destroying lease ("%s").',
1070
$lease->getPHID()),
1071
$ex);
1072
}
1073
1074
$lease
1075
->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
1076
->save();
1077
1078
$lease->logEvent(
1079
DrydockLeaseActivationFailureLogType::LOGCONST,
1080
array(
1081
'class' => get_class($ex),
1082
'message' => $ex->getMessage(),
1083
));
1084
1085
$lease->awakenTasks();
1086
1087
$this->queueTask(
1088
__CLASS__,
1089
array(
1090
'leasePHID' => $lease->getPHID(),
1091
),
1092
array(
1093
'objectPHID' => $lease->getPHID(),
1094
));
1095
1096
throw new PhabricatorWorkerPermanentFailureException(
1097
pht(
1098
'Permanent failure while activating lease ("%s"): %s',
1099
$lease->getPHID(),
1100
$ex->getMessage()));
1101
}
1102
1103
1104
/* -( Destroying Leases )-------------------------------------------------- */
1105
1106
1107
/**
1108
* @task destroy
1109
*/
1110
private function destroyLease(DrydockLease $lease) {
1111
$resource = $lease->getResource();
1112
1113
if ($resource) {
1114
$blueprint = $resource->getBlueprint();
1115
$blueprint->destroyLease($resource, $lease);
1116
}
1117
1118
DrydockSlotLock::releaseLocks($lease->getPHID());
1119
1120
$lease
1121
->setStatus(DrydockLeaseStatus::STATUS_DESTROYED)
1122
->save();
1123
1124
$lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);
1125
1126
$lease->awakenTasks();
1127
}
1128
1129
}
1130
1131