@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement a rough AlmanacService blueprint in Drydock

Summary:
Ref T9253. Broadly, this realigns Allocator behavior to be more consistent and straightforward and amenable to intended future changes.

This attempts to make language more consistent: resources are "allocated" and leases are "acquired".

This prepares for (but does not implement) optimistic "slot locking", as discussed in D10304. Although I suspect some blueprints will need to perform other locking eventually, this does feel like a good fit for most of the locking blueprints need to do.

In particular, I've made the blueprint operations on `$resource` and `$lease` objects more purposeful: they need to invoke an activator on the appropriate object to be implemented correctly. Before they invoke this activator method, they configure the object. In a future diff, this configuration will include specifying slot locks that the lease or resource must acquire. So the API will be something like:

$lease
->setActivateWhenAcquired(true)
->needSlotLock('x')
->needSlotLock('y')
->acquireOnResource($resource);

In the common case where slot locks are a good fit, I think this should make correct blueprint implementation very straightforward.

This prepares for (but does not implement) resources and leases which need significant setup steps. I've basically carved out two modes:

- The "activate immediately" mode, as here, immediately opens the resource or activates the lease. This is appropriate if little or no setup is required. I expect many leases to operate in this mode, although I expect many resources will operate in the other mode.
- The "allocate now, activate later" mode, which is not fully implemented yet. This will queue setup workers when the allocator exits. Overall, this will work very similarly to Harbormaster.
- This new structure makes it acceptable for blueprints to sleep as long as they want during resource allocation and lease acquisition, so long as they are not waiting on anything which needs to be completed by the queue. Putting a `sleep(15 * 60)` in your EC2Blueprint to wait for EC2 to bring a machine up will perform worse than using delayed activation, but won't deadlock the queue or block any locks.

Overall, this flow is more similar to Harbormaster's flow. Having consistency between Harbormaster's model and Drydock's model is good, and I think Harbormaster's model is also simply much better than Drydock's (what exists today in Drydock was implemented a long time ago, and we had more support and infrastructure by the time Harbormaster was implemented, as well as a more clearly defined problem).

The particular strength of Harbormaster is that objects always (or almost always, at least) have a single, clearly defined writer. Ensuring objects have only one writer prevents races and makes reasoning about everything easier.

Drydock does not currently have a clearly defined single writer, but this moves us in that direction. We'll probably need more primitives eventually to flesh this out, like Harbormaster's command queue for messaging objects which you can't write to.

This blueprint was originally implemented in D13843. This makes a few changes to the blueprint itself:

- A bunch of code from that (e.g., interfaces) doesn't exist yet.
- I let the blueprint have multiple services. This simplifies the code a little and seems like it costs us nothing.

This also removes `bin/drydock create-resource`, which no longer makes sense to expose. It won't get locking, leasing, etc., correct, and can not be made correct.

NOTE: This technically works but doesn't do anything useful yet.

Test Plan: Used `bin/drydock lease --type host` to acquire leases against these blueprints.

Reviewers: hach-que, chad

Reviewed By: hach-que, chad

Subscribers: Mnkras

Maniphest Tasks: T9253

Differential Revision: https://secure.phabricator.com/D14117

