[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/drydock/blueprint/ -> DrydockBlueprintImplementation.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1