Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
12256 views
1
<?php
2
3
/**
4
* @task lease Lease Acquisition
5
* @task resource Resource Allocation
6
* @task interface Resource Interfaces
7
* @task log Logging
8
*/
9
abstract class DrydockBlueprintImplementation extends Phobject {
10
11
abstract public function getType();
12
13
abstract public function isEnabled();
14
15
abstract public function getBlueprintName();
16
abstract public function getDescription();
17
18
public function getBlueprintIcon() {
19
return 'fa-map-o';
20
}
21
22
public function getFieldSpecifications() {
23
$fields = array();
24
25
$fields += $this->getCustomFieldSpecifications();
26
27
if ($this->shouldUseConcurrentResourceLimit()) {
28
$fields += array(
29
'allocator.limit' => array(
30
'name' => pht('Limit'),
31
'caption' => pht(
32
'Maximum number of resources this blueprint can have active '.
33
'concurrently.'),
34
'type' => 'int',
35
),
36
);
37
}
38
39
return $fields;
40
}
41
42
protected function getCustomFieldSpecifications() {
43
return array();
44
}
45
46
public function getViewer() {
47
return PhabricatorUser::getOmnipotentUser();
48
}
49
50
51
/* -( Lease Acquisition )-------------------------------------------------- */
52
53
54
/**
55
* Enforce basic checks on lease/resource compatibility. Allows resources to
56
* reject leases if they are incompatible, even if the resource types match.
57
*
58
* For example, if a resource represents a 32-bit host, this method might
59
* reject leases that need a 64-bit host. The blueprint might also reject
60
* a resource if the lease needs 8GB of RAM and the resource only has 6GB
61
* free.
62
*
63
* This method should not acquire locks or expect anything to be locked. This
64
* is a coarse compatibility check between a lease and a resource.
65
*
66
* @param DrydockBlueprint Concrete blueprint to allocate for.
67
* @param DrydockResource Candidate resource to allocate the lease on.
68
* @param DrydockLease Pending lease that wants to allocate here.
69
* @return bool True if the resource and lease are compatible.
70
* @task lease
71
*/
72
abstract public function canAcquireLeaseOnResource(
73
DrydockBlueprint $blueprint,
74
DrydockResource $resource,
75
DrydockLease $lease);
76
77
78
/**
79
* Acquire a lease. Allows resources to perform setup as leases are brought
80
* online.
81
*
82
* If acquisition fails, throw an exception.
83
*
84
* @param DrydockBlueprint Blueprint which built the resource.
85
* @param DrydockResource Resource to acquire a lease on.
86
* @param DrydockLease Requested lease.
87
* @return void
88
* @task lease
89
*/
90
abstract public function acquireLease(
91
DrydockBlueprint $blueprint,
92
DrydockResource $resource,
93
DrydockLease $lease);
94
95
96
/**
97
* @return void
98
* @task lease
99
*/
100
public function activateLease(
101
DrydockBlueprint $blueprint,
102
DrydockResource $resource,
103
DrydockLease $lease) {
104
throw new PhutilMethodNotImplementedException();
105
}
106
107
108
/**
109
* React to a lease being released.
110
*
111
* This callback is primarily useful for automatically releasing resources
112
* once all leases are released.
113
*
114
* @param DrydockBlueprint Blueprint which built the resource.
115
* @param DrydockResource Resource a lease was released on.
116
* @param DrydockLease Recently released lease.
117
* @return void
118
* @task lease
119
*/
120
abstract public function didReleaseLease(
121
DrydockBlueprint $blueprint,
122
DrydockResource $resource,
123
DrydockLease $lease);
124
125
126
/**
127
* Destroy any temporary data associated with a lease.
128
*
129
* If a lease creates temporary state while held, destroy it here.
130
*
131
* @param DrydockBlueprint Blueprint which built the resource.
132
* @param DrydockResource Resource the lease is acquired on.
133
* @param DrydockLease The lease being destroyed.
134
* @return void
135
* @task lease
136
*/
137
abstract public function destroyLease(
138
DrydockBlueprint $blueprint,
139
DrydockResource $resource,
140
DrydockLease $lease);
141
142
/**
143
* Return true to try to allocate a new resource and expand the resource
144
* pool instead of permitting an otherwise valid acquisition on an existing
145
* resource.
146
*
147
* This allows the blueprint to provide a soft hint about when the resource
148
* pool should grow.
149
*
150
* Returning "true" in all cases generally makes sense when a blueprint
151
* controls a fixed pool of resources, like a particular number of physical
152
* hosts: you want to put all the hosts in service, so whenever it is
153
* possible to allocate a new host you want to do this.
154
*
155
* Returning "false" in all cases generally make sense when a blueprint
156
* has a flexible pool of expensive resources and you want to pack leases
157
* onto them as tightly as possible.
158
*
159
* @param DrydockBlueprint The blueprint for an existing resource being
160
* acquired.
161
* @param DrydockResource The resource being acquired, which we may want to
162
* build a supplemental resource for.
163
* @param DrydockLease The current lease performing acquisition.
164
* @return bool True to prefer allocating a supplemental resource.
165
*
166
* @task lease
167
*/
168
public function shouldAllocateSupplementalResource(
169
DrydockBlueprint $blueprint,
170
DrydockResource $resource,
171
DrydockLease $lease) {
172
return false;
173
}
174
175
/* -( Resource Allocation )------------------------------------------------ */
176
177
178
/**
179
* Enforce fundamental implementation/lease checks. Allows implementations to
180
* reject a lease which no concrete blueprint can ever satisfy.
181
*
182
* For example, if a lease only builds ARM hosts and the lease needs a
183
* PowerPC host, it may be rejected here.
184
*
185
* This is the earliest rejection phase, and followed by
186
* @{method:canEverAllocateResourceForLease}.
187
*
188
* This method should not actually check if a resource can be allocated
189
* right now, or even if a blueprint which can allocate a suitable resource
190
* really exists, only if some blueprint may conceivably exist which could
191
* plausibly be able to build a suitable resource.
192
*
193
* @param DrydockLease Requested lease.
194
* @return bool True if some concrete blueprint of this implementation's
195
* type might ever be able to build a resource for the lease.
196
* @task resource
197
*/
198
abstract public function canAnyBlueprintEverAllocateResourceForLease(
199
DrydockLease $lease);
200
201
202
/**
203
* Enforce basic blueprint/lease checks. Allows blueprints to reject a lease
204
* which they can not build a resource for.
205
*
206
* This is the second rejection phase. It follows
207
* @{method:canAnyBlueprintEverAllocateResourceForLease} and is followed by
208
* @{method:canAllocateResourceForLease}.
209
*
210
* This method should not check if a resource can be built right now, only
211
* if the blueprint as configured may, at some time, be able to build a
212
* suitable resource.
213
*
214
* @param DrydockBlueprint Blueprint which may be asked to allocate a
215
* resource.
216
* @param DrydockLease Requested lease.
217
* @return bool True if this blueprint can eventually build a suitable
218
* resource for the lease, as currently configured.
219
* @task resource
220
*/
221
abstract public function canEverAllocateResourceForLease(
222
DrydockBlueprint $blueprint,
223
DrydockLease $lease);
224
225
226
/**
227
* Enforce basic availability limits. Allows blueprints to reject resource
228
* allocation if they are currently overallocated.
229
*
230
* This method should perform basic capacity/limit checks. For example, if
231
* it has a limit of 6 resources and currently has 6 resources allocated,
232
* it might reject new leases.
233
*
234
* This method should not acquire locks or expect locks to be acquired. This
235
* is a coarse check to determine if the operation is likely to succeed
236
* right now without needing to acquire locks.
237
*
238
* It is expected that this method will sometimes return `true` (indicating
239
* that a resource can be allocated) but find that another allocator has
240
* eaten up free capacity by the time it actually tries to build a resource.
241
* This is normal and the allocator will recover from it.
242
*
243
* @param DrydockBlueprint The blueprint which may be asked to allocate a
244
* resource.
245
* @param DrydockLease Requested lease.
246
* @return bool True if this blueprint appears likely to be able to allocate
247
* a suitable resource.
248
* @task resource
249
*/
250
abstract public function canAllocateResourceForLease(
251
DrydockBlueprint $blueprint,
252
DrydockLease $lease);
253
254
255
/**
256
* Allocate a suitable resource for a lease.
257
*
258
* This method MUST acquire, hold, and manage locks to prevent multiple
259
* allocations from racing. World state is not locked before this method is
260
* called. Blueprints are entirely responsible for any lock handling they
261
* need to perform.
262
*
263
* @param DrydockBlueprint The blueprint which should allocate a resource.
264
* @param DrydockLease Requested lease.
265
* @return DrydockResource Allocated resource.
266
* @task resource
267
*/
268
abstract public function allocateResource(
269
DrydockBlueprint $blueprint,
270
DrydockLease $lease);
271
272
273
/**
274
* @task resource
275
*/
276
public function activateResource(
277
DrydockBlueprint $blueprint,
278
DrydockResource $resource) {
279
throw new PhutilMethodNotImplementedException();
280
}
281
282
283
/**
284
* Destroy any temporary data associated with a resource.
285
*
286
* If a resource creates temporary state when allocated, destroy that state
287
* here. For example, you might shut down a virtual host or destroy a working
288
* copy on disk.
289
*
290
* @param DrydockBlueprint Blueprint which built the resource.
291
* @param DrydockResource Resource being destroyed.
292
* @return void
293
* @task resource
294
*/
295
abstract public function destroyResource(
296
DrydockBlueprint $blueprint,
297
DrydockResource $resource);
298
299
300
/**
301
* Get a human readable name for a resource.
302
*
303
* @param DrydockBlueprint Blueprint which built the resource.
304
* @param DrydockResource Resource to get the name of.
305
* @return string Human-readable resource name.
306
* @task resource
307
*/
308
abstract public function getResourceName(
309
DrydockBlueprint $blueprint,
310
DrydockResource $resource);
311
312
313
/* -( Resource Interfaces )------------------------------------------------ */
314
315
316
abstract public function getInterface(
317
DrydockBlueprint $blueprint,
318
DrydockResource $resource,
319
DrydockLease $lease,
320
$type);
321
322
323
/* -( Logging )------------------------------------------------------------ */
324
325
326
public static function getAllBlueprintImplementations() {
327
return id(new PhutilClassMapQuery())
328
->setAncestorClass(__CLASS__)
329
->execute();
330
}
331
332
333
/**
334
* Get all the @{class:DrydockBlueprintImplementation}s which can possibly
335
* build a resource to satisfy a lease.
336
*
337
* This method returns blueprints which might, at some time, be able to
338
* build a resource which can satisfy the lease. They may not be able to
339
* build that resource right now.
340
*
341
* @param DrydockLease Requested lease.
342
* @return list<DrydockBlueprintImplementation> List of qualifying blueprint
343
* implementations.
344
*/
345
public static function getAllForAllocatingLease(
346
DrydockLease $lease) {
347
348
$impls = self::getAllBlueprintImplementations();
349
350
$keep = array();
351
foreach ($impls as $key => $impl) {
352
// Don't use disabled blueprint types.
353
if (!$impl->isEnabled()) {
354
continue;
355
}
356
357
// Don't use blueprint types which can't allocate the correct kind of
358
// resource.
359
if ($impl->getType() != $lease->getResourceType()) {
360
continue;
361
}
362
363
if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
364
continue;
365
}
366
367
$keep[$key] = $impl;
368
}
369
370
return $keep;
371
}
372
373
public static function getNamedImplementation($class) {
374
return idx(self::getAllBlueprintImplementations(), $class);
375
}
376
377
protected function newResourceTemplate(DrydockBlueprint $blueprint) {
378
379
$resource = id(new DrydockResource())
380
->setBlueprintPHID($blueprint->getPHID())
381
->attachBlueprint($blueprint)
382
->setType($this->getType())
383
->setStatus(DrydockResourceStatus::STATUS_PENDING);
384
385
// Pre-allocate the resource PHID.
386
$resource->setPHID($resource->generatePHID());
387
388
return $resource;
389
}
390
391
protected function newLease(DrydockBlueprint $blueprint) {
392
return DrydockLease::initializeNewLease()
393
->setAuthorizingPHID($blueprint->getPHID());
394
}
395
396
protected function requireActiveLease(DrydockLease $lease) {
397
$lease_status = $lease->getStatus();
398
399
switch ($lease_status) {
400
case DrydockLeaseStatus::STATUS_PENDING:
401
case DrydockLeaseStatus::STATUS_ACQUIRED:
402
throw new PhabricatorWorkerYieldException(15);
403
case DrydockLeaseStatus::STATUS_ACTIVE:
404
return;
405
default:
406
throw new Exception(
407
pht(
408
'Lease ("%s") is in bad state ("%s"), expected "%s".',
409
$lease->getPHID(),
410
$lease_status,
411
DrydockLeaseStatus::STATUS_ACTIVE));
412
}
413
}
414
415
416
/**
417
* Does this implementation use concurrent resource limits?
418
*
419
* Implementations can override this method to opt into standard limit
420
* behavior, which provides a simple concurrent resource limit.
421
*
422
* @return bool True to use limits.
423
*/
424
protected function shouldUseConcurrentResourceLimit() {
425
return false;
426
}
427
428
429
/**
430
* Get the effective concurrent resource limit for this blueprint.
431
*
432
* @param DrydockBlueprint Blueprint to get the limit for.
433
* @return int|null Limit, or `null` for no limit.
434
*/
435
protected function getConcurrentResourceLimit(DrydockBlueprint $blueprint) {
436
if ($this->shouldUseConcurrentResourceLimit()) {
437
$limit = $blueprint->getFieldValue('allocator.limit');
438
$limit = (int)$limit;
439
if ($limit > 0) {
440
return $limit;
441
} else {
442
return null;
443
}
444
}
445
446
return null;
447
}
448
449
450
protected function getConcurrentResourceLimitSlotLock(
451
DrydockBlueprint $blueprint) {
452
453
$limit = $this->getConcurrentResourceLimit($blueprint);
454
if ($limit === null) {
455
return;
456
}
457
458
$blueprint_phid = $blueprint->getPHID();
459
460
// TODO: This logic shouldn't do anything awful, but is a little silly. It
461
// would be nice to unify the "huge limit" and "small limit" cases
462
// eventually but it's a little tricky.
463
464
// If the limit is huge, just pick a random slot. This is just stopping
465
// us from exploding if someone types a billion zillion into the box.
466
if ($limit > 1024) {
467
$slot = mt_rand(0, $limit - 1);
468
return "allocator({$blueprint_phid}).limit({$slot})";
469
}
470
471
// For reasonable limits, actually check for an available slot.
472
$slots = range(0, $limit - 1);
473
shuffle($slots);
474
475
$lock_names = array();
476
foreach ($slots as $slot) {
477
$lock_names[] = "allocator({$blueprint_phid}).limit({$slot})";
478
}
479
480
$locks = DrydockSlotLock::loadHeldLocks($lock_names);
481
$locks = mpull($locks, null, 'getLockKey');
482
483
foreach ($lock_names as $lock_name) {
484
if (empty($locks[$lock_name])) {
485
return $lock_name;
486
}
487
}
488
489
// If we found no free slot, just return whatever we checked last (which
490
// is just a random slot). There's a small chance we'll get lucky and the
491
// lock will be free by the time we try to take it, but usually we'll just
492
// fail to grab the lock, throw an appropriate lock exception, and get back
493
// on the right path to retry later.
494
495
return $lock_name;
496
}
497
498
499
500
/**
501
* Apply standard limits on resource allocation rate.
502
*
503
* @param DrydockBlueprint The blueprint requesting an allocation.
504
* @return bool True if further allocations should be limited.
505
*/
506
protected function shouldLimitAllocatingPoolSize(
507
DrydockBlueprint $blueprint) {
508
509
// Limit on total number of active resources.
510
$total_limit = $this->getConcurrentResourceLimit($blueprint);
511
if ($total_limit === null) {
512
return false;
513
}
514
515
$resource = new DrydockResource();
516
$conn = $resource->establishConnection('r');
517
518
$counts = queryfx_all(
519
$conn,
520
'SELECT status, COUNT(*) N FROM %R
521
WHERE blueprintPHID = %s AND status != %s
522
GROUP BY status',
523
$resource,
524
$blueprint->getPHID(),
525
DrydockResourceStatus::STATUS_DESTROYED);
526
$counts = ipull($counts, 'N', 'status');
527
528
$n_alloc = idx($counts, DrydockResourceStatus::STATUS_PENDING, 0);
529
$n_active = idx($counts, DrydockResourceStatus::STATUS_ACTIVE, 0);
530
$n_broken = idx($counts, DrydockResourceStatus::STATUS_BROKEN, 0);
531
$n_released = idx($counts, DrydockResourceStatus::STATUS_RELEASED, 0);
532
533
// If we're at the limit on total active resources, limit additional
534
// allocations.
535
$n_total = ($n_alloc + $n_active + $n_broken + $n_released);
536
if ($n_total >= $total_limit) {
537
return true;
538
}
539
540
return false;
541
}
542
543
}
544
545