+684 -401
+2 -2
src/__phutil_library_map__.php
··· 797 797 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 798 798 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', 799 799 'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php', 800 + 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 800 801 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 801 802 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', 802 803 'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php', ··· 845 846 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 846 847 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 847 848 'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php', 848 - 'DrydockManagementCreateResourceWorkflow' => 'applications/drydock/management/DrydockManagementCreateResourceWorkflow.php', 849 849 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 850 850 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 851 851 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', ··· 4502 4502 'DoorkeeperTagView' => 'AphrontView', 4503 4503 'DoorkeeperTagsController' => 'PhabricatorController', 4504 4504 'DrydockAllocatorWorker' => 'PhabricatorWorker', 4505 + 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 4505 4506 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 4506 4507 'DrydockBlueprint' => array( 4507 4508 'DrydockDAO', ··· 4564 4565 'DrydockLogQuery' => 'DrydockQuery', 4565 4566 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 4566 4567 'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow', 4567 - 'DrydockManagementCreateResourceWorkflow' => 'DrydockManagementWorkflow', 4568 4568 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 4569 4569 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 4570 4570 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
+234
src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
··· 1 + <?php 2 + 3 + final class DrydockAlmanacServiceHostBlueprintImplementation 4 + extends DrydockBlueprintImplementation { 5 + 6 + private $services; 7 + private $freeBindings; 8 + 9 + public function isEnabled() { 10 + $almanac_app = 'PhabricatorAlmanacApplication'; 11 + return PhabricatorApplication::isClassInstalled($almanac_app); 12 + } 13 + 14 + public function getBlueprintName() { 15 + return pht('Almanac Hosts'); 16 + } 17 + 18 + public function getDescription() { 19 + return pht( 20 + 'Allows Drydock to lease existing hosts defined in an Almanac service '. 21 + 'pool.'); 22 + } 23 + 24 + public function canAnyBlueprintEverAllocateResourceForLease( 25 + DrydockLease $lease) { 26 + return true; 27 + } 28 + 29 + public function canEverAllocateResourceForLease( 30 + DrydockBlueprint $blueprint, 31 + DrydockLease $lease) { 32 + $services = $this->loadServices($blueprint); 33 + $bindings = $this->loadAllBindings($services); 34 + 35 + if (!$bindings) { 36 + // If there are no devices bound to the services for this blueprint, 37 + // we can not allocate resources. 38 + return false; 39 + } 40 + 41 + return true; 42 + } 43 + 44 + public function canAllocateResourceForLease( 45 + DrydockBlueprint $blueprint, 46 + DrydockLease $lease) { 47 + 48 + // We will only allocate one resource per unique device bound to the 49 + // services for this blueprint. Make sure we have a free device somewhere. 50 + $free_bindings = $this->loadFreeBindings($blueprint); 51 + if (!$free_bindings) { 52 + return false; 53 + } 54 + 55 + return true; 56 + } 57 + 58 + public function allocateResource( 59 + DrydockBlueprint $blueprint, 60 + DrydockLease $lease) { 61 + 62 + $free_bindings = $this->loadFreeBindings($blueprint); 63 + shuffle($free_bindings); 64 + 65 + $exceptions = array(); 66 + foreach ($free_bindings as $binding) { 67 + $device = $binding->getDevice(); 68 + $device_name = $device->getName(); 69 + 70 + $resource = $this->newResourceTemplate($blueprint, $device_name) 71 + ->setActivateWhenAllocated(true) 72 + ->setAttribute('almanacServicePHID', $binding->getServicePHID()) 73 + ->setAttribute('almanacBindingPHID', $binding->getPHID()); 74 + 75 + // TODO: This algorithm can race, and the "free" binding may not be 76 + // free by the time we acquire it. Do slot-locking here if that works 77 + // out, or some other kind of locking if it does not. 78 + 79 + try { 80 + return $resource->allocateResource(DrydockResourceStatus::STATUS_OPEN); 81 + } catch (Exception $ex) { 82 + $exceptions[] = $ex; 83 + } 84 + } 85 + 86 + throw new PhutilAggregateException( 87 + pht('Unable to allocate any binding as a resource.'), 88 + $exceptions); 89 + } 90 + 91 + public function canAcquireLeaseOnResource( 92 + DrydockBlueprint $blueprint, 93 + DrydockResource $resource, 94 + DrydockLease $lease) { 95 + 96 + // TODO: We'll currently lease each resource an unlimited number of times, 97 + // but should stop doing that. 98 + 99 + return true; 100 + } 101 + 102 + public function acquireLease( 103 + DrydockBlueprint $blueprint, 104 + DrydockResource $resource, 105 + DrydockLease $lease) { 106 + 107 + // TODO: Once we have limit rules, we should perform slot locking (or other 108 + // kinds of locking) here. 109 + 110 + $lease 111 + ->setActivateWhenAcquired(true) 112 + ->acquireOnResource($resource); 113 + } 114 + 115 + public function getType() { 116 + return 'host'; 117 + } 118 + 119 + public function getInterface( 120 + DrydockResource $resource, 121 + DrydockLease $lease, 122 + $type) { 123 + // TODO: Actually do stuff here, this needs work and currently makes this 124 + // entire exercise pointless. 125 + } 126 + 127 + public function getFieldSpecifications() { 128 + return array( 129 + 'almanacServicePHIDs' => array( 130 + 'name' => pht('Almanac Services'), 131 + 'type' => 'datasource', 132 + 'datasource.class' => 'AlmanacServiceDatasource', 133 + 'datasource.parameters' => array( 134 + 'serviceClasses' => $this->getAlmanacServiceClasses(), 135 + ), 136 + 'required' => true, 137 + ), 138 + 'credentialPHID' => array( 139 + 'name' => pht('Credentials'), 140 + 'type' => 'credential', 141 + 'credential.provides' => 142 + PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE, 143 + 'credential.type' => 144 + PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE, 145 + ), 146 + ) + parent::getFieldSpecifications(); 147 + } 148 + 149 + private function loadServices(DrydockBlueprint $blueprint) { 150 + if (!$this->services) { 151 + $service_phids = $blueprint->getFieldValue('almanacServicePHIDs'); 152 + if (!$service_phids) { 153 + throw new Exception( 154 + pht( 155 + 'This blueprint ("%s") does not define any Almanac Service PHIDs.', 156 + $blueprint->getBlueprintName())); 157 + } 158 + 159 + $viewer = PhabricatorUser::getOmnipotentUser(); 160 + $services = id(new AlmanacServiceQuery()) 161 + ->setViewer($viewer) 162 + ->withPHIDs($service_phids) 163 + ->withServiceClasses($this->getAlmanacServiceClasses()) 164 + ->needBindings(true) 165 + ->execute(); 166 + $services = mpull($services, null, 'getPHID'); 167 + 168 + if (count($services) != count($service_phids)) { 169 + $missing_phids = array_diff($service_phids, array_keys($services)); 170 + throw new Exception( 171 + pht( 172 + 'Some of the Almanac Services defined by this blueprint '. 173 + 'could not be loaded. They may be invalid, no longer exist, '. 174 + 'or be of the wrong type: %s.', 175 + implode(', ', $missing_phids))); 176 + } 177 + 178 + $this->services = $services; 179 + } 180 + 181 + return $this->services; 182 + } 183 + 184 + private function loadAllBindings(array $services) { 185 + assert_instances_of($services, 'AlmanacService'); 186 + $bindings = array_mergev(mpull($services, 'getBindings')); 187 + return mpull($bindings, null, 'getPHID'); 188 + } 189 + 190 + private function loadFreeBindings(DrydockBlueprint $blueprint) { 191 + if ($this->freeBindings === null) { 192 + $viewer = PhabricatorUser::getOmnipotentUser(); 193 + 194 + $pool = id(new DrydockResourceQuery()) 195 + ->setViewer($viewer) 196 + ->withBlueprintPHIDs(array($blueprint->getPHID())) 197 + ->withStatuses( 198 + array( 199 + DrydockResourceStatus::STATUS_PENDING, 200 + DrydockResourceStatus::STATUS_OPEN, 201 + DrydockResourceStatus::STATUS_CLOSED, 202 + )) 203 + ->execute(); 204 + 205 + $allocated_phids = array(); 206 + foreach ($pool as $resource) { 207 + $allocated_phids[] = $resource->getAttribute('almanacDevicePHID'); 208 + } 209 + $allocated_phids = array_fuse($allocated_phids); 210 + 211 + $services = $this->loadServices($blueprint); 212 + $bindings = $this->loadAllBindings($services); 213 + 214 + $free = array(); 215 + foreach ($bindings as $binding) { 216 + if (empty($allocated_phids[$binding->getPHID()])) { 217 + $free[] = $binding; 218 + } 219 + } 220 + 221 + $this->freeBindings = $free; 222 + } 223 + 224 + return $this->freeBindings; 225 + } 226 + 227 + private function getAlmanacServiceClasses() { 228 + return array( 229 + 'AlmanacDrydockPoolServiceType', 230 + ); 231 + } 232 + 233 + 234 + }
+19 -218
src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
··· 60 60 return array(); 61 61 } 62 62 63 - public function getDetail($key, $default = null) { 64 - return $this->getInstance()->getDetail($key, $default); 65 - } 66 - 67 63 68 64 /* -( Lease Acquisition )-------------------------------------------------- */ 69 65 ··· 86 82 * @return bool True if the resource and lease are compatible. 87 83 * @task lease 88 84 */ 89 - abstract public function canAllocateLeaseOnResource( 85 + abstract public function canAcquireLeaseOnResource( 90 86 DrydockBlueprint $blueprint, 91 87 DrydockResource $resource, 92 88 DrydockLease $lease); 93 89 94 90 95 91 /** 96 - * @task lease 97 - */ 98 - final public function allocateLease( 99 - DrydockResource $resource, 100 - DrydockLease $lease) { 101 - 102 - $scope = $this->pushActiveScope($resource, $lease); 103 - 104 - $this->log(pht('Trying to Allocate Lease')); 105 - 106 - $lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING); 107 - $lease->setResourceID($resource->getID()); 108 - $lease->attachResource($resource); 109 - 110 - $ephemeral_lease = id(clone $lease)->makeEphemeral(); 111 - 112 - $allocated = false; 113 - $allocation_exception = null; 114 - 115 - $resource->openTransaction(); 116 - $resource->beginReadLocking(); 117 - $resource->reload(); 118 - 119 - // TODO: Policy stuff. 120 - $other_leases = id(new DrydockLease())->loadAllWhere( 121 - 'status IN (%Ld) AND resourceID = %d', 122 - array( 123 - DrydockLeaseStatus::STATUS_ACQUIRING, 124 - DrydockLeaseStatus::STATUS_ACTIVE, 125 - ), 126 - $resource->getID()); 127 - 128 - try { 129 - $allocated = $this->shouldAllocateLease( 130 - $resource, 131 - $ephemeral_lease, 132 - $other_leases); 133 - } catch (Exception $ex) { 134 - $allocation_exception = $ex; 135 - } 136 - 137 - if ($allocated) { 138 - $lease->save(); 139 - } 140 - $resource->endReadLocking(); 141 - if ($allocated) { 142 - $resource->saveTransaction(); 143 - $this->log(pht('Allocated Lease')); 144 - } else { 145 - $resource->killTransaction(); 146 - $this->log(pht('Failed to Allocate Lease')); 147 - } 148 - 149 - if ($allocation_exception) { 150 - $this->logException($allocation_exception); 151 - } 152 - 153 - return $allocated; 154 - } 155 - 156 - 157 - /** 158 - * Enforce lease limits on resources. Allows resources to reject leases if 159 - * they would become over-allocated by accepting them. 92 + * Acquire a lease. Allows resources to peform setup as leases are brought 93 + * online. 160 94 * 161 - * For example, if a resource represents disk space, this method might check 162 - * how much space the lease is asking for (say, 200MB) and how much space is 163 - * left unallocated on the resource. It could grant the lease (return true) 164 - * if it has enough remaining space (more than 200MB), and reject the lease 165 - * (return false) if it does not (less than 200MB). 95 + * If acquisition fails, throw an exception. 166 96 * 167 - * A resource might also allow only exclusive leases. In this case it could 168 - * accept a new lease (return true) if there are no active leases, or reject 169 - * the new lease (return false) if there any other leases. 170 - * 171 - * A lock is held on the resource while this method executes to prevent 172 - * multiple processes from allocating leases on the resource simultaneously. 173 - * However, this means you should implement the method as cheaply as possible. 174 - * In particular, do not perform any actual acquisition or setup in this 175 - * method. 176 - * 177 - * If allocation is permitted, the lease will be moved to `ACQUIRING` status 178 - * and @{method:executeAcquireLease} will be called to actually perform 179 - * acquisition. 180 - * 181 - * General compatibility checks unrelated to resource limits and capacity are 182 - * better implemented in @{method:canAllocateLease}, which serves as a 183 - * cheap filter before lock acquisition. 184 - * 185 - * @param DrydockResource Candidate resource to allocate the lease on. 186 - * @param DrydockLease Pending lease that wants to allocate here. 187 - * @param list<DrydockLease> Other allocated and acquired leases on the 188 - * resource. The implementation can inspect them 189 - * to verify it can safely add the new lease. 190 - * @return bool True to allocate the lease on the resource; 191 - * false to reject it. 97 + * @param DrydockBlueprint Blueprint which built the resource. 98 + * @param DrydockResource Resource to acquire a lease on. 99 + * @param DrydockLease Requested lease. 100 + * @return void 192 101 * @task lease 193 102 */ 194 - abstract protected function shouldAllocateLease( 195 - DrydockResource $resource, 196 - DrydockLease $lease, 197 - array $other_leases); 198 - 199 - 200 - /** 201 - * @task lease 202 - */ 203 - final public function acquireLease( 204 - DrydockResource $resource, 205 - DrydockLease $lease) { 206 - 207 - $scope = $this->pushActiveScope($resource, $lease); 208 - 209 - $this->log(pht('Acquiring Lease')); 210 - $lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE); 211 - $lease->setResourceID($resource->getID()); 212 - $lease->attachResource($resource); 213 - 214 - $ephemeral_lease = id(clone $lease)->makeEphemeral(); 215 - 216 - try { 217 - $this->executeAcquireLease($resource, $ephemeral_lease); 218 - } catch (Exception $ex) { 219 - $this->logException($ex); 220 - throw $ex; 221 - } 222 - 223 - $lease->setAttributes($ephemeral_lease->getAttributes()); 224 - $lease->save(); 225 - $this->log(pht('Acquired Lease')); 226 - } 227 - 228 - 229 - /** 230 - * Acquire and activate an allocated lease. Allows resources to peform setup 231 - * as leases are brought online. 232 - * 233 - * Following a successful call to @{method:canAllocateLease}, a lease is moved 234 - * to `ACQUIRING` status and this method is called after resource locks are 235 - * released. Nothing is locked while this method executes; the implementation 236 - * is free to perform expensive operations like writing files and directories, 237 - * executing commands, etc. 238 - * 239 - * After this method executes, the lease status is moved to `ACTIVE` and the 240 - * original leasee may access it. 241 - * 242 - * If acquisition fails, throw an exception. 243 - * 244 - * @param DrydockResource Resource to acquire a lease on. 245 - * @param DrydockLease Lease to acquire. 246 - * @return void 247 - */ 248 - abstract protected function executeAcquireLease( 103 + abstract public function acquireLease( 104 + DrydockBlueprint $blueprint, 249 105 DrydockResource $resource, 250 106 DrydockLease $lease); 251 - 252 - 253 107 254 108 final public function releaseLease( 255 109 DrydockResource $resource, ··· 352 206 * @param DrydockLease Requested lease. 353 207 * @return bool True if this blueprint appears likely to be able to allocate 354 208 * a suitable resource. 209 + * @task resource 355 210 */ 356 211 abstract public function canAllocateResourceForLease( 357 212 DrydockBlueprint $blueprint, ··· 369 224 * @param DrydockBlueprint The blueprint which should allocate a resource. 370 225 * @param DrydockLease Requested lease. 371 226 * @return DrydockResource Allocated resource. 227 + * @task resource 372 228 */ 373 - abstract protected function executeAllocateResource( 229 + abstract public function allocateResource( 374 230 DrydockBlueprint $blueprint, 375 231 DrydockLease $lease); 376 232 377 - final public function allocateResource( 378 - DrydockBlueprint $blueprint, 379 - DrydockLease $lease) { 380 - 381 - $scope = $this->pushActiveScope(null, $lease); 382 - 383 - $this->log( 384 - pht( 385 - "Blueprint '%s': Allocating Resource for '%s'", 386 - $this->getBlueprintClass(), 387 - $lease->getLeaseName())); 388 - 389 - try { 390 - $resource = $this->executeAllocateResource($blueprint, $lease); 391 - $this->validateAllocatedResource($resource); 392 - } catch (Exception $ex) { 393 - $this->logException($ex); 394 - throw $ex; 395 - } 396 - 397 - return $resource; 398 - } 399 - 400 233 401 234 /* -( Logging )------------------------------------------------------------ */ 402 235 ··· 454 287 return idx(self::getAllBlueprintImplementations(), $class); 455 288 } 456 289 457 - protected function newResourceTemplate($name) { 290 + protected function newResourceTemplate( 291 + DrydockBlueprint $blueprint, 292 + $name) { 293 + 458 294 $resource = id(new DrydockResource()) 459 - ->setBlueprintPHID($this->getInstance()->getPHID()) 460 - ->setBlueprintClass($this->getBlueprintClass()) 295 + ->setBlueprintPHID($blueprint->getPHID()) 461 296 ->setType($this->getType()) 462 297 ->setStatus(DrydockResourceStatus::STATUS_PENDING) 463 - ->setName($name) 464 - ->save(); 298 + ->setName($name); 465 299 466 300 $this->activeResource = $resource; 467 301 ··· 471 305 $this->getBlueprintClass())); 472 306 473 307 return $resource; 474 - } 475 - 476 - /** 477 - * Sanity checks that the blueprint is implemented properly. 478 - */ 479 - private function validateAllocatedResource($resource) { 480 - $blueprint = $this->getBlueprintClass(); 481 - 482 - if (!($resource instanceof DrydockResource)) { 483 - throw new Exception( 484 - pht( 485 - "Blueprint '%s' is not properly implemented: %s must return an ". 486 - "object of type %s or throw, but returned something else.", 487 - $blueprint, 488 - 'executeAllocateResource()', 489 - 'DrydockResource')); 490 - } 491 - 492 - $current_status = $resource->getStatus(); 493 - $req_status = DrydockResourceStatus::STATUS_OPEN; 494 - if ($current_status != $req_status) { 495 - $current_name = DrydockResourceStatus::getNameForStatus($current_status); 496 - $req_name = DrydockResourceStatus::getNameForStatus($req_status); 497 - throw new Exception( 498 - pht( 499 - "Blueprint '%s' is not properly implemented: %s must return a %s ". 500 - "with status '%s', but returned one with status '%s'.", 501 - $blueprint, 502 - 'executeAllocateResource()', 503 - 'DrydockResource', 504 - $req_name, 505 - $current_name)); 506 - } 507 308 } 508 309 509 310 private function pushActiveScope(
+5 -11
src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
··· 35 35 return true; 36 36 } 37 37 38 - public function canAllocateLeaseOnResource( 38 + public function canAcquireLeaseOnResource( 39 39 DrydockBlueprint $blueprint, 40 40 DrydockResource $resource, 41 41 DrydockLease $lease) { ··· 47 47 return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo)); 48 48 } 49 49 50 - protected function shouldAllocateLease( 51 - DrydockResource $resource, 52 - DrydockLease $lease, 53 - array $other_leases) { 54 - // TODO: These checks are out of date. 55 - return !$other_leases; 56 - } 57 - 58 - protected function executeAllocateResource( 50 + public function allocateResource( 59 51 DrydockBlueprint $blueprint, 60 52 DrydockLease $lease) { 61 53 ··· 105 97 $this->log(pht('Complete.')); 106 98 107 99 $resource = $this->newResourceTemplate( 100 + $blueprint, 108 101 pht( 109 102 'Working Copy (%s)', 110 103 $repository->getCallsign())); ··· 117 110 return $resource; 118 111 } 119 112 120 - protected function executeAcquireLease( 113 + public function acquireLease( 114 + DrydockBlueprint $blueprint, 121 115 DrydockResource $resource, 122 116 DrydockLease $lease) { 123 117 return;
+3 -3
src/applications/drydock/constants/DrydockLeaseStatus.php
··· 3 3 final class DrydockLeaseStatus extends DrydockConstants { 4 4 5 5 const STATUS_PENDING = 0; 6 - const STATUS_ACQUIRING = 5; 6 + const STATUS_ACQUIRED = 5; 7 7 const STATUS_ACTIVE = 1; 8 8 const STATUS_RELEASED = 2; 9 9 const STATUS_BROKEN = 3; ··· 12 12 public static function getNameForStatus($status) { 13 13 $map = array( 14 14 self::STATUS_PENDING => pht('Pending'), 15 - self::STATUS_ACQUIRING => pht('Acquiring'), 15 + self::STATUS_ACQUIRED => pht('Acquired'), 16 16 self::STATUS_ACTIVE => pht('Active'), 17 17 self::STATUS_RELEASED => pht('Released'), 18 18 self::STATUS_BROKEN => pht('Broken'), ··· 25 25 public static function getAllStatuses() { 26 26 return array( 27 27 self::STATUS_PENDING, 28 - self::STATUS_ACQUIRING, 28 + self::STATUS_ACQUIRED, 29 29 self::STATUS_ACTIVE, 30 30 self::STATUS_RELEASED, 31 31 self::STATUS_BROKEN,
+4
src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php
··· 40 40 return; 41 41 } 42 42 43 + public function getBlueprintFieldValue() { 44 + return $this->getProxy()->getFieldValue(); 45 + } 46 + 43 47 }
+5 -1
src/applications/drydock/customfield/DrydockBlueprintCustomField.php
··· 1 1 <?php 2 2 3 3 abstract class DrydockBlueprintCustomField 4 - extends PhabricatorCustomField {} 4 + extends PhabricatorCustomField { 5 + 6 + abstract public function getBlueprintFieldValue(); 7 + 8 + }
-81
src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php
··· 1 - <?php 2 - 3 - final class DrydockManagementCreateResourceWorkflow 4 - extends DrydockManagementWorkflow { 5 - 6 - protected function didConstruct() { 7 - $this 8 - ->setName('create-resource') 9 - ->setSynopsis(pht('Create a resource manually.')) 10 - ->setArguments( 11 - array( 12 - array( 13 - 'name' => 'name', 14 - 'param' => 'resource_name', 15 - 'help' => pht('Resource name.'), 16 - ), 17 - array( 18 - 'name' => 'blueprint', 19 - 'param' => 'blueprint_id', 20 - 'help' => pht('Blueprint ID.'), 21 - ), 22 - array( 23 - 'name' => 'attributes', 24 - 'param' => 'name=value,...', 25 - 'help' => pht('Resource attributes.'), 26 - ), 27 - )); 28 - } 29 - 30 - public function execute(PhutilArgumentParser $args) { 31 - $console = PhutilConsole::getConsole(); 32 - 33 - $resource_name = $args->getArg('name'); 34 - if (!$resource_name) { 35 - throw new PhutilArgumentUsageException( 36 - pht( 37 - 'Specify a resource name with `%s`.', 38 - '--name')); 39 - } 40 - 41 - $blueprint_id = $args->getArg('blueprint'); 42 - if (!$blueprint_id) { 43 - throw new PhutilArgumentUsageException( 44 - pht( 45 - 'Specify a blueprint ID with `%s`.', 46 - '--blueprint')); 47 - } 48 - 49 - $attributes = $args->getArg('attributes'); 50 - if ($attributes) { 51 - $options = new PhutilSimpleOptions(); 52 - $options->setCaseSensitive(true); 53 - $attributes = $options->parse($attributes); 54 - } 55 - 56 - $viewer = $this->getViewer(); 57 - 58 - $blueprint = id(new DrydockBlueprintQuery()) 59 - ->setViewer($viewer) 60 - ->withIDs(array($blueprint_id)) 61 - ->executeOne(); 62 - if (!$blueprint) { 63 - throw new PhutilArgumentUsageException( 64 - pht('Specified blueprint does not exist.')); 65 - } 66 - 67 - $resource = id(new DrydockResource()) 68 - ->setBlueprintPHID($blueprint->getPHID()) 69 - ->setType($blueprint->getImplementation()->getType()) 70 - ->setName($resource_name) 71 - ->setStatus(DrydockResourceStatus::STATUS_OPEN); 72 - if ($attributes) { 73 - $resource->setAttributes($attributes); 74 - } 75 - $resource->save(); 76 - 77 - $console->writeOut("%s\n", pht('Created Resource %s', $resource->getID())); 78 - return 0; 79 - } 80 - 81 - }
+1 -1
src/applications/drydock/query/DrydockLeaseSearchEngine.php
··· 74 74 'statuses', 75 75 array( 76 76 DrydockLeaseStatus::STATUS_PENDING, 77 - DrydockLeaseStatus::STATUS_ACQUIRING, 77 + DrydockLeaseStatus::STATUS_ACQUIRED, 78 78 DrydockLeaseStatus::STATUS_ACTIVE, 79 79 )); 80 80 case 'all':
+76 -2
src/applications/drydock/storage/DrydockBlueprint.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task resource Allocating Resources 5 + * @task lease Acquiring Leases 6 + */ 3 7 final class DrydockBlueprint extends DrydockDAO 4 8 implements 5 9 PhabricatorApplicationTransactionInterface, ··· 14 18 15 19 private $implementation = self::ATTACHABLE; 16 20 private $customFields = self::ATTACHABLE; 21 + private $fields = null; 17 22 18 23 public static function initializeNewBlueprint(PhabricatorUser $actor) { 19 24 $app = id(new PhabricatorApplicationQuery()) ··· 68 73 return $this; 69 74 } 70 75 76 + public function getFieldValue($key) { 77 + $key = "std:drydock:core:{$key}"; 78 + $fields = $this->loadCustomFields(); 79 + 80 + $field = idx($fields, $key); 81 + if (!$field) { 82 + throw new Exception( 83 + pht( 84 + 'Unknown blueprint field "%s"!', 85 + $key)); 86 + } 87 + 88 + return $field->getBlueprintFieldValue(); 89 + } 90 + 91 + private function loadCustomFields() { 92 + if ($this->fields === null) { 93 + $field_list = PhabricatorCustomField::getObjectFields( 94 + $this, 95 + PhabricatorCustomField::ROLE_VIEW); 96 + $field_list->readFieldsFromStorage($this); 97 + 98 + $this->fields = $field_list->getFields(); 99 + } 100 + return $this->fields; 101 + } 102 + 103 + 104 + /* -( Allocating Resources )----------------------------------------------- */ 105 + 106 + 107 + /** 108 + * @task resource 109 + */ 71 110 public function canEverAllocateResourceForLease(DrydockLease $lease) { 72 111 return $this->getImplementation()->canEverAllocateResourceForLease( 73 112 $this, 74 113 $lease); 75 114 } 76 115 116 + 117 + /** 118 + * @task resource 119 + */ 77 120 public function canAllocateResourceForLease(DrydockLease $lease) { 78 121 return $this->getImplementation()->canAllocateResourceForLease( 79 122 $this, 80 123 $lease); 81 124 } 82 125 83 - public function canAllocateLeaseOnResource( 126 + 127 + /** 128 + * @task resource 129 + */ 130 + public function allocateResource(DrydockLease $lease) { 131 + return $this->getImplementation()->allocateResource( 132 + $this, 133 + $lease); 134 + } 135 + 136 + 137 + /* -( Acquiring Leases )--------------------------------------------------- */ 138 + 139 + 140 + /** 141 + * @task lease 142 + */ 143 + public function canAcquireLeaseOnResource( 144 + DrydockResource $resource, 145 + DrydockLease $lease) { 146 + return $this->getImplementation()->canAcquireLeaseOnResource( 147 + $this, 148 + $resource, 149 + $lease); 150 + } 151 + 152 + 153 + /** 154 + * @task lease 155 + */ 156 + public function acquireLease( 84 157 DrydockResource $resource, 85 158 DrydockLease $lease) { 86 - return $this->getImplementation()->canAllocateLeaseOnResource( 159 + return $this->getImplementation()->acquireLease( 87 160 $this, 88 161 $resource, 89 162 $lease); 90 163 } 164 + 91 165 92 166 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 93 167
+51 -2
src/applications/drydock/storage/DrydockLease.php
··· 13 13 14 14 private $resource = self::ATTACHABLE; 15 15 private $releaseOnDestruction; 16 + private $isAcquired = false; 17 + private $activateWhenAcquired = false; 16 18 17 19 /** 18 20 * Flag this lease to be released when its destructor is called. This is ··· 133 135 134 136 public function isActive() { 135 137 switch ($this->status) { 138 + case DrydockLeaseStatus::STATUS_ACQUIRED: 136 139 case DrydockLeaseStatus::STATUS_ACTIVE: 137 - case DrydockLeaseStatus::STATUS_ACQUIRING: 138 140 return true; 139 141 } 140 142 return false; ··· 171 173 case DrydockLeaseStatus::STATUS_BROKEN: 172 174 throw new Exception(pht('Lease has been broken!')); 173 175 case DrydockLeaseStatus::STATUS_PENDING: 174 - case DrydockLeaseStatus::STATUS_ACQUIRING: 176 + case DrydockLeaseStatus::STATUS_ACQUIRED: 175 177 break; 176 178 default: 177 179 throw new Exception(pht('Unknown status??')); ··· 197 199 198 200 self::waitForLeases(array($this)); 199 201 return $this; 202 + } 203 + 204 + public function setActivateWhenAcquired($activate) { 205 + $this->activateWhenAcquired = true; 206 + return $this; 207 + } 208 + 209 + public function acquireOnResource(DrydockResource $resource) { 210 + $expect_status = DrydockLeaseStatus::STATUS_PENDING; 211 + $actual_status = $this->getStatus(); 212 + if ($actual_status != $expect_status) { 213 + throw new Exception( 214 + pht( 215 + 'Trying to acquire a lease on a resource which is in the wrong '. 216 + 'state: status must be "%s", actually "%s".', 217 + $expect_status, 218 + $actual_status)); 219 + } 220 + 221 + if ($this->activateWhenAcquired) { 222 + $new_status = DrydockLeaseStatus::STATUS_ACTIVE; 223 + } else { 224 + $new_status = DrydockLeaseStatus::STATUS_PENDING; 225 + } 226 + 227 + if ($new_status === DrydockLeaseStatus::STATUS_ACTIVE) { 228 + if ($resource->getStatus() === DrydockResourceStatus::STATUS_PENDING) { 229 + throw new Exception( 230 + pht( 231 + 'Trying to acquire an active lease on a pending resource. '. 232 + 'You can not immediately activate leases on resources which '. 233 + 'need time to start up.')); 234 + } 235 + } 236 + 237 + $this 238 + ->setResourceID($resource->getID()) 239 + ->setStatus($new_status) 240 + ->save(); 241 + 242 + $this->isAcquired = true; 243 + 244 + return $this; 245 + } 246 + 247 + public function isAcquiredLease() { 248 + return $this->isAcquired; 200 249 } 201 250 202 251
+43 -4
src/applications/drydock/storage/DrydockResource.php
··· 15 15 protected $ownerPHID; 16 16 17 17 private $blueprint = self::ATTACHABLE; 18 + private $isAllocated = false; 19 + private $activateWhenAllocated = false; 18 20 19 21 protected function getConfiguration() { 20 22 return array( ··· 73 75 return $this; 74 76 } 75 77 76 - public function canAllocateLease(DrydockLease $lease) { 77 - return $this->getBlueprint()->canAllocateLeaseOnResource( 78 - $this, 79 - $lease); 78 + public function setActivateWhenAllocated($activate) { 79 + $this->activateWhenAllocated = $activate; 80 + return $this; 81 + } 82 + 83 + public function allocateResource($status) { 84 + if ($this->getID()) { 85 + throw new Exception( 86 + pht( 87 + 'Trying to allocate a resource which has already been persisted. '. 88 + 'Only new resources may be allocated.')); 89 + } 90 + 91 + $expect_status = DrydockResourceStatus::STATUS_PENDING; 92 + $actual_status = $this->getStatus(); 93 + if ($actual_status != $expect_status) { 94 + throw new Exception( 95 + pht( 96 + 'Trying to allocate a resource from the wrong status. Status must '. 97 + 'be "%s", actually "%s".', 98 + $expect_status, 99 + $actual_status)); 100 + } 101 + 102 + if ($this->activateWhenAllocated) { 103 + $new_status = DrydockResourceStatus::STATUS_OPEN; 104 + } else { 105 + $new_status = DrydockResourceStatus::STATUS_PENDING; 106 + } 107 + 108 + $this 109 + ->setStatus($new_status) 110 + ->save(); 111 + 112 + $this->didAllocate = true; 113 + 114 + return $this; 115 + } 116 + 117 + public function isAllocatedResource() { 118 + return $this->isAllocated; 80 119 } 81 120 82 121 public function closeResource() {
+240 -75
src/applications/drydock/worker/DrydockAllocatorWorker.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task allocate Allocator 5 + * @task resource Managing Resources 6 + * @task lease Managing Leases 7 + */ 3 8 final class DrydockAllocatorWorker extends PhabricatorWorker { 4 9 5 10 private function getViewer() { ··· 27 32 28 33 protected function doWork() { 29 34 $lease = $this->loadLease(); 30 - $this->allocateLease($lease); 35 + $this->allocateAndAcquireLease($lease); 31 36 } 32 37 33 - private function allocateLease(DrydockLease $lease) { 38 + 39 + /* -( Allocator )---------------------------------------------------------- */ 40 + 41 + 42 + /** 43 + * Find or build a resource which can satisfy a given lease request, then 44 + * acquire the lease. 45 + * 46 + * @param DrydockLease Requested lease. 47 + * @return void 48 + * @task allocator 49 + */ 50 + private function allocateAndAcquireLease(DrydockLease $lease) { 34 51 $blueprints = $this->loadBlueprintsForAllocatingLease($lease); 35 52 36 53 // If we get nothing back, that means no blueprint is defined which can ··· 72 89 $exceptions = array(); 73 90 foreach ($usable_blueprints as $blueprint) { 74 91 try { 75 - $resources[] = $blueprint->allocateResource($lease); 92 + $resources[] = $this->allocateResource($blueprint, $lease); 93 + 76 94 // Bail after allocating one resource, we don't need any more than 77 95 // this. 78 96 break; ··· 106 124 $allocated = false; 107 125 foreach ($resources as $resource) { 108 126 try { 109 - $blueprint->allocateLease($resource, $lease); 127 + $this->acquireLease($resource, $lease); 110 128 $allocated = true; 111 129 break; 112 130 } catch (Exception $ex) { ··· 129 147 130 148 131 149 /** 150 + * Get all the @{class:DrydockBlueprintImplementation}s which can possibly 151 + * build a resource to satisfy a lease. 152 + * 153 + * This method returns blueprints which might, at some time, be able to 154 + * build a resource which can satisfy the lease. They may not be able to 155 + * build that resource right now. 156 + * 157 + * @param DrydockLease Requested lease. 158 + * @return list<DrydockBlueprintImplementation> List of qualifying blueprint 159 + * implementations. 160 + * @task allocator 161 + */ 162 + private function loadBlueprintImplementationsForAllocatingLease( 163 + DrydockLease $lease) { 164 + 165 + $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); 166 + 167 + $keep = array(); 168 + foreach ($impls as $key => $impl) { 169 + // Don't use disabled blueprint types. 170 + if (!$impl->isEnabled()) { 171 + continue; 172 + } 173 + 174 + // Don't use blueprint types which can't allocate the correct kind of 175 + // resource. 176 + if ($impl->getType() != $lease->getResourceType()) { 177 + continue; 178 + } 179 + 180 + if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { 181 + continue; 182 + } 183 + 184 + $keep[$key] = $impl; 185 + } 186 + 187 + return $keep; 188 + } 189 + 190 + 191 + /** 192 + * Get all the concrete @{class:DrydockBlueprint}s which can possibly 193 + * build a resource to satisfy a lease. 194 + * 195 + * @param DrydockLease Requested lease. 196 + * @return list<DrydockBlueprint> List of qualifying blueprints. 197 + * @task allocator 198 + */ 199 + private function loadBlueprintsForAllocatingLease( 200 + DrydockLease $lease) { 201 + $viewer = $this->getViewer(); 202 + 203 + $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease); 204 + if (!$impls) { 205 + return array(); 206 + } 207 + 208 + // TODO: When blueprints can be disabled, this query should ignore disabled 209 + // blueprints. 210 + 211 + $blueprints = id(new DrydockBlueprintQuery()) 212 + ->setViewer($viewer) 213 + ->withBlueprintClasses(array_keys($impls)) 214 + ->execute(); 215 + 216 + $keep = array(); 217 + foreach ($blueprints as $key => $blueprint) { 218 + if (!$blueprint->canEverAllocateResourceForLease($lease)) { 219 + continue; 220 + } 221 + 222 + $keep[$key] = $blueprint; 223 + } 224 + 225 + return $keep; 226 + } 227 + 228 + 229 + /** 132 230 * Load a list of all resources which a given lease can possibly be 133 231 * allocated against. 134 232 * ··· 137 235 * @param DrydockLease Requested lease. 138 236 * @return list<DrydockResource> Resources which may be able to allocate 139 237 * the lease. 238 + * @task allocator 140 239 */ 141 240 private function loadResourcesForAllocatingLease( 142 241 array $blueprints, ··· 157 256 158 257 $keep = array(); 159 258 foreach ($resources as $key => $resource) { 160 - if (!$resource->canAllocateLease($lease)) { 259 + $blueprint = $resource->getBlueprint(); 260 + 261 + if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) { 161 262 continue; 162 263 } 163 264 ··· 169 270 170 271 171 272 /** 273 + * Remove blueprints which are too heavily allocated to build a resource for 274 + * a lease from a list of blueprints. 275 + * 276 + * @param list<DrydockBlueprint> List of blueprints. 277 + * @return list<DrydockBlueprint> List with blueprints that can not allocate 278 + * a resource for the lease right now removed. 279 + * @task allocator 280 + */ 281 + private function removeOverallocatedBlueprints( 282 + array $blueprints, 283 + DrydockLease $lease) { 284 + assert_instances_of($blueprints, 'DrydockBlueprint'); 285 + 286 + $keep = array(); 287 + foreach ($blueprints as $key => $blueprint) { 288 + if (!$blueprint->canAllocateResourceForLease($lease)) { 289 + continue; 290 + } 291 + 292 + $keep[$key] = $blueprint; 293 + } 294 + 295 + return $keep; 296 + } 297 + 298 + 299 + /** 172 300 * Rank blueprints by suitability for building a new resource for a 173 301 * particular lease. 174 302 * 175 303 * @param list<DrydockBlueprint> List of blueprints. 176 304 * @param DrydockLease Requested lease. 177 305 * @return list<DrydockBlueprint> Ranked list of blueprints. 306 + * @task allocator 178 307 */ 179 308 private function rankBlueprints(array $blueprints, DrydockLease $lease) { 180 309 assert_instances_of($blueprints, 'DrydockBlueprint'); ··· 193 322 * @param list<DrydockResource> List of resources. 194 323 * @param DrydockLease Requested lease. 195 324 * @return list<DrydockResource> Ranked list of resources. 325 + * @task allocator 196 326 */ 197 327 private function rankResources(array $resources, DrydockLease $lease) { 198 328 assert_instances_of($resources, 'DrydockResource'); ··· 205 335 } 206 336 207 337 338 + /* -( Managing Resources )------------------------------------------------- */ 339 + 340 + 208 341 /** 209 - * Get all the concrete @{class:DrydockBlueprint}s which can possibly 210 - * build a resource to satisfy a lease. 342 + * Perform an actual resource allocation with a particular blueprint. 211 343 * 344 + * @param DrydockBlueprint The blueprint to allocate a resource from. 212 345 * @param DrydockLease Requested lease. 213 - * @return list<DrydockBlueprint> List of qualifying blueprints. 346 + * @return DrydockResource Allocated resource. 347 + * @task resource 348 + */ 349 + private function allocateResource( 350 + DrydockBlueprint $blueprint, 351 + DrydockLease $lease) { 352 + $resource = $blueprint->allocateResource($lease); 353 + $this->validateAllocatedResource($resource); 354 + return $resource; 355 + } 356 + 357 + 358 + /** 359 + * Check that the resource a blueprint allocated is roughly the sort of 360 + * object we expect. 361 + * 362 + * @param DrydockBlueprint Blueprint which built the resource. 363 + * @param wild Thing which the blueprint claims is a valid resource. 364 + * @param DrydockLease Lease the resource was allocated for. 365 + * @return void 366 + * @task resource 214 367 */ 215 - private function loadBlueprintsForAllocatingLease( 368 + private function validateAllocatedResource( 369 + DrydockBlueprint $blueprint, 370 + $resource, 216 371 DrydockLease $lease) { 217 - $viewer = $this->getViewer(); 372 + $blueprint = $this->getBlueprintClass(); 218 373 219 - $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease); 220 - if (!$impls) { 221 - return array(); 374 + if (!($resource instanceof DrydockResource)) { 375 + throw new Exception( 376 + pht( 377 + 'Blueprint "%s" (of type "%s") is not properly implemented: %s must '. 378 + 'return an object of type %s or throw, but returned something else.', 379 + $blueprint->getBlueprintName(), 380 + $blueprint->getClassName(), 381 + 'allocateResource()', 382 + 'DrydockResource')); 222 383 } 223 384 224 - // TODO: When blueprints can be disabled, this query should ignore disabled 225 - // blueprints. 385 + if (!$resource->isAllocatedResource()) { 386 + throw new Exception( 387 + pht( 388 + 'Blueprint "%s" (of type "%s") is not properly implemented: %s '. 389 + 'must actually allocate the resource it returns.', 390 + $blueprint->getBlueprintName(), 391 + $blueprint->getClassName(), 392 + 'allocateResource()')); 393 + } 226 394 227 - $blueprints = id(new DrydockBlueprintQuery()) 228 - ->setViewer($viewer) 229 - ->withBlueprintClasses(array_keys($impls)) 230 - ->execute(); 395 + $resource_type = $resource->getType(); 396 + $lease_type = $lease->getResourceType(); 231 397 232 - $keep = array(); 233 - foreach ($blueprints as $key => $blueprint) { 234 - if (!$blueprint->canEverAllocateResourceForLease($lease)) { 235 - continue; 236 - } 398 + if ($resource_type !== $lease_type) { 399 + // TODO: Destroy the resource here? 237 400 238 - $keep[$key] = $blueprint; 401 + throw new Exception( 402 + pht( 403 + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. 404 + 'built a resource of type "%s" to satisfy a lease requesting a '. 405 + 'resource of type "%s".', 406 + $blueprint->getBlueprintName(), 407 + $blueprint->getClassName(), 408 + $resource_type, 409 + $lease_type)); 239 410 } 240 - 241 - return $keep; 242 411 } 243 412 244 413 414 + /* -( Managing Leases )---------------------------------------------------- */ 415 + 416 + 245 417 /** 246 - * Get all the @{class:DrydockBlueprintImplementation}s which can possibly 247 - * build a resource to satisfy a lease. 418 + * Perform an actual lease acquisition on a particular resource. 248 419 * 249 - * This method returns blueprints which might, at some time, be able to 250 - * build a resource which can satisfy the lease. They may not be able to 251 - * build that resource right now. 252 - * 253 - * @param DrydockLease Requested lease. 254 - * @return list<DrydockBlueprintImplementation> List of qualifying blueprint 255 - * implementations. 420 + * @param DrydockResource Resource to acquire a lease on. 421 + * @param DrydockLease Lease to acquire. 422 + * @return void 423 + * @task lease 256 424 */ 257 - private function loadBlueprintImplementationsForAllocatingLease( 425 + private function acquireLease( 426 + DrydockResource $resource, 258 427 DrydockLease $lease) { 259 428 260 - $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); 261 - 262 - $keep = array(); 263 - foreach ($impls as $key => $impl) { 264 - // Don't use disabled blueprint types. 265 - if (!$impl->isEnabled()) { 266 - continue; 267 - } 429 + $blueprint = $resource->getBlueprint(); 430 + $blueprint->acquireLease($resource, $lease); 268 431 269 - // Don't use blueprint types which can't allocate the correct kind of 270 - // resource. 271 - if ($impl->getType() != $lease->getResourceType()) { 272 - continue; 273 - } 274 - 275 - if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { 276 - continue; 277 - } 278 - 279 - $keep[$key] = $impl; 280 - } 281 - 282 - return $keep; 432 + $this->validateAcquiredLease($blueprint, $resource, $lease); 283 433 } 284 434 285 435 286 436 /** 287 - * Remove blueprints which are too heavily allocated to build a resource for 288 - * a lease from a list of blueprints. 437 + * Make sure that a lease was really acquired properly. 289 438 * 290 - * @param list<DrydockBlueprint> List of blueprints. 291 - * @param list<DrydockBlueprint> List with fully allocated blueprints 292 - * removed. 439 + * @param DrydockBlueprint Blueprint which created the resource. 440 + * @param DrydockResource Resource which was acquired. 441 + * @param DrydockLease The lease which was supposedly acquired. 442 + * @return void 443 + * @task lease 293 444 */ 294 - private function removeOverallocatedBlueprints( 295 - array $blueprints, 445 + private function validateAcquiredLease( 446 + DrydockBlueprint $blueprint, 447 + DrydockResource $resource, 296 448 DrydockLease $lease) { 297 - assert_instances_of($blueprints, 'DrydockBlueprint'); 298 449 299 - $keep = array(); 300 - foreach ($blueprints as $key => $blueprint) { 301 - if (!$blueprint->canAllocateResourceForLease($lease)) { 302 - continue; 303 - } 450 + if (!$lease->isAcquiredLease()) { 451 + throw new Exception( 452 + pht( 453 + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. 454 + 'returned from "%s" without acquiring a lease.', 455 + $blueprint->getBlueprintName(), 456 + $blueprint->getClassName(), 457 + 'acquireLease()')); 458 + } 459 + 460 + $lease_id = $lease->getResourceID(); 461 + $resource_id = $resource->getID(); 304 462 305 - $keep[$key] = $blueprint; 463 + if ($lease_id !== $resource_id) { 464 + // TODO: Destroy the lease? 465 + throw new Exception( 466 + pht( 467 + 'Blueprint "%s" (of type "%s") is not properly implemented: it '. 468 + 'returned from "%s" with a lease acquired on the wrong resource.', 469 + $blueprint->getBlueprintName(), 470 + $blueprint->getClassName(), 471 + 'acquireLease()')); 306 472 } 307 - 308 - return $keep; 309 473 } 474 + 310 475 311 476 }
+1 -1
src/applications/passphrase/controller/PassphraseCredentialEditController.php
··· 31 31 throw new Exception( 32 32 pht( 33 33 'Credential has noncreateable type "%s"!', 34 - $credential->getCredentialType())); 34 + $type_const)); 35 35 } 36 36 37 37 $credential = PassphraseCredential::initializeNewCredential($viewer)