@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.

Reduce garbage-level of Drydock Allocator implementation

Summary:
Ref T9253. The Drydock allocator is very pseudocodey right now. Particularly, it was written before Blueprints were concrete.

Reorganize it to make its responsibilities and error handling behaviors more clear.

In particular, the Allocator does not manage locks. It's primarily trying to reject allocations which can not possibly work. Blueprints are responsible for locks. See some discussion in D10304.

NOTE: This code probably doesn't work as written, see future diffs.

Test Plan: See future diffs.

Reviewers: hach-que, chad

Reviewed By: hach-que, chad

Subscribers: cburroughs

Maniphest Tasks: T9253

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

+409 -184
+105 -40
src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
··· 69 69 70 70 71 71 /** 72 - * @task lease 73 - */ 74 - final public function filterResource( 75 - DrydockResource $resource, 76 - DrydockLease $lease) { 77 - 78 - $scope = $this->pushActiveScope($resource, $lease); 79 - 80 - return $this->canAllocateLease($resource, $lease); 81 - } 82 - 83 - 84 - /** 85 72 * Enforce basic checks on lease/resource compatibility. Allows resources to 86 73 * reject leases if they are incompatible, even if the resource types match. 87 74 * 88 75 * For example, if a resource represents a 32-bit host, this method might 89 - * reject leases that need a 64-bit host. If a resource represents a working 90 - * copy of repository "X", this method might reject leases which need a 91 - * working copy of repository "Y". Generally, although the main types of 92 - * a lease and resource may match (e.g., both "host"), it may not actually be 93 - * possible to satisfy the lease with a specific resource. 76 + * reject leases that need a 64-bit host. The blueprint might also reject 77 + * a resource if the lease needs 8GB of RAM and the resource only has 6GB 78 + * free. 94 79 * 95 - * This method generally should not enforce limits or perform capacity 96 - * checks. Perform those in @{method:shouldAllocateLease} instead. It also 97 - * should not perform actual acquisition of the lease; perform that in 98 - * @{method:executeAcquireLease} instead. 80 + * This method should not acquire locks or expect anything to be locked. This 81 + * is a coarse compatibility check between a lease and a resource. 99 82 * 100 - * @param DrydockResource Candidiate resource to allocate the lease on. 101 - * @param DrydockLease Pending lease that wants to allocate here. 102 - * @return bool True if the resource and lease are compatible. 83 + * @param DrydockBlueprint Concrete blueprint to allocate for. 84 + * @param DrydockResource Candidiate resource to allocate the lease on. 85 + * @param DrydockLease Pending lease that wants to allocate here. 86 + * @return bool True if the resource and lease are compatible. 103 87 * @task lease 104 88 */ 105 - abstract protected function canAllocateLease( 89 + abstract public function canAllocateLeaseOnResource( 90 + DrydockBlueprint $blueprint, 106 91 DrydockResource $resource, 107 92 DrydockLease $lease); 108 93 ··· 297 282 /* -( Resource Allocation )------------------------------------------------ */ 298 283 299 284 300 - public function canAllocateMoreResources(array $pool) { 301 - return true; 302 - } 285 + /** 286 + * Enforce fundamental implementation/lease checks. Allows implementations to 287 + * reject a lease which no concrete blueprint can ever satisfy. 288 + * 289 + * For example, if a lease only builds ARM hosts and the lease needs a 290 + * PowerPC host, it may be rejected here. 291 + * 292 + * This is the earliest rejection phase, and followed by 293 + * @{method:canEverAllocateResourceForLease}. 294 + * 295 + * This method should not actually check if a resource can be allocated 296 + * right now, or even if a blueprint which can allocate a suitable resource 297 + * really exists, only if some blueprint may conceivably exist which could 298 + * plausibly be able to build a suitable resource. 299 + * 300 + * @param DrydockLease Requested lease. 301 + * @return bool True if some concrete blueprint of this implementation's 302 + * type might ever be able to build a resource for the lease. 303 + * @task resource 304 + */ 305 + abstract public function canAnyBlueprintEverAllocateResourceForLease( 306 + DrydockLease $lease); 303 307 304 - abstract protected function executeAllocateResource(DrydockLease $lease); 305 308 309 + /** 310 + * Enforce basic blueprint/lease checks. Allows blueprints to reject a lease 311 + * which they can not build a resource for. 312 + * 313 + * This is the second rejection phase. It follows 314 + * @{method:canAnyBlueprintEverAllocateResourceForLease} and is followed by 315 + * @{method:canAllocateResourceForLease}. 316 + * 317 + * This method should not check if a resource can be built right now, only 318 + * if the blueprint as configured may, at some time, be able to build a 319 + * suitable resource. 320 + * 321 + * @param DrydockBlueprint Blueprint which may be asked to allocate a 322 + * resource. 323 + * @param DrydockLease Requested lease. 324 + * @return bool True if this blueprint can eventually build a suitable 325 + * resource for the lease, as currently configured. 326 + * @task resource 327 + */ 328 + abstract public function canEverAllocateResourceForLease( 329 + DrydockBlueprint $blueprint, 330 + DrydockLease $lease); 306 331 307 - final public function allocateResource(DrydockLease $lease) { 332 + 333 + /** 334 + * Enforce basic availability limits. Allows blueprints to reject resource 335 + * allocation if they are currently overallocated. 336 + * 337 + * This method should perform basic capacity/limit checks. For example, if 338 + * it has a limit of 6 resources and currently has 6 resources allocated, 339 + * it might reject new leases. 340 + * 341 + * This method should not acquire locks or expect locks to be acquired. This 342 + * is a coarse check to determine if the operation is likely to succeed 343 + * right now without needing to acquire locks. 344 + * 345 + * It is expected that this method will sometimes return `true` (indicating 346 + * that a resource can be allocated) but find that another allocator has 347 + * eaten up free capacity by the time it actually tries to build a resource. 348 + * This is normal and the allocator will recover from it. 349 + * 350 + * @param DrydockBlueprint The blueprint which may be asked to allocate a 351 + * resource. 352 + * @param DrydockLease Requested lease. 353 + * @return bool True if this blueprint appears likely to be able to allocate 354 + * a suitable resource. 355 + */ 356 + abstract public function canAllocateResourceForLease( 357 + DrydockBlueprint $blueprint, 358 + DrydockLease $lease); 359 + 360 + 361 + /** 362 + * Allocate a suitable resource for a lease. 363 + * 364 + * This method MUST acquire, hold, and manage locks to prevent multiple 365 + * allocations from racing. World state is not locked before this method is 366 + * called. Blueprints are entirely responsible for any lock handling they 367 + * need to perform. 368 + * 369 + * @param DrydockBlueprint The blueprint which should allocate a resource. 370 + * @param DrydockLease Requested lease. 371 + * @return DrydockResource Allocated resource. 372 + */ 373 + abstract protected function executeAllocateResource( 374 + DrydockBlueprint $blueprint, 375 + DrydockLease $lease); 376 + 377 + final public function allocateResource( 378 + DrydockBlueprint $blueprint, 379 + DrydockLease $lease) { 380 + 308 381 $scope = $this->pushActiveScope(null, $lease); 309 382 310 383 $this->log( ··· 314 387 $lease->getLeaseName())); 315 388 316 389 try { 317 - $resource = $this->executeAllocateResource($lease); 390 + $resource = $this->executeAllocateResource($blueprint, $lease); 318 391 $this->validateAllocatedResource($resource); 319 392 } catch (Exception $ex) { 320 393 $this->logException($ex); ··· 375 448 return id(new PhutilClassMapQuery()) 376 449 ->setAncestorClass(__CLASS__) 377 450 ->execute(); 378 - } 379 - 380 - public static function getAllBlueprintImplementationsForResource($type) { 381 - static $groups = null; 382 - if ($groups === null) { 383 - $groups = mgroup(self::getAllBlueprintImplementations(), 'getType'); 384 - } 385 - return idx($groups, $type, array()); 386 451 } 387 452 388 453 public static function getNamedImplementation($class) {
+28 -3
src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
··· 15 15 return pht('Allows Drydock to check out working copies of repositories.'); 16 16 } 17 17 18 - protected function canAllocateLease( 18 + public function canAnyBlueprintEverAllocateResourceForLease( 19 + DrydockLease $lease) { 20 + // TODO: These checks are out of date. 21 + return true; 22 + } 23 + 24 + public function canEverAllocateResourceForLease( 25 + DrydockBlueprint $blueprint, 26 + DrydockLease $lease) { 27 + // TODO: These checks are out of date. 28 + return true; 29 + } 30 + 31 + public function canAllocateResourceForLease( 32 + DrydockBlueprint $blueprint, 33 + DrydockLease $lease) { 34 + // TODO: These checks are out of date. 35 + return true; 36 + } 37 + 38 + public function canAllocateLeaseOnResource( 39 + DrydockBlueprint $blueprint, 19 40 DrydockResource $resource, 20 41 DrydockLease $lease) { 42 + // TODO: These checks are out of date. 21 43 22 44 $resource_repo = $resource->getAttribute('repositoryID'); 23 45 $lease_repo = $lease->getAttribute('repositoryID'); ··· 29 51 DrydockResource $resource, 30 52 DrydockLease $lease, 31 53 array $other_leases) { 32 - 54 + // TODO: These checks are out of date. 33 55 return !$other_leases; 34 56 } 35 57 36 - protected function executeAllocateResource(DrydockLease $lease) { 58 + protected function executeAllocateResource( 59 + DrydockBlueprint $blueprint, 60 + DrydockLease $lease) { 61 + 37 62 $repository_id = $lease->getAttribute('repositoryID'); 38 63 if (!$repository_id) { 39 64 throw new Exception(
+21 -10
src/applications/drydock/storage/DrydockBlueprint.php
··· 51 51 } 52 52 53 53 public function getImplementation() { 54 - $class = $this->className; 55 - $implementations = 56 - DrydockBlueprintImplementation::getAllBlueprintImplementations(); 57 - if (!isset($implementations[$class])) { 58 - throw new Exception( 59 - pht( 60 - "Invalid class name for blueprint (got '%s')", 61 - $class)); 62 - } 63 - return id(new $class())->attachInstance($this); 54 + return $this->assertAttached($this->implementation); 64 55 } 65 56 66 57 public function attachImplementation(DrydockBlueprintImplementation $impl) { ··· 77 68 return $this; 78 69 } 79 70 71 + public function canEverAllocateResourceForLease(DrydockLease $lease) { 72 + return $this->getImplementation()->canEverAllocateResourceForLease( 73 + $this, 74 + $lease); 75 + } 76 + 77 + public function canAllocateResourceForLease(DrydockLease $lease) { 78 + return $this->getImplementation()->canAllocateResourceForLease( 79 + $this, 80 + $lease); 81 + } 82 + 83 + public function canAllocateLeaseOnResource( 84 + DrydockResource $resource, 85 + DrydockLease $lease) { 86 + return $this->getImplementation()->canAllocateLeaseOnResource( 87 + $this, 88 + $resource, 89 + $lease); 90 + } 80 91 81 92 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 82 93
+255 -131
src/applications/drydock/worker/DrydockAllocatorWorker.php
··· 2 2 3 3 final class DrydockAllocatorWorker extends PhabricatorWorker { 4 4 5 - private $lease; 5 + private function getViewer() { 6 + return PhabricatorUser::getOmnipotentUser(); 7 + } 6 8 7 - public function getRequiredLeaseTime() { 8 - return 3600 * 24; 9 - } 9 + private function loadLease() { 10 + $viewer = $this->getViewer(); 10 11 11 - public function getMaximumRetryCount() { 12 - // TODO: Allow Drydock allocations to retry. For now, every failure is 13 - // permanent and most of them are because I am bad at programming, so fail 14 - // fast rather than ending up in limbo. 15 - return 0; 16 - } 12 + // TODO: Make the task data a dictionary like every other worker, and 13 + // probably make this a PHID. 14 + $lease_id = $this->getTaskData(); 17 15 18 - private function loadLease() { 19 - if (empty($this->lease)) { 20 - $lease = id(new DrydockLeaseQuery()) 21 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 22 - ->withIDs(array($this->getTaskData())) 23 - ->executeOne(); 24 - if (!$lease) { 25 - throw new PhabricatorWorkerPermanentFailureException( 26 - pht('No such lease %d!', $this->getTaskData())); 27 - } 28 - $this->lease = $lease; 16 + $lease = id(new DrydockLeaseQuery()) 17 + ->setViewer($viewer) 18 + ->withIDs(array($lease_id)) 19 + ->executeOne(); 20 + if (!$lease) { 21 + throw new PhabricatorWorkerPermanentFailureException( 22 + pht('No such lease "%s"!', $lease_id)); 29 23 } 30 - return $this->lease; 31 - } 32 24 33 - private function logToDrydock($message) { 34 - DrydockBlueprintImplementation::writeLog( 35 - null, 36 - $this->loadLease(), 37 - $message); 25 + return $lease; 38 26 } 39 27 40 28 protected function doWork() { 41 29 $lease = $this->loadLease(); 42 - $this->logToDrydock(pht('Allocating Lease')); 30 + $this->allocateLease($lease); 31 + } 32 + 33 + private function allocateLease(DrydockLease $lease) { 34 + $blueprints = $this->loadBlueprintsForAllocatingLease($lease); 35 + 36 + // If we get nothing back, that means no blueprint is defined which can 37 + // ever build the requested resource. This is a permanent failure, since 38 + // we don't expect to succeed no matter how many times we try. 39 + if (!$blueprints) { 40 + $lease 41 + ->setStatus(DrydockLeaseStatus::STATUS_BROKEN) 42 + ->save(); 43 + throw new PhabricatorWorkerPermanentFailureException( 44 + pht( 45 + 'No active Drydock blueprint exists which can ever allocate a '. 46 + 'resource for lease "%s".', 47 + $lease->getPHID())); 48 + } 49 + 50 + // First, try to find a suitable open resource which we can acquire a new 51 + // lease on. 52 + $resources = $this->loadResourcesForAllocatingLease($blueprints, $lease); 53 + 54 + // If no resources exist yet, see if we can build one. 55 + if (!$resources) { 56 + $usable_blueprints = $this->removeOverallocatedBlueprints( 57 + $blueprints, 58 + $lease); 59 + 60 + // If we get nothing back here, some blueprint claims it can eventually 61 + // satisfy the lease, just not right now. This is a temporary failure, 62 + // and we expect allocation to succeed eventually. 63 + if (!$blueprints) { 64 + // TODO: More formal temporary failure here. We should retry this 65 + // "soon" but not "immediately". 66 + throw new Exception( 67 + pht('No blueprints have space to allocate a resource right now.')); 68 + } 69 + 70 + $usable_blueprints = $this->rankBlueprints($blueprints, $lease); 71 + 72 + $exceptions = array(); 73 + foreach ($usable_blueprints as $blueprint) { 74 + try { 75 + $resources[] = $blueprint->allocateResource($lease); 76 + // Bail after allocating one resource, we don't need any more than 77 + // this. 78 + break; 79 + } catch (Exception $ex) { 80 + $exceptions[] = $ex; 81 + } 82 + } 43 83 44 - try { 45 - $this->allocateLease($lease); 46 - } catch (Exception $ex) { 84 + if (!$resources) { 85 + // TODO: We should distinguish between temporary and permament failures 86 + // here. If any blueprint failed temporarily, retry "soon". If none 87 + // of these failures were temporary, maybe this should be a permanent 88 + // failure? 89 + throw new PhutilAggregateException( 90 + pht( 91 + 'All blueprints failed to allocate a suitable new resource when '. 92 + 'trying to allocate lease "%s".', 93 + $lease->getPHID()), 94 + $exceptions); 95 + } 47 96 48 - // TODO: We should really do this when archiving the task, if we've 49 - // suffered a permanent failure. But we don't have hooks for that yet 50 - // and always fail after the first retry right now, so this is 51 - // functionally equivalent. 52 - $lease->reload(); 53 - if ($lease->getStatus() == DrydockLeaseStatus::STATUS_PENDING) { 54 - $lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN); 55 - $lease->save(); 97 + // NOTE: We have not acquired the lease yet, so it is possible that the 98 + // resource we just built will be snatched up by some other lease before 99 + // we can. This is not problematic: we'll retry a little later and should 100 + // suceed eventually. 101 + } 102 + 103 + $resources = $this->rankResources($resources, $lease); 104 + 105 + $exceptions = array(); 106 + $allocated = false; 107 + foreach ($resources as $resource) { 108 + try { 109 + $blueprint->allocateLease($resource, $lease); 110 + $allocated = true; 111 + break; 112 + } catch (Exception $ex) { 113 + $exceptions[] = $ex; 56 114 } 115 + } 57 116 58 - throw $ex; 117 + if (!$allocated) { 118 + // TODO: We should distinguish between temporary and permanent failures 119 + // here. If any failures were temporary (specifically, failed to acquire 120 + // locks) 121 + 122 + throw new PhutilAggregateException( 123 + pht( 124 + 'Unable to acquire lease "%s" on any resouce.', 125 + $lease->getPHID()), 126 + $exceptions); 59 127 } 60 128 } 61 129 62 - private function loadAllBlueprints() { 63 - $viewer = PhabricatorUser::getOmnipotentUser(); 64 - $instances = id(new DrydockBlueprintQuery()) 130 + 131 + /** 132 + * Load a list of all resources which a given lease can possibly be 133 + * allocated against. 134 + * 135 + * @param list<DrydockBlueprint> Blueprints which may produce suitable 136 + * resources. 137 + * @param DrydockLease Requested lease. 138 + * @return list<DrydockResource> Resources which may be able to allocate 139 + * the lease. 140 + */ 141 + private function loadResourcesForAllocatingLease( 142 + array $blueprints, 143 + DrydockLease $lease) { 144 + assert_instances_of($blueprints, 'DrydockBlueprint'); 145 + $viewer = $this->getViewer(); 146 + 147 + $resources = id(new DrydockResourceQuery()) 65 148 ->setViewer($viewer) 149 + ->withBlueprintPHIDs(mpull($blueprints, 'getPHID')) 150 + ->withTypes(array($lease->getResourceType())) 151 + ->withStatuses( 152 + array( 153 + DrydockResourceStatus::STATUS_PENDING, 154 + DrydockResourceStatus::STATUS_OPEN, 155 + )) 66 156 ->execute(); 67 - $blueprints = array(); 68 - foreach ($instances as $instance) { 69 - $blueprints[$instance->getPHID()] = $instance; 157 + 158 + $keep = array(); 159 + foreach ($resources as $key => $resource) { 160 + if (!$resource->canAllocateLease($lease)) { 161 + continue; 162 + } 163 + 164 + $keep[$key] = $resource; 70 165 } 166 + 167 + return $keep; 168 + } 169 + 170 + 171 + /** 172 + * Rank blueprints by suitability for building a new resource for a 173 + * particular lease. 174 + * 175 + * @param list<DrydockBlueprint> List of blueprints. 176 + * @param DrydockLease Requested lease. 177 + * @return list<DrydockBlueprint> Ranked list of blueprints. 178 + */ 179 + private function rankBlueprints(array $blueprints, DrydockLease $lease) { 180 + assert_instances_of($blueprints, 'DrydockBlueprint'); 181 + 182 + // TODO: Implement improvements to this ranking algorithm if they become 183 + // available. 184 + shuffle($blueprints); 185 + 71 186 return $blueprints; 72 187 } 73 188 74 - private function allocateLease(DrydockLease $lease) { 75 - $type = $lease->getResourceType(); 76 189 77 - $blueprints = $this->loadAllBlueprints(); 190 + /** 191 + * Rank resources by suitability for allocating a particular lease. 192 + * 193 + * @param list<DrydockResource> List of resources. 194 + * @param DrydockLease Requested lease. 195 + * @return list<DrydockResource> Ranked list of resources. 196 + */ 197 + private function rankResources(array $resources, DrydockLease $lease) { 198 + assert_instances_of($resources, 'DrydockResource'); 78 199 79 - // TODO: Policy stuff. 80 - $pool = id(new DrydockResource())->loadAllWhere( 81 - 'type = %s AND status = %s', 82 - $lease->getResourceType(), 83 - DrydockResourceStatus::STATUS_OPEN); 200 + // TODO: Implement improvements to this ranking algorithm if they become 201 + // available. 202 + shuffle($resources); 84 203 85 - $this->logToDrydock( 86 - pht('Found %d Open Resource(s)', count($pool))); 204 + return $resources; 205 + } 87 206 88 - $candidates = array(); 89 - foreach ($pool as $key => $candidate) { 90 - if (!isset($blueprints[$candidate->getBlueprintPHID()])) { 91 - unset($pool[$key]); 92 - continue; 93 - } 94 207 95 - $blueprint = $blueprints[$candidate->getBlueprintPHID()]; 96 - $implementation = $blueprint->getImplementation(); 208 + /** 209 + * Get all the concrete @{class:DrydockBlueprint}s which can possibly 210 + * build a resource to satisfy a lease. 211 + * 212 + * @param DrydockLease Requested lease. 213 + * @return list<DrydockBlueprint> List of qualifying blueprints. 214 + */ 215 + private function loadBlueprintsForAllocatingLease( 216 + DrydockLease $lease) { 217 + $viewer = $this->getViewer(); 97 218 98 - if ($implementation->filterResource($candidate, $lease)) { 99 - $candidates[] = $candidate; 100 - } 219 + $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease); 220 + if (!$impls) { 221 + return array(); 101 222 } 102 223 103 - $this->logToDrydock(pht('%d Open Resource(s) Remain', count($candidates))); 224 + // TODO: When blueprints can be disabled, this query should ignore disabled 225 + // blueprints. 104 226 105 - $resource = null; 106 - if ($candidates) { 107 - shuffle($candidates); 108 - foreach ($candidates as $candidate_resource) { 109 - $blueprint = $blueprints[$candidate_resource->getBlueprintPHID()] 110 - ->getImplementation(); 111 - if ($blueprint->allocateLease($candidate_resource, $lease)) { 112 - $resource = $candidate_resource; 113 - break; 114 - } 227 + $blueprints = id(new DrydockBlueprintQuery()) 228 + ->setViewer($viewer) 229 + ->withBlueprintClasses(array_keys($impls)) 230 + ->execute(); 231 + 232 + $keep = array(); 233 + foreach ($blueprints as $key => $blueprint) { 234 + if (!$blueprint->canEverAllocateResourceForLease($lease)) { 235 + continue; 115 236 } 237 + 238 + $keep[$key] = $blueprint; 116 239 } 117 240 118 - if (!$resource) { 119 - $blueprints = DrydockBlueprintImplementation 120 - ::getAllBlueprintImplementationsForResource($type); 241 + return $keep; 242 + } 121 243 122 - $this->logToDrydock( 123 - pht('Found %d Blueprints', count($blueprints))); 124 244 125 - foreach ($blueprints as $key => $candidate_blueprint) { 126 - if (!$candidate_blueprint->isEnabled()) { 127 - unset($blueprints[$key]); 128 - continue; 129 - } 130 - } 245 + /** 246 + * Get all the @{class:DrydockBlueprintImplementation}s which can possibly 247 + * build a resource to satisfy a lease. 248 + * 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. 256 + */ 257 + private function loadBlueprintImplementationsForAllocatingLease( 258 + DrydockLease $lease) { 131 259 132 - $this->logToDrydock( 133 - pht('%d Blueprints Enabled', count($blueprints))); 260 + $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations(); 134 261 135 - foreach ($blueprints as $key => $candidate_blueprint) { 136 - if (!$candidate_blueprint->canAllocateMoreResources($pool)) { 137 - unset($blueprints[$key]); 138 - continue; 139 - } 262 + $keep = array(); 263 + foreach ($impls as $key => $impl) { 264 + // Don't use disabled blueprint types. 265 + if (!$impl->isEnabled()) { 266 + continue; 140 267 } 141 268 142 - $this->logToDrydock( 143 - pht('%d Blueprints Can Allocate', count($blueprints))); 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 + } 144 274 145 - if (!$blueprints) { 146 - $lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN); 147 - $lease->save(); 275 + if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) { 276 + continue; 277 + } 148 278 149 - $this->logToDrydock( 150 - pht( 151 - "There are no resources of type '%s' available, and no ". 152 - "blueprints which can allocate new ones.", 153 - $type)); 279 + $keep[$key] = $impl; 280 + } 154 281 155 - return; 156 - } 282 + return $keep; 283 + } 157 284 158 - // TODO: Rank intelligently. 159 - shuffle($blueprints); 160 285 161 - $blueprint = head($blueprints); 162 - $resource = $blueprint->allocateResource($lease); 286 + /** 287 + * Remove blueprints which are too heavily allocated to build a resource for 288 + * a lease from a list of blueprints. 289 + * 290 + * @param list<DrydockBlueprint> List of blueprints. 291 + * @param list<DrydockBlueprint> List with fully allocated blueprints 292 + * removed. 293 + */ 294 + private function removeOverallocatedBlueprints( 295 + array $blueprints, 296 + DrydockLease $lease) { 297 + assert_instances_of($blueprints, 'DrydockBlueprint'); 163 298 164 - if (!$blueprint->allocateLease($resource, $lease)) { 165 - // TODO: This "should" happen only if we lost a race with another lease, 166 - // which happened to acquire this resource immediately after we 167 - // allocated it. In this case, the right behavior is to retry 168 - // immediately. However, other things like a blueprint allocating a 169 - // resource it can't actually allocate the lease on might be happening 170 - // too, in which case we'd just allocate infinite resources. Probably 171 - // what we should do is test for an active or allocated lease and retry 172 - // if we find one (although it might have already been released by now) 173 - // and fail really hard ("your configuration is a huge broken mess") 174 - // otherwise. But just throw for now since this stuff is all edge-casey. 175 - // Alternatively we could bring resources up in a "BESPOKE" status 176 - // and then switch them to "OPEN" only after the allocating lease gets 177 - // its grubby mitts on the resource. This might make more sense but 178 - // is a bit messy. 179 - throw new Exception(pht('Lost an allocation race?')); 299 + $keep = array(); 300 + foreach ($blueprints as $key => $blueprint) { 301 + if (!$blueprint->canAllocateResourceForLease($lease)) { 302 + continue; 180 303 } 304 + 305 + $keep[$key] = $blueprint; 181 306 } 182 307 183 - $blueprint = $resource->getBlueprint(); 184 - $blueprint->acquireLease($resource, $lease); 308 + return $keep; 185 309 } 186 310 187 311 }