Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/drydock/storage/DrydockLease.php
12256 views
1
<?php
2
3
final class DrydockLease extends DrydockDAO
4
implements
5
PhabricatorPolicyInterface,
6
PhabricatorConduitResultInterface {
7
8
protected $resourcePHID;
9
protected $resourceType;
10
protected $until;
11
protected $ownerPHID;
12
protected $authorizingPHID;
13
protected $attributes = array();
14
protected $status = DrydockLeaseStatus::STATUS_PENDING;
15
protected $acquiredEpoch;
16
protected $activatedEpoch;
17
18
private $resource = self::ATTACHABLE;
19
private $unconsumedCommands = self::ATTACHABLE;
20
21
private $releaseOnDestruction;
22
private $isAcquired = false;
23
private $isActivated = false;
24
private $activateWhenAcquired = false;
25
private $slotLocks = array();
26
27
public static function initializeNewLease() {
28
$lease = new DrydockLease();
29
30
// Pregenerate a PHID so that the caller can set something up to release
31
// this lease before queueing it for activation.
32
$lease->setPHID($lease->generatePHID());
33
34
return $lease;
35
}
36
37
/**
38
* Flag this lease to be released when its destructor is called. This is
39
* mostly useful if you have a script which acquires, uses, and then releases
40
* a lease, as you don't need to explicitly handle exceptions to properly
41
* release the lease.
42
*/
43
public function setReleaseOnDestruction($release) {
44
$this->releaseOnDestruction = $release;
45
return $this;
46
}
47
48
public function __destruct() {
49
if (!$this->releaseOnDestruction) {
50
return;
51
}
52
53
if (!$this->canRelease()) {
54
return;
55
}
56
57
$actor = PhabricatorUser::getOmnipotentUser();
58
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
59
60
$command = DrydockCommand::initializeNewCommand($actor)
61
->setTargetPHID($this->getPHID())
62
->setAuthorPHID($drydock_phid)
63
->setCommand(DrydockCommand::COMMAND_RELEASE)
64
->save();
65
66
$this->scheduleUpdate();
67
}
68
69
public function setStatus($status) {
70
if ($status == DrydockLeaseStatus::STATUS_ACQUIRED) {
71
if (!$this->getAcquiredEpoch()) {
72
$this->setAcquiredEpoch(PhabricatorTime::getNow());
73
}
74
}
75
76
if ($status == DrydockLeaseStatus::STATUS_ACTIVE) {
77
if (!$this->getActivatedEpoch()) {
78
$this->setActivatedEpoch(PhabricatorTime::getNow());
79
}
80
}
81
82
return parent::setStatus($status);
83
}
84
85
public function getLeaseName() {
86
return pht('Lease %d', $this->getID());
87
}
88
89
protected function getConfiguration() {
90
return array(
91
self::CONFIG_AUX_PHID => true,
92
self::CONFIG_SERIALIZATION => array(
93
'attributes' => self::SERIALIZATION_JSON,
94
),
95
self::CONFIG_COLUMN_SCHEMA => array(
96
'status' => 'text32',
97
'until' => 'epoch?',
98
'resourceType' => 'text128',
99
'ownerPHID' => 'phid?',
100
'resourcePHID' => 'phid?',
101
'acquiredEpoch' => 'epoch?',
102
'activatedEpoch' => 'epoch?',
103
),
104
self::CONFIG_KEY_SCHEMA => array(
105
'key_resource' => array(
106
'columns' => array('resourcePHID', 'status'),
107
),
108
'key_status' => array(
109
'columns' => array('status'),
110
),
111
'key_owner' => array(
112
'columns' => array('ownerPHID'),
113
),
114
'key_recent' => array(
115
'columns' => array('resourcePHID', 'dateModified'),
116
),
117
),
118
) + parent::getConfiguration();
119
}
120
121
public function setAttribute($key, $value) {
122
$this->attributes[$key] = $value;
123
return $this;
124
}
125
126
public function getAttribute($key, $default = null) {
127
return idx($this->attributes, $key, $default);
128
}
129
130
public function generatePHID() {
131
return PhabricatorPHID::generateNewPHID(DrydockLeasePHIDType::TYPECONST);
132
}
133
134
public function getInterface($type) {
135
return $this->getResource()->getInterface($this, $type);
136
}
137
138
public function getResource() {
139
return $this->assertAttached($this->resource);
140
}
141
142
public function attachResource(DrydockResource $resource = null) {
143
$this->resource = $resource;
144
return $this;
145
}
146
147
public function hasAttachedResource() {
148
return ($this->resource !== null);
149
}
150
151
public function getUnconsumedCommands() {
152
return $this->assertAttached($this->unconsumedCommands);
153
}
154
155
public function attachUnconsumedCommands(array $commands) {
156
$this->unconsumedCommands = $commands;
157
return $this;
158
}
159
160
public function isReleasing() {
161
foreach ($this->getUnconsumedCommands() as $command) {
162
if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {
163
return true;
164
}
165
}
166
167
return false;
168
}
169
170
public function queueForActivation() {
171
if ($this->getID()) {
172
throw new Exception(
173
pht('Only new leases may be queued for activation!'));
174
}
175
176
if (!$this->getAuthorizingPHID()) {
177
throw new Exception(
178
pht(
179
'Trying to queue a lease for activation without an authorizing '.
180
'object. Use "%s" to specify the PHID of the authorizing object. '.
181
'The authorizing object must be approved to use the allowed '.
182
'blueprints.',
183
'setAuthorizingPHID()'));
184
}
185
186
if (!$this->getAllowedBlueprintPHIDs()) {
187
throw new Exception(
188
pht(
189
'Trying to queue a lease for activation without any allowed '.
190
'Blueprints. Use "%s" to specify allowed blueprints. The '.
191
'authorizing object must be approved to use the allowed blueprints.',
192
'setAllowedBlueprintPHIDs()'));
193
}
194
195
$this
196
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
197
->save();
198
199
$this->scheduleUpdate();
200
201
$this->logEvent(DrydockLeaseQueuedLogType::LOGCONST);
202
203
return $this;
204
}
205
206
public function setActivateWhenAcquired($activate) {
207
$this->activateWhenAcquired = true;
208
return $this;
209
}
210
211
public function needSlotLock($key) {
212
$this->slotLocks[] = $key;
213
return $this;
214
}
215
216
public function acquireOnResource(DrydockResource $resource) {
217
$expect_status = DrydockLeaseStatus::STATUS_PENDING;
218
$actual_status = $this->getStatus();
219
if ($actual_status != $expect_status) {
220
throw new Exception(
221
pht(
222
'Trying to acquire a lease on a resource which is in the wrong '.
223
'state: status must be "%s", actually "%s".',
224
$expect_status,
225
$actual_status));
226
}
227
228
if ($this->activateWhenAcquired) {
229
$new_status = DrydockLeaseStatus::STATUS_ACTIVE;
230
} else {
231
$new_status = DrydockLeaseStatus::STATUS_ACQUIRED;
232
}
233
234
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
235
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
236
throw new Exception(
237
pht(
238
'Trying to acquire an active lease on a pending resource. '.
239
'You can not immediately activate leases on resources which '.
240
'need time to start up.'));
241
}
242
}
243
244
// Before we associate the lease with the resource, we lock the resource
245
// and reload it to make sure it is still pending or active. If we don't
246
// do this, the resource may have just been reclaimed. (Once we acquire
247
// the resource that stops it from being released, so we're nearly safe.)
248
249
$resource_phid = $resource->getPHID();
250
$hash = PhabricatorHash::digestForIndex($resource_phid);
251
$lock_key = 'drydock.resource:'.$hash;
252
$lock = PhabricatorGlobalLock::newLock($lock_key);
253
254
try {
255
$lock->lock(15);
256
} catch (Exception $ex) {
257
throw new DrydockResourceLockException(
258
pht(
259
'Failed to acquire lock for resource ("%s") while trying to '.
260
'acquire lease ("%s").',
261
$resource->getPHID(),
262
$this->getPHID()));
263
}
264
265
$resource->reload();
266
267
if (($resource->getStatus() !== DrydockResourceStatus::STATUS_ACTIVE) &&
268
($resource->getStatus() !== DrydockResourceStatus::STATUS_PENDING)) {
269
throw new DrydockAcquiredBrokenResourceException(
270
pht(
271
'Trying to acquire lease ("%s") on a resource ("%s") in the '.
272
'wrong status ("%s").',
273
$this->getPHID(),
274
$resource->getPHID(),
275
$resource->getStatus()));
276
}
277
278
$caught = null;
279
try {
280
$this->openTransaction();
281
282
try {
283
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
284
$this->slotLocks = array();
285
} catch (DrydockSlotLockException $ex) {
286
$this->killTransaction();
287
288
$this->logEvent(
289
DrydockSlotLockFailureLogType::LOGCONST,
290
array(
291
'locks' => $ex->getLockMap(),
292
));
293
294
throw $ex;
295
}
296
297
$this
298
->setResourcePHID($resource->getPHID())
299
->attachResource($resource)
300
->setStatus($new_status)
301
->save();
302
303
$this->saveTransaction();
304
} catch (Exception $ex) {
305
$caught = $ex;
306
}
307
308
$lock->unlock();
309
310
if ($caught) {
311
throw $caught;
312
}
313
314
$this->isAcquired = true;
315
316
$this->logEvent(DrydockLeaseAcquiredLogType::LOGCONST);
317
318
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
319
$this->didActivate();
320
}
321
322
return $this;
323
}
324
325
public function isAcquiredLease() {
326
return $this->isAcquired;
327
}
328
329
public function activateOnResource(DrydockResource $resource) {
330
$expect_status = DrydockLeaseStatus::STATUS_ACQUIRED;
331
$actual_status = $this->getStatus();
332
if ($actual_status != $expect_status) {
333
throw new Exception(
334
pht(
335
'Trying to activate a lease which has the wrong status: status '.
336
'must be "%s", actually "%s".',
337
$expect_status,
338
$actual_status));
339
}
340
341
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
342
// TODO: Be stricter about this?
343
throw new Exception(
344
pht(
345
'Trying to activate a lease on a pending resource.'));
346
}
347
348
$this->openTransaction();
349
350
try {
351
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
352
$this->slotLocks = array();
353
} catch (DrydockSlotLockException $ex) {
354
$this->killTransaction();
355
356
$this->logEvent(
357
DrydockSlotLockFailureLogType::LOGCONST,
358
array(
359
'locks' => $ex->getLockMap(),
360
));
361
362
throw $ex;
363
}
364
365
$this
366
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
367
->save();
368
369
$this->saveTransaction();
370
371
$this->isActivated = true;
372
373
$this->didActivate();
374
375
return $this;
376
}
377
378
public function isActivatedLease() {
379
return $this->isActivated;
380
}
381
382
public function scheduleUpdate($epoch = null) {
383
PhabricatorWorker::scheduleTask(
384
'DrydockLeaseUpdateWorker',
385
array(
386
'leasePHID' => $this->getPHID(),
387
'isExpireTask' => ($epoch !== null),
388
),
389
array(
390
'objectPHID' => $this->getPHID(),
391
'delayUntil' => ($epoch ? (int)$epoch : null),
392
));
393
}
394
395
public function getAllocatedResourcePHIDs() {
396
return $this->getAttribute('internal.resourcePHIDs.allocated', array());
397
}
398
399
public function setAllocatedResourcePHIDs(array $phids) {
400
return $this->setAttribute('internal.resourcePHIDs.allocated', $phids);
401
}
402
403
public function addAllocatedResourcePHIDs(array $phids) {
404
$allocated_phids = $this->getAllocatedResourcePHIDs();
405
406
foreach ($phids as $phid) {
407
$allocated_phids[$phid] = $phid;
408
}
409
410
return $this->setAllocatedResourcePHIDs($allocated_phids);
411
}
412
413
public function removeAllocatedResourcePHIDs(array $phids) {
414
$allocated_phids = $this->getAllocatedResourcePHIDs();
415
416
foreach ($phids as $phid) {
417
unset($allocated_phids[$phid]);
418
}
419
420
return $this->setAllocatedResourcePHIDs($allocated_phids);
421
}
422
423
public function getReclaimedResourcePHIDs() {
424
return $this->getAttribute('internal.resourcePHIDs.reclaimed', array());
425
}
426
427
public function setReclaimedResourcePHIDs(array $phids) {
428
return $this->setAttribute('internal.resourcePHIDs.reclaimed', $phids);
429
}
430
431
public function addReclaimedResourcePHIDs(array $phids) {
432
$reclaimed_phids = $this->getReclaimedResourcePHIDs();
433
434
foreach ($phids as $phid) {
435
$reclaimed_phids[$phid] = $phid;
436
}
437
438
return $this->setReclaimedResourcePHIDs($reclaimed_phids);
439
}
440
441
public function removeReclaimedResourcePHIDs(array $phids) {
442
$reclaimed_phids = $this->getReclaimedResourcePHIDs();
443
444
foreach ($phids as $phid) {
445
unset($reclaimed_phids[$phid]);
446
}
447
448
return $this->setReclaimedResourcePHIDs($reclaimed_phids);
449
}
450
451
public function setAwakenTaskIDs(array $ids) {
452
$this->setAttribute('internal.awakenTaskIDs', $ids);
453
return $this;
454
}
455
456
public function setAllowedBlueprintPHIDs(array $phids) {
457
$this->setAttribute('internal.blueprintPHIDs', $phids);
458
return $this;
459
}
460
461
public function getAllowedBlueprintPHIDs() {
462
return $this->getAttribute('internal.blueprintPHIDs', array());
463
}
464
465
private function didActivate() {
466
$viewer = PhabricatorUser::getOmnipotentUser();
467
$need_update = false;
468
469
$this->logEvent(DrydockLeaseActivatedLogType::LOGCONST);
470
471
$commands = id(new DrydockCommandQuery())
472
->setViewer($viewer)
473
->withTargetPHIDs(array($this->getPHID()))
474
->withConsumed(false)
475
->execute();
476
if ($commands) {
477
$need_update = true;
478
}
479
480
if ($need_update) {
481
$this->scheduleUpdate();
482
}
483
484
$expires = $this->getUntil();
485
if ($expires) {
486
$this->scheduleUpdate($expires);
487
}
488
489
$this->awakenTasks();
490
}
491
492
public function logEvent($type, array $data = array()) {
493
$log = id(new DrydockLog())
494
->setEpoch(PhabricatorTime::getNow())
495
->setType($type)
496
->setData($data);
497
498
$log->setLeasePHID($this->getPHID());
499
500
$resource_phid = $this->getResourcePHID();
501
if ($resource_phid) {
502
$resource = $this->getResource();
503
504
$log->setResourcePHID($resource->getPHID());
505
$log->setBlueprintPHID($resource->getBlueprintPHID());
506
}
507
508
return $log->save();
509
}
510
511
/**
512
* Awaken yielded tasks after a state change.
513
*
514
* @return this
515
*/
516
public function awakenTasks() {
517
$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');
518
if (is_array($awaken_ids) && $awaken_ids) {
519
PhabricatorWorker::awakenTaskIDs($awaken_ids);
520
}
521
522
return $this;
523
}
524
525
public function getURI() {
526
$id = $this->getID();
527
return "/drydock/lease/{$id}/";
528
}
529
530
public function getDisplayName() {
531
return pht('Drydock Lease %d', $this->getID());
532
}
533
534
535
/* -( Status )------------------------------------------------------------- */
536
537
538
public function getStatusObject() {
539
return DrydockLeaseStatus::newStatusObject($this->getStatus());
540
}
541
542
public function getStatusIcon() {
543
return $this->getStatusObject()->getIcon();
544
}
545
546
public function getStatusColor() {
547
return $this->getStatusObject()->getColor();
548
}
549
550
public function getStatusDisplayName() {
551
return $this->getStatusObject()->getDisplayName();
552
}
553
554
public function isActivating() {
555
return $this->getStatusObject()->isActivating();
556
}
557
558
public function isActive() {
559
return $this->getStatusObject()->isActive();
560
}
561
562
public function canRelease() {
563
if (!$this->getID()) {
564
return false;
565
}
566
567
return $this->getStatusObject()->canRelease();
568
}
569
570
public function canReceiveCommands() {
571
return $this->getStatusObject()->canReceiveCommands();
572
}
573
574
575
/* -( PhabricatorPolicyInterface )----------------------------------------- */
576
577
578
public function getCapabilities() {
579
return array(
580
PhabricatorPolicyCapability::CAN_VIEW,
581
PhabricatorPolicyCapability::CAN_EDIT,
582
);
583
}
584
585
public function getPolicy($capability) {
586
if ($this->getResource()) {
587
return $this->getResource()->getPolicy($capability);
588
}
589
590
// TODO: Implement reasonable policies.
591
592
return PhabricatorPolicies::getMostOpenPolicy();
593
}
594
595
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
596
if ($this->getResource()) {
597
return $this->getResource()->hasAutomaticCapability($capability, $viewer);
598
}
599
return false;
600
}
601
602
public function describeAutomaticCapability($capability) {
603
return pht('Leases inherit policies from the resources they lease.');
604
}
605
606
607
/* -( PhabricatorConduitResultInterface )---------------------------------- */
608
609
610
public function getFieldSpecificationsForConduit() {
611
return array(
612
id(new PhabricatorConduitSearchFieldSpecification())
613
->setKey('resourcePHID')
614
->setType('phid?')
615
->setDescription(pht('PHID of the leased resource, if any.')),
616
id(new PhabricatorConduitSearchFieldSpecification())
617
->setKey('resourceType')
618
->setType('string')
619
->setDescription(pht('Type of resource being leased.')),
620
id(new PhabricatorConduitSearchFieldSpecification())
621
->setKey('until')
622
->setType('int?')
623
->setDescription(pht('Epoch at which this lease expires, if any.')),
624
id(new PhabricatorConduitSearchFieldSpecification())
625
->setKey('ownerPHID')
626
->setType('phid?')
627
->setDescription(pht('The PHID of the object that owns this lease.')),
628
id(new PhabricatorConduitSearchFieldSpecification())
629
->setKey('authorizingPHID')
630
->setType('phid')
631
->setDescription(pht(
632
'The PHID of the object that authorized this lease.')),
633
id(new PhabricatorConduitSearchFieldSpecification())
634
->setKey('status')
635
->setType('map<string, wild>')
636
->setDescription(pht(
637
"The string constant and name of this lease's status.")),
638
);
639
}
640
641
public function getFieldValuesForConduit() {
642
$status = $this->getStatus();
643
644
$until = $this->getUntil();
645
if ($until) {
646
$until = (int)$until;
647
} else {
648
$until = null;
649
}
650
651
return array(
652
'resourcePHID' => $this->getResourcePHID(),
653
'resourceType' => $this->getResourceType(),
654
'until' => $until,
655
'ownerPHID' => $this->getOwnerPHID(),
656
'authorizingPHID' => $this->getAuthorizingPHID(),
657
'status' => array(
658
'value' => $status,
659
'name' => DrydockLeaseStatus::getNameForStatus($status),
660
),
661
);
662
}
663
664
public function getConduitSearchAttachments() {
665
return array();
666
}
667
668
}
669
670