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

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