[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @task lease Lease Acquisition 5 * @task resource Resource Allocation 6 * @task log Logging 7 */ 8 abstract class DrydockBlueprintImplementation { 9 10 private $activeResource; 11 private $activeLease; 12 private $instance; 13 14 abstract public function getType(); 15 abstract public function getInterface( 16 DrydockResource $resource, 17 DrydockLease $lease, 18 $type); 19 20 abstract public function isEnabled(); 21 22 abstract public function getBlueprintName(); 23 abstract public function getDescription(); 24 25 public function getBlueprintClass() { 26 return get_class($this); 27 } 28 29 protected function loadLease($lease_id) { 30 // TODO: Get rid of this? 31 $query = id(new DrydockLeaseQuery()) 32 ->setViewer(PhabricatorUser::getOmnipotentUser()) 33 ->withIDs(array($lease_id)) 34 ->execute(); 35 36 $lease = idx($query, $lease_id); 37 38 if (!$lease) { 39 throw new Exception("No such lease '{$lease_id}'!"); 40 } 41 42 return $lease; 43 } 44 45 protected function getInstance() { 46 if (!$this->instance) { 47 throw new Exception( 48 'Attach the blueprint instance to the implementation.'); 49 } 50 51 return $this->instance; 52 } 53 54 public function attachInstance(DrydockBlueprint $instance) { 55 $this->instance = $instance; 56 return $this; 57 } 58 59 public function getFieldSpecifications() { 60 return array(); 61 } 62 63 public function getDetail($key, $default = null) { 64 return $this->getInstance()->getDetail($key, $default); 65 } 66 67 68 /* -( Lease Acquisition )-------------------------------------------------- */ 69 70 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 * Enforce basic checks on lease/resource compatibility. Allows resources to 86 * reject leases if they are incompatible, even if the resource types match. 87 * 88 * 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. 94 * 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. 99 * 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. 103 * @task lease 104 */ 105 abstract protected function canAllocateLease( 106 DrydockResource $resource, 107 DrydockLease $lease); 108 109 110 /** 111 * @task lease 112 */ 113 final public function allocateLease( 114 DrydockResource $resource, 115 DrydockLease $lease) { 116 117 $scope = $this->pushActiveScope($resource, $lease); 118 119 $this->log('Trying to Allocate Lease'); 120 121 $lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING); 122 $lease->setResourceID($resource->getID()); 123 $lease->attachResource($resource); 124 125 $ephemeral_lease = id(clone $lease)->makeEphemeral(); 126 127 $allocated = false; 128 $allocation_exception = null; 129 130 $resource->openTransaction(); 131 $resource->beginReadLocking(); 132 $resource->reload(); 133 134 // TODO: Policy stuff. 135 $other_leases = id(new DrydockLease())->loadAllWhere( 136 'status IN (%Ld) AND resourceID = %d', 137 array( 138 DrydockLeaseStatus::STATUS_ACQUIRING, 139 DrydockLeaseStatus::STATUS_ACTIVE, 140 ), 141 $resource->getID()); 142 143 try { 144 $allocated = $this->shouldAllocateLease( 145 $resource, 146 $ephemeral_lease, 147 $other_leases); 148 } catch (Exception $ex) { 149 $allocation_exception = $ex; 150 } 151 152 if ($allocated) { 153 $lease->save(); 154 } 155 $resource->endReadLocking(); 156 if ($allocated) { 157 $resource->saveTransaction(); 158 $this->log('Allocated Lease'); 159 } else { 160 $resource->killTransaction(); 161 $this->log('Failed to Allocate Lease'); 162 } 163 164 if ($allocation_exception) { 165 $this->logException($allocation_exception); 166 } 167 168 return $allocated; 169 } 170 171 172 /** 173 * Enforce lease limits on resources. Allows resources to reject leases if 174 * they would become over-allocated by accepting them. 175 * 176 * For example, if a resource represents disk space, this method might check 177 * how much space the lease is asking for (say, 200MB) and how much space is 178 * left unallocated on the resource. It could grant the lease (return true) 179 * if it has enough remaining space (more than 200MB), and reject the lease 180 * (return false) if it does not (less than 200MB). 181 * 182 * A resource might also allow only exclusive leases. In this case it could 183 * accept a new lease (return true) if there are no active leases, or reject 184 * the new lease (return false) if there any other leases. 185 * 186 * A lock is held on the resource while this method executes to prevent 187 * multiple processes from allocating leases on the resource simultaneously. 188 * However, this means you should implement the method as cheaply as possible. 189 * In particular, do not perform any actual acquisition or setup in this 190 * method. 191 * 192 * If allocation is permitted, the lease will be moved to `ACQUIRING` status 193 * and @{method:executeAcquireLease} will be called to actually perform 194 * acquisition. 195 * 196 * General compatibility checks unrelated to resource limits and capacity are 197 * better implemented in @{method:canAllocateLease}, which serves as a 198 * cheap filter before lock acquisition. 199 * 200 * @param DrydockResource Candidate resource to allocate the lease on. 201 * @param DrydockLease Pending lease that wants to allocate here. 202 * @param list<DrydockLease> Other allocated and acquired leases on the 203 * resource. The implementation can inspect them 204 * to verify it can safely add the new lease. 205 * @return bool True to allocate the lease on the resource; 206 * false to reject it. 207 * @task lease 208 */ 209 abstract protected function shouldAllocateLease( 210 DrydockResource $resource, 211 DrydockLease $lease, 212 array $other_leases); 213 214 215 /** 216 * @task lease 217 */ 218 final public function acquireLease( 219 DrydockResource $resource, 220 DrydockLease $lease) { 221 222 $scope = $this->pushActiveScope($resource, $lease); 223 224 $this->log('Acquiring Lease'); 225 $lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE); 226 $lease->setResourceID($resource->getID()); 227 $lease->attachResource($resource); 228 229 $ephemeral_lease = id(clone $lease)->makeEphemeral(); 230 231 try { 232 $this->executeAcquireLease($resource, $ephemeral_lease); 233 } catch (Exception $ex) { 234 $this->logException($ex); 235 throw $ex; 236 } 237 238 $lease->setAttributes($ephemeral_lease->getAttributes()); 239 $lease->save(); 240 $this->log('Acquired Lease'); 241 } 242 243 244 /** 245 * Acquire and activate an allocated lease. Allows resources to peform setup 246 * as leases are brought online. 247 * 248 * Following a successful call to @{method:canAllocateLease}, a lease is moved 249 * to `ACQUIRING` status and this method is called after resource locks are 250 * released. Nothing is locked while this method executes; the implementation 251 * is free to perform expensive operations like writing files and directories, 252 * executing commands, etc. 253 * 254 * After this method executes, the lease status is moved to `ACTIVE` and the 255 * original leasee may access it. 256 * 257 * If acquisition fails, throw an exception. 258 * 259 * @param DrydockResource Resource to acquire a lease on. 260 * @param DrydockLease Lease to acquire. 261 * @return void 262 */ 263 abstract protected function executeAcquireLease( 264 DrydockResource $resource, 265 DrydockLease $lease); 266 267 268 269 final public function releaseLease( 270 DrydockResource $resource, 271 DrydockLease $lease) { 272 $scope = $this->pushActiveScope(null, $lease); 273 274 $released = false; 275 276 $lease->openTransaction(); 277 $lease->beginReadLocking(); 278 $lease->reload(); 279 280 if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) { 281 $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED); 282 $lease->save(); 283 $released = true; 284 } 285 286 $lease->endReadLocking(); 287 $lease->saveTransaction(); 288 289 if (!$released) { 290 throw new Exception('Unable to release lease: lease not active!'); 291 } 292 293 } 294 295 296 297 /* -( Resource Allocation )------------------------------------------------ */ 298 299 300 public function canAllocateMoreResources(array $pool) { 301 return true; 302 } 303 304 abstract protected function executeAllocateResource(DrydockLease $lease); 305 306 307 final public function allocateResource(DrydockLease $lease) { 308 $scope = $this->pushActiveScope(null, $lease); 309 310 $this->log( 311 pht( 312 "Blueprint '%s': Allocating Resource for '%s'", 313 $this->getBlueprintClass(), 314 $lease->getLeaseName())); 315 316 try { 317 $resource = $this->executeAllocateResource($lease); 318 $this->validateAllocatedResource($resource); 319 } catch (Exception $ex) { 320 $this->logException($ex); 321 throw $ex; 322 } 323 324 return $resource; 325 } 326 327 328 /* -( Logging )------------------------------------------------------------ */ 329 330 331 /** 332 * @task log 333 */ 334 protected function logException(Exception $ex) { 335 $this->log($ex->getMessage()); 336 } 337 338 339 /** 340 * @task log 341 */ 342 protected function log($message) { 343 self::writeLog( 344 $this->activeResource, 345 $this->activeLease, 346 $message); 347 } 348 349 350 /** 351 * @task log 352 */ 353 public static function writeLog( 354 DrydockResource $resource = null, 355 DrydockLease $lease = null, 356 $message) { 357 358 $log = id(new DrydockLog()) 359 ->setEpoch(time()) 360 ->setMessage($message); 361 362 if ($resource) { 363 $log->setResourceID($resource->getID()); 364 } 365 366 if ($lease) { 367 $log->setLeaseID($lease->getID()); 368 } 369 370 $log->save(); 371 } 372 373 374 public static function getAllBlueprintImplementations() { 375 static $list = null; 376 377 if ($list === null) { 378 $blueprints = id(new PhutilSymbolLoader()) 379 ->setType('class') 380 ->setAncestorClass('DrydockBlueprintImplementation') 381 ->setConcreteOnly(true) 382 ->selectAndLoadSymbols(); 383 $list = ipull($blueprints, 'name', 'name'); 384 foreach ($list as $class_name => $ignored) { 385 $list[$class_name] = newv($class_name, array()); 386 } 387 } 388 389 return $list; 390 } 391 392 public static function getAllBlueprintImplementationsForResource($type) { 393 static $groups = null; 394 if ($groups === null) { 395 $groups = mgroup(self::getAllBlueprintImplementations(), 'getType'); 396 } 397 return idx($groups, $type, array()); 398 } 399 400 public static function getNamedImplementation($class) { 401 return idx(self::getAllBlueprintImplementations(), $class); 402 } 403 404 protected function newResourceTemplate($name) { 405 $resource = id(new DrydockResource()) 406 ->setBlueprintPHID($this->getInstance()->getPHID()) 407 ->setBlueprintClass($this->getBlueprintClass()) 408 ->setType($this->getType()) 409 ->setStatus(DrydockResourceStatus::STATUS_PENDING) 410 ->setName($name) 411 ->save(); 412 413 $this->activeResource = $resource; 414 415 $this->log( 416 pht( 417 "Blueprint '%s': Created New Template", 418 $this->getBlueprintClass())); 419 420 return $resource; 421 } 422 423 /** 424 * Sanity checks that the blueprint is implemented properly. 425 */ 426 private function validateAllocatedResource($resource) { 427 $blueprint = $this->getBlueprintClass(); 428 429 if (!($resource instanceof DrydockResource)) { 430 throw new Exception( 431 "Blueprint '{$blueprint}' is not properly implemented: ". 432 "executeAllocateResource() must return an object of type ". 433 "DrydockResource or throw, but returned something else."); 434 } 435 436 $current_status = $resource->getStatus(); 437 $req_status = DrydockResourceStatus::STATUS_OPEN; 438 if ($current_status != $req_status) { 439 $current_name = DrydockResourceStatus::getNameForStatus($current_status); 440 $req_name = DrydockResourceStatus::getNameForStatus($req_status); 441 throw new Exception( 442 "Blueprint '{$blueprint}' is not properly implemented: ". 443 "executeAllocateResource() must return a DrydockResource with ". 444 "status '{$req_name}', but returned one with status ". 445 "'{$current_name}'."); 446 } 447 } 448 449 private function pushActiveScope( 450 DrydockResource $resource = null, 451 DrydockLease $lease = null) { 452 453 if (($this->activeResource !== null) || 454 ($this->activeLease !== null)) { 455 throw new Exception('There is already an active resource or lease!'); 456 } 457 458 $this->activeResource = $resource; 459 $this->activeLease = $lease; 460 461 return new DrydockBlueprintScopeGuard($this); 462 } 463 464 public function popActiveScope() { 465 $this->activeResource = null; 466 $this->activeLease = null; 467 } 468 469 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |