Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
index 5b794d3479..43e9535b7c 100644
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -1,455 +1,456 @@
<?php
/**
* @task lease Lease Acquisition
* @task resource Resource Allocation
* @task log Logging
*/
abstract class DrydockBlueprintImplementation {
private $activeResource;
private $activeLease;
private $instance;
abstract public function getType();
abstract public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type);
abstract public function isEnabled();
abstract public function getDescription();
public function getBlueprintClass() {
return get_class($this);
}
protected function loadLease($lease_id) {
// TODO: Get rid of this?
$query = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($lease_id))
->execute();
$lease = idx($query, $lease_id);
if (!$lease) {
throw new Exception("No such lease '{$lease_id}'!");
}
return $lease;
}
protected function getInstance() {
if (!$this->instance) {
throw new Exception(
"Attach the blueprint instance to the implementation.");
}
return $this->instance;
}
public function attachInstance(DrydockBlueprint $instance) {
$this->instance = $instance;
return $this;
}
/* -( Lease Acquisition )-------------------------------------------------- */
/**
* @task lease
*/
final public function filterResource(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope($resource, $lease);
return $this->canAllocateLease($resource, $lease);
}
/**
* Enforce basic checks on lease/resource compatibility. Allows resources to
* reject leases if they are incompatible, even if the resource types match.
*
* For example, if a resource represents a 32-bit host, this method might
* reject leases that need a 64-bit host. If a resource represents a working
* copy of repository "X", this method might reject leases which need a
* working copy of repository "Y". Generally, although the main types of
* a lease and resource may match (e.g., both "host"), it may not actually be
* possible to satisfy the lease with a specific resource.
*
* This method generally should not enforce limits or perform capacity
* checks. Perform those in @{method:shouldAllocateLease} instead. It also
* should not perform actual acquisition of the lease; perform that in
* @{method:executeAcquireLease} instead.
*
* @param DrydockResource Candidiate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @return bool True if the resource and lease are compatible.
* @task lease
*/
abstract protected function canAllocateLease(
DrydockResource $resource,
DrydockLease $lease);
/**
* @task lease
*/
final public function allocateLease(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope($resource, $lease);
$this->log('Trying to Allocate Lease');
$lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING);
$lease->setResourceID($resource->getID());
$lease->attachResource($resource);
$ephemeral_lease = id(clone $lease)->makeEphemeral();
$allocated = false;
$allocation_exception = null;
$resource->openTransaction();
$resource->beginReadLocking();
$resource->reload();
+ // TODO: Policy stuff.
$other_leases = id(new DrydockLease())->loadAllWhere(
'status IN (%Ld) AND resourceID = %d',
array(
DrydockLeaseStatus::STATUS_ACQUIRING,
DrydockLeaseStatus::STATUS_ACTIVE,
),
$resource->getID());
try {
$allocated = $this->shouldAllocateLease(
$resource,
$ephemeral_lease,
$other_leases);
} catch (Exception $ex) {
$allocation_exception = $ex;
}
if ($allocated) {
$lease->save();
}
$resource->endReadLocking();
if ($allocated) {
$resource->saveTransaction();
$this->log('Allocated Lease');
} else {
$resource->killTransaction();
$this->log('Failed to Allocate Lease');
}
if ($allocation_exception) {
$this->logException($allocation_exception);
}
return $allocated;
}
/**
* Enforce lease limits on resources. Allows resources to reject leases if
* they would become over-allocated by accepting them.
*
* For example, if a resource represents disk space, this method might check
* how much space the lease is asking for (say, 200MB) and how much space is
* left unallocated on the resource. It could grant the lease (return true)
* if it has enough remaining space (more than 200MB), and reject the lease
* (return false) if it does not (less than 200MB).
*
* A resource might also allow only exclusive leases. In this case it could
* accept a new lease (return true) if there are no active leases, or reject
* the new lease (return false) if there any other leases.
*
* A lock is held on the resource while this method executes to prevent
* multiple processes from allocating leases on the resource simultaneously.
* However, this means you should implement the method as cheaply as possible.
* In particular, do not perform any actual acquisition or setup in this
* method.
*
* If allocation is permitted, the lease will be moved to `ACQUIRING` status
* and @{method:executeAcquireLease} will be called to actually perform
* acquisition.
*
* General compatibility checks unrelated to resource limits and capacity are
* better implemented in @{method:canAllocateLease}, which serves as a
* cheap filter before lock acquisition.
*
* @param DrydockResource Candidate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @param list<DrydockLease> Other allocated and acquired leases on the
* resource. The implementation can inspect them
* to verify it can safely add the new lease.
* @return bool True to allocate the lease on the resource;
* false to reject it.
* @task lease
*/
abstract protected function shouldAllocateLease(
DrydockResource $resource,
DrydockLease $lease,
array $other_leases);
/**
* @task lease
*/
final public function acquireLease(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope($resource, $lease);
$this->log('Acquiring Lease');
$lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE);
$lease->setResourceID($resource->getID());
$lease->attachResource($resource);
$ephemeral_lease = id(clone $lease)->makeEphemeral();
try {
$this->executeAcquireLease($resource, $ephemeral_lease);
} catch (Exception $ex) {
$this->logException($ex);
throw $ex;
}
$lease->setAttributes($ephemeral_lease->getAttributes());
$lease->save();
$this->log('Acquired Lease');
}
/**
* Acquire and activate an allocated lease. Allows resources to peform setup
* as leases are brought online.
*
* Following a successful call to @{method:canAllocateLease}, a lease is moved
* to `ACQUIRING` status and this method is called after resource locks are
* released. Nothing is locked while this method executes; the implementation
* is free to perform expensive operations like writing files and directories,
* executing commands, etc.
*
* After this method executes, the lease status is moved to `ACTIVE` and the
* original leasee may access it.
*
* If acquisition fails, throw an exception.
*
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Lease to acquire.
* @return void
*/
abstract protected function executeAcquireLease(
DrydockResource $resource,
DrydockLease $lease);
final public function releaseLease(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope(null, $lease);
$released = false;
$lease->openTransaction();
$lease->beginReadLocking();
$lease->reload();
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) {
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
$lease->save();
$released = true;
}
$lease->endReadLocking();
$lease->saveTransaction();
if (!$released) {
throw new Exception("Unable to release lease: lease not active!");
}
}
/* -( Resource Allocation )------------------------------------------------ */
public function canAllocateMoreResources(array $pool) {
return true;
}
abstract protected function executeAllocateResource(DrydockLease $lease);
final public function allocateResource(DrydockLease $lease) {
$scope = $this->pushActiveScope(null, $lease);
$this->log(
pht(
"Blueprint '%s': Allocating Resource for '%s'",
$this->getBlueprintClass(),
$lease->getLeaseName()));
try {
$resource = $this->executeAllocateResource($lease);
$this->validateAllocatedResource($resource);
} catch (Exception $ex) {
$this->logException($ex);
throw $ex;
}
return $resource;
}
/* -( Logging )------------------------------------------------------------ */
/**
* @task log
*/
protected function logException(Exception $ex) {
$this->log($ex->getMessage());
}
/**
* @task log
*/
protected function log($message) {
self::writeLog(
$this->activeResource,
$this->activeLease,
$message);
}
/**
* @task log
*/
public static function writeLog(
DrydockResource $resource = null,
DrydockLease $lease = null,
$message) {
$log = id(new DrydockLog())
->setEpoch(time())
->setMessage($message);
if ($resource) {
$log->setResourceID($resource->getID());
}
if ($lease) {
$log->setLeaseID($lease->getID());
}
$log->save();
}
public static function getAllBlueprintImplementations() {
static $list = null;
if ($list === null) {
$blueprints = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass('DrydockBlueprintImplementation')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$list = ipull($blueprints, 'name', 'name');
foreach ($list as $class_name => $ignored) {
$list[$class_name] = newv($class_name, array());
}
}
return $list;
}
public static function getAllBlueprintImplementationsForResource($type) {
static $groups = null;
if ($groups === null) {
$groups = mgroup(self::getAllBlueprintImplementations(), 'getType');
}
return idx($groups, $type, array());
}
protected function newResourceTemplate($name) {
- $resource = new DrydockResource();
- $resource->setBlueprintPHID($this->getInstance()->getPHID());
- $resource->setBlueprintClass($this->getBlueprintClass());
- $resource->setType($this->getType());
- $resource->setStatus(DrydockResourceStatus::STATUS_PENDING);
- $resource->setName($name);
- $resource->save();
+ $resource = id(new DrydockResource())
+ ->setBlueprintPHID($this->getInstance()->getPHID())
+ ->setBlueprintClass($this->getBlueprintClass())
+ ->setType($this->getType())
+ ->setStatus(DrydockResourceStatus::STATUS_PENDING)
+ ->setName($name)
+ ->save();
$this->activeResource = $resource;
$this->log(
pht(
"Blueprint '%s': Created New Template",
$this->getBlueprintClass()));
return $resource;
}
/**
* Sanity checks that the blueprint is implemented properly.
*/
private function validateAllocatedResource($resource) {
$blueprint = $this->getBlueprintClass();
if (!($resource instanceof DrydockResource)) {
throw new Exception(
"Blueprint '{$blueprint}' is not properly implemented: ".
"executeAllocateResource() must return an object of type ".
"DrydockResource or throw, but returned something else.");
}
$current_status = $resource->getStatus();
$req_status = DrydockResourceStatus::STATUS_OPEN;
if ($current_status != $req_status) {
$current_name = DrydockResourceStatus::getNameForStatus($current_status);
$req_name = DrydockResourceStatus::getNameForStatus($req_status);
throw new Exception(
"Blueprint '{$blueprint}' is not properly implemented: ".
"executeAllocateResource() must return a DrydockResource with ".
"status '{$req_name}', but returned one with status ".
"'{$current_name}'.");
}
}
private function pushActiveScope(
DrydockResource $resource = null,
DrydockLease $lease = null) {
if (($this->activeResource !== null) ||
($this->activeLease !== null)) {
throw new Exception("There is already an active resource or lease!");
}
$this->activeResource = $resource;
$this->activeLease = $lease;
return new DrydockBlueprintScopeGuard($this);
}
public function popActiveScope() {
$this->activeResource = null;
$this->activeLease = null;
}
}
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
index b4b7cd976c..4bfdfa433a 100644
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -1,108 +1,109 @@
<?php
final class DrydockWorkingCopyBlueprintImplementation
extends DrydockBlueprintImplementation {
public function isEnabled() {
return true;
}
public function getDescription() {
return pht('Allocates out working copies of repositories.');
}
protected function canAllocateLease(
DrydockResource $resource,
DrydockLease $lease) {
$resource_repo = $resource->getAttribute('repositoryID');
$lease_repo = $lease->getAttribute('repositoryID');
return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo));
}
protected function shouldAllocateLease(
DrydockResource $resource,
DrydockLease $lease,
array $other_leases) {
return !$other_leases;
}
protected function executeAllocateResource(DrydockLease $lease) {
$repository_id = $lease->getAttribute('repositoryID');
if (!$repository_id) {
throw new Exception(
"Lease is missing required 'repositoryID' attribute.");
}
// TODO: (T603) Figure out the interaction between policies and
// Drydock.
$repository = id(new PhabricatorRepository())->load($repository_id);
if (!$repository) {
throw new Exception(
"Repository '{$repository_id}' does not exist!");
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception("Unsupported VCS!");
}
+ // TODO: Policy stuff here too.
$host_lease = id(new DrydockLease())
->setResourceType('host')
->waitUntilActive();
$path = $host_lease->getAttribute('path').$repository->getCallsign();
$this->log(
pht('Cloning %s into %s....', $repository->getCallsign(), $path));
$cmd = $host_lease->getInterface('command');
$cmd->execx(
'git clone --origin origin %P %s',
$repository->getRemoteURIEnvelope(),
$path);
$this->log(pht('Complete.'));
$resource = $this->newResourceTemplate(
'Working Copy ('.$repository->getCallsign().')');
$resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
$resource->setAttribute('lease.host', $host_lease->getID());
$resource->setAttribute('path', $path);
$resource->setAttribute('repositoryID', $repository->getID());
$resource->save();
return $resource;
}
protected function executeAcquireLease(
DrydockResource $resource,
DrydockLease $lease) {
return;
}
public function getType() {
return 'working-copy';
}
public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type) {
switch ($type) {
case 'command':
return $this
->loadLease($resource->getAttribute('lease.host'))
->getInterface($type);
}
throw new Exception("No interface of type '{$type}'.");
}
}
diff --git a/src/applications/drydock/controller/DrydockBlueprintCreateController.php b/src/applications/drydock/controller/DrydockBlueprintCreateController.php
index d45980334a..3b0909a90b 100644
--- a/src/applications/drydock/controller/DrydockBlueprintCreateController.php
+++ b/src/applications/drydock/controller/DrydockBlueprintCreateController.php
@@ -1,65 +1,65 @@
<?php
final class DrydockBlueprintCreateController
extends DrydockBlueprintController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
if ($request->isFormPost()) {
$class = $request->getStr('blueprint-type');
if (!isset($implementations[$class])) {
return $this->createDialog($implementations);
}
- $blueprint = new DrydockBlueprint();
- $blueprint->setClassName($class);
- $blueprint->setDetails(array());
- $blueprint->setViewPolicy(PhabricatorPolicies::POLICY_ADMIN);
- $blueprint->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN);
- $blueprint->save();
+ $blueprint = id(new DrydockBlueprint())
+ ->setClassName($class)
+ ->setDetails(array())
+ ->setViewPolicy(PhabricatorPolicies::POLICY_ADMIN)
+ ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
+ ->save();
$edit_uri = $this->getApplicationURI(
"blueprint/edit/".$blueprint->getID()."/");
return id(new AphrontRedirectResponse())->setURI($edit_uri);
}
return $this->createDialog($implementations);
}
function createDialog(array $implementations) {
$request = $this->getRequest();
$viewer = $request->getUser();
$control = id(new AphrontFormRadioButtonControl())
->setName('blueprint-type');
foreach ($implementations as $implementation_name => $implementation) {
$control
->addButton(
$implementation_name,
$implementation->getBlueprintClass(),
$implementation->getDescription());
}
$dialog = new AphrontDialogView();
$dialog->setTitle(pht('Create New Blueprint'))
->setUser($viewer)
->addSubmitButton(pht('Create Blueprint'))
->addCancelButton($this->getApplicationURI('blueprint/'));
$dialog->appendChild(
phutil_tag(
'p',
array(),
pht(
'Select what type of blueprint you want to create: ')));
$dialog->appendChild($control);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php
index 740bbb8644..f73f1d455b 100644
--- a/src/applications/drydock/controller/DrydockBlueprintViewController.php
+++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php
@@ -1,98 +1,101 @@
<?php
final class DrydockBlueprintViewController extends DrydockBlueprintController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
- $user = $request->getUser();
+ $viewer = $request->getUser();
- $blueprint = id(new DrydockBlueprint())->load($this->id);
+ $blueprint = id(new DrydockBlueprintQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
$title = 'Blueprint '.$blueprint->getID().' '.$blueprint->getClassName();
$header = id(new PHUIHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($blueprint);
$properties = $this->buildPropertyListView($blueprint, $actions);
$blueprint_uri = 'blueprint/'.$blueprint->getID().'/';
$blueprint_uri = $this->getApplicationURI($blueprint_uri);
$resources = id(new DrydockResourceQuery())
->withBlueprintPHIDs(array($blueprint->getPHID()))
- ->setViewer($user)
+ ->setViewer($viewer)
->execute();
$resource_list = $this->buildResourceListView($resources);
$resource_list->setNoDataString(pht('This blueprint has no resources.'));
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($blueprint_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID()));
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$resource_list
),
array(
'device' => true,
'title' => $title,
));
}
private function buildActionListView(DrydockBlueprint $blueprint) {
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($blueprint);
$uri = '/blueprint/edit/'.$blueprint->getID().'/';
$uri = $this->getApplicationURI($uri);
$view->addAction(
id(new PhabricatorActionView())
->setHref($uri)
->setName(pht('Edit Blueprint Policies'))
->setIcon('edit')
->setWorkflow(true)
->setDisabled(false));
return $view;
}
private function buildPropertyListView(
DrydockBlueprint $blueprint,
PhabricatorActionListView $actions) {
$view = new PHUIPropertyListView();
$view->setActionList($actions);
$view->addProperty(
pht('Implementation'),
$blueprint->getClassName());
return $view;
}
}
diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php
index 96bb9c2952..cbf83d6120 100644
--- a/src/applications/drydock/controller/DrydockLeaseViewController.php
+++ b/src/applications/drydock/controller/DrydockLeaseViewController.php
@@ -1,135 +1,138 @@
<?php
final class DrydockLeaseViewController extends DrydockLeaseController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
- $user = $request->getUser();
+ $viewer = $request->getUser();
- $lease = id(new DrydockLease())->load($this->id);
+ $lease = id(new DrydockLeaseQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
if (!$lease) {
return new Aphront404Response();
}
$lease_uri = $this->getApplicationURI('lease/'.$lease->getID().'/');
$title = pht('Lease %d', $lease->getID());
$header = id(new PHUIHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($lease);
$properties = $this->buildPropertyListView($lease, $actions);
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($lease_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$logs = id(new DrydockLogQuery())
- ->setViewer($user)
+ ->setViewer($viewer)
->withLeaseIDs(array($lease->getID()))
->executeWithOffsetPager($pager);
$log_table = $this->buildLogTableView($logs);
$log_table->appendChild($pager);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addTextCrumb($title, $lease_uri);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$log_table,
),
array(
'device' => true,
'title' => $title,
));
}
private function buildActionListView(DrydockLease $lease) {
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($lease);
$id = $lease->getID();
$can_release = ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Release Lease'))
->setIcon('delete')
->setHref($this->getApplicationURI("/lease/{$id}/release/"))
->setWorkflow(true)
->setDisabled(!$can_release));
return $view;
}
private function buildPropertyListView(
DrydockLease $lease,
PhabricatorActionListView $actions) {
$view = new PHUIPropertyListView();
$view->setActionList($actions);
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
$status = pht('Active');
break;
case DrydockLeaseStatus::STATUS_RELEASED:
$status = pht('Released');
break;
case DrydockLeaseStatus::STATUS_EXPIRED:
$status = pht('Expired');
break;
case DrydockLeaseStatus::STATUS_PENDING:
$status = pht('Pending');
break;
case DrydockLeaseStatus::STATUS_BROKEN:
$status = pht('Broken');
break;
default:
$status = pht('Unknown');
break;
}
$view->addProperty(
pht('Status'),
$status);
$view->addProperty(
pht('Resource Type'),
$lease->getResourceType());
$view->addProperty(
pht('Resource'),
$lease->getResourceID());
$attributes = $lease->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}
}
return $view;
}
}
diff --git a/src/applications/drydock/controller/DrydockResourceCloseController.php b/src/applications/drydock/controller/DrydockResourceCloseController.php
index 8936814878..7963612ee4 100644
--- a/src/applications/drydock/controller/DrydockResourceCloseController.php
+++ b/src/applications/drydock/controller/DrydockResourceCloseController.php
@@ -1,52 +1,55 @@
<?php
final class DrydockResourceCloseController extends DrydockResourceController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
- $user = $request->getUser();
+ $viewer = $request->getUser();
- $resource = id(new DrydockResource())->load($this->id);
+ $resource = id(new DrydockResourceQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$resource_uri = '/resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) {
$dialog = id(new AphrontDialogView())
- ->setUser($user)
+ ->setUser($viewer)
->setTitle(pht('Resource Not Open'))
->appendChild(phutil_tag('p', array(), pht(
'You can only close "open" resources.')))
->addCancelButton($resource_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
- if (!$request->isDialogFormPost()) {
- $dialog = id(new AphrontDialogView())
- ->setUser($user)
- ->setTitle(pht('Really close resource?'))
- ->appendChild(phutil_tag('p', array(), pht(
- 'Closing a resource releases all leases and destroys the '.
- 'resource. It can not be undone. Continue?')))
- ->addSubmitButton(pht('Close Resource'))
- ->addCancelButton($resource_uri);
-
- return id(new AphrontDialogResponse())->setDialog($dialog);
+ if ($request->isFormPost()) {
+ $resource->closeResource();
+ return id(new AphrontReloadResponse())->setURI($resource_uri);
}
- $resource->closeResource();
+ $dialog = id(new AphrontDialogView())
+ ->setUser($viewer)
+ ->setTitle(pht('Really close resource?'))
+ ->appendChild(
+ pht(
+ 'Closing a resource releases all leases and destroys the '.
+ 'resource. It can not be undone. Continue?'))
+ ->addSubmitButton(pht('Close Resource'))
+ ->addCancelButton($resource_uri);
- return id(new AphrontReloadResponse())->setURI($resource_uri);
+ return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php
index d2f7e87c3b..e8e7340a88 100644
--- a/src/applications/drydock/controller/DrydockResourceViewController.php
+++ b/src/applications/drydock/controller/DrydockResourceViewController.php
@@ -1,128 +1,131 @@
<?php
final class DrydockResourceViewController extends DrydockResourceController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
- $user = $request->getUser();
+ $viewer = $request->getUser();
- $resource = id(new DrydockResource())->load($this->id);
+ $resource = id(new DrydockResourceQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$title = 'Resource '.$resource->getID().' '.$resource->getName();
$header = id(new PHUIHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($resource);
$properties = $this->buildPropertyListView($resource, $actions);
$resource_uri = 'resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
$leases = id(new DrydockLeaseQuery())
- ->setViewer($user)
+ ->setViewer($viewer)
->withResourceIDs(array($resource->getID()))
->execute();
$lease_list = $this->buildLeaseListView($leases);
$lease_list->setNoDataString(pht('This resource has no leases.'));
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($resource_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$logs = id(new DrydockLogQuery())
- ->setViewer($user)
+ ->setViewer($viewer)
->withResourceIDs(array($resource->getID()))
->executeWithOffsetPager($pager);
$log_table = $this->buildLogTableView($logs);
$log_table->appendChild($pager);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addTextCrumb(pht('Resource %d', $resource->getID()));
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$lease_list,
$log_table,
),
array(
'device' => true,
'title' => $title,
));
}
private function buildActionListView(DrydockResource $resource) {
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($resource);
$can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN);
$uri = '/resource/'.$resource->getID().'/close/';
$uri = $this->getApplicationURI($uri);
$view->addAction(
id(new PhabricatorActionView())
->setHref($uri)
->setName(pht('Close Resource'))
->setIcon('delete')
->setWorkflow(true)
->setDisabled(!$can_close));
return $view;
}
private function buildPropertyListView(
DrydockResource $resource,
PhabricatorActionListView $actions) {
$view = new PHUIPropertyListView();
$view->setActionList($actions);
$status = $resource->getStatus();
$status = DrydockResourceStatus::getNameForStatus($status);
$view->addProperty(
pht('Status'),
$status);
$view->addProperty(
pht('Resource Type'),
$resource->getType());
// TODO: Load handle.
$view->addProperty(
pht('Blueprint'),
$resource->getBlueprintPHID());
$attributes = $resource->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}
}
return $view;
}
}
diff --git a/src/applications/drydock/management/DrydockManagementCloseWorkflow.php b/src/applications/drydock/management/DrydockManagementCloseWorkflow.php
index 3d30f7f802..1261778c78 100644
--- a/src/applications/drydock/management/DrydockManagementCloseWorkflow.php
+++ b/src/applications/drydock/management/DrydockManagementCloseWorkflow.php
@@ -1,42 +1,49 @@
<?php
final class DrydockManagementCloseWorkflow
extends DrydockManagementWorkflow {
public function didConstruct() {
$this
->setName('close')
->setSynopsis('Close a resource.')
->setArguments(
array(
array(
'name' => 'ids',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$ids = $args->getArg('ids');
if (!$ids) {
throw new PhutilArgumentUsageException(
"Specify one or more resource IDs to close.");
}
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $resources = id(new DrydockResourceQuery())
+ ->setViewer($viewer)
+ ->withIDs($ids)
+ ->execute();
+
foreach ($ids as $id) {
- $resource = id(new DrydockResource())->load($id);
+ $resource = idx($resources, $id);
if (!$resource) {
$console->writeErr("Resource %d does not exist!\n", $id);
} else if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) {
$console->writeErr("Resource %d is not 'open'!\n", $id);
} else {
$resource->closeResource();
$console->writeErr("Closed resource %d.\n", $id);
}
}
}
}
diff --git a/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php
index c93220380e..40899c1bb1 100644
--- a/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php
+++ b/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php
@@ -1,72 +1,77 @@
<?php
final class DrydockManagementCreateResourceWorkflow
extends DrydockManagementWorkflow {
public function didConstruct() {
$this
->setName('create-resource')
->setSynopsis('Create a resource manually.')
->setArguments(
array(
array(
'name' => 'name',
'param' => 'resource_name',
'help' => 'Resource name.',
),
array(
'name' => 'blueprint',
'param' => 'blueprint_id',
'help' => 'Blueprint ID.',
),
array(
'name' => 'attributes',
'param' => 'name=value,...',
'help' => 'Resource attributes.',
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$resource_name = $args->getArg('name');
if (!$resource_name) {
throw new PhutilArgumentUsageException(
"Specify a resource name with `--name`.");
}
$blueprint_id = $args->getArg('blueprint');
if (!$blueprint_id) {
throw new PhutilArgumentUsageException(
"Specify a blueprint ID with `--blueprint`.");
}
$attributes = $args->getArg('attributes');
if ($attributes) {
$options = new PhutilSimpleOptions();
$options->setCaseSensitive(true);
$attributes = $options->parse($attributes);
}
- $blueprint = id(new DrydockBlueprint())->load((int)$blueprint_id);
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $blueprint = id(new DrydockBlueprintQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($blueprint_id))
+ ->executeOne();
if (!$blueprint) {
throw new PhutilArgumentUsageException(
"Specified blueprint does not exist.");
}
- $resource = new DrydockResource();
- $resource->setBlueprintPHID($blueprint->getPHID());
- $resource->setType($blueprint->getImplementation()->getType());
- $resource->setName($resource_name);
- $resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
+ $resource = id(new DrydockResource())
+ ->setBlueprintPHID($blueprint->getPHID())
+ ->setType($blueprint->getImplementation()->getType())
+ ->setName($resource_name)
+ ->setStatus(DrydockResourceStatus::STATUS_OPEN);
if ($attributes) {
$resource->setAttributes($attributes);
}
$resource->save();
$console->writeOut("Created Resource %s\n", $resource->getID());
return 0;
}
}
diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php
index 4220fa0547..6dd08d00e3 100644
--- a/src/applications/drydock/query/DrydockLeaseQuery.php
+++ b/src/applications/drydock/query/DrydockLeaseQuery.php
@@ -1,105 +1,113 @@
<?php
final class DrydockLeaseQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $resourceIDs;
private $statuses;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withResourceIDs(array $ids) {
$this->resourceIDs = $ids;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function loadPage() {
$table = new DrydockLease();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT lease.* FROM %T lease %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
public function willFilterPage(array $leases) {
- $resources = id(new DrydockResourceQuery())
- ->setParentQuery($this)
- ->setViewer($this->getViewer())
- ->withIDs(mpull($leases, 'getResourceID'))
- ->execute();
+ $resource_ids = array_filter(mpull($leases, 'getResourceID'));
+ if ($resource_ids) {
+ $resources = id(new DrydockResourceQuery())
+ ->setParentQuery($this)
+ ->setViewer($this->getViewer())
+ ->withIDs($resource_ids)
+ ->execute();
+ } else {
+ $resources = array();
+ }
foreach ($leases as $key => $lease) {
- $resource = idx($resources, $lease->getResourceID());
- if (!$resource) {
- unset($leases[$key]);
- continue;
+ $resource = null;
+ if ($lease->getResourceID()) {
+ $resource = idx($resources, $lease->getResourceID());
+ if (!$resource) {
+ unset($leases[$key]);
+ continue;
+ }
}
$lease->attachResource($resource);
}
return $leases;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->resourceIDs) {
$where[] = qsprintf(
$conn_r,
'resourceID IN (%Ld)',
$this->resourceIDs);
}
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->statuses) {
$where[] = qsprintf(
$conn_r,
'status IN (%Ld)',
$this->statuses);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationDrydock';
}
}
diff --git a/src/applications/drydock/query/DrydockLogQuery.php b/src/applications/drydock/query/DrydockLogQuery.php
index 712f5a6714..880c56a77c 100644
--- a/src/applications/drydock/query/DrydockLogQuery.php
+++ b/src/applications/drydock/query/DrydockLogQuery.php
@@ -1,75 +1,117 @@
<?php
final class DrydockLogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $resourceIDs;
private $leaseIDs;
public function withResourceIDs(array $ids) {
$this->resourceIDs = $ids;
return $this;
}
public function withLeaseIDs(array $ids) {
$this->leaseIDs = $ids;
return $this;
}
public function loadPage() {
$table = new DrydockLog();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT log.* FROM %T log %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
public function willFilterPage(array $logs) {
- $resource_ids = mpull($logs, 'getResourceID');
- $resources = id(new DrydockResourceQuery())
- ->setParentQuery($this)
- ->setViewer($this->getViewer())
- ->withIDs($resource_ids)
- ->execute();
+ $resource_ids = array_filter(mpull($logs, 'getResourceID'));
+ if ($resource_ids) {
+ $resources = id(new DrydockResourceQuery())
+ ->setParentQuery($this)
+ ->setViewer($this->getViewer())
+ ->withIDs($resource_ids)
+ ->execute();
+ } else {
+ $resources = array();
+ }
foreach ($logs as $key => $log) {
- $resource = idx($resources, $log->getResourceID());
+ $resource = null;
+ if ($log->getResourceID()) {
+ $resource = idx($resources, $log->getResourceID());
+ if (!$resource) {
+ unset($logs[$key]);
+ continue;
+ }
+ }
$log->attachResource($resource);
}
+ $lease_ids = array_filter(mpull($logs, 'getLeaseID'));
+ if ($lease_ids) {
+ $leases = id(new DrydockLeaseQuery())
+ ->setParentQuery($this)
+ ->setViewer($this->getViewer())
+ ->withIDs($lease_ids)
+ ->execute();
+ } else {
+ $leases = array();
+ }
+
+ foreach ($logs as $key => $log) {
+ $lease = null;
+ if ($log->getLeaseID()) {
+ $lease = idx($leases, $log->getLeaseID());
+ if (!$lease) {
+ unset($logs[$key]);
+ continue;
+ }
+ }
+ $log->attachLease($lease);
+ }
+
+ // These logs are meaningless and their policies aren't computable. They
+ // shouldn't exist, but throw them away if they do.
+ foreach ($logs as $key => $log) {
+ if (!$log->getResource() && !$log->getLease()) {
+ unset($logs[$key]);
+ }
+ }
+
return $logs;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->resourceIDs) {
$where[] = qsprintf(
$conn_r,
'resourceID IN (%Ld)',
$this->resourceIDs);
}
if ($this->leaseIDs) {
$where[] = qsprintf(
$conn_r,
'leaseID IN (%Ld)',
$this->leaseIDs);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationDrydock';
}
}
diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php
index 2c39cfcb0a..26312ef51c 100644
--- a/src/applications/drydock/storage/DrydockLease.php
+++ b/src/applications/drydock/storage/DrydockLease.php
@@ -1,208 +1,214 @@
<?php
final class DrydockLease extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourceID;
protected $resourceType;
protected $until;
protected $ownerPHID;
protected $attributes = array();
protected $status = DrydockLeaseStatus::STATUS_PENDING;
protected $taskID;
private $resource = self::ATTACHABLE;
private $releaseOnDestruction;
/**
* Flag this lease to be released when its destructor is called. This is
* mostly useful if you have a script which acquires, uses, and then releases
* a lease, as you don't need to explicitly handle exceptions to properly
* release the lease.
*/
public function releaseOnDestruction() {
$this->releaseOnDestruction = true;
return $this;
}
public function __destruct() {
if ($this->releaseOnDestruction) {
if ($this->isActive()) {
$this->release();
}
}
}
public function getLeaseName() {
return pht('Lease %d', $this->getID());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attributes' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(DrydockPHIDTypeLease::TYPECONST);
}
public function getInterface($type) {
return $this->getResource()->getInterface($this, $type);
}
public function getResource() {
return $this->assertAttached($this->resource);
}
- public function attachResource(DrydockResource $resource) {
+ public function attachResource(DrydockResource $resource = null) {
$this->resource = $resource;
return $this;
}
public function hasAttachedResource() {
return ($this->resource !== null);
}
public function loadResource() {
return id(new DrydockResource())->loadOneWhere(
'id = %d',
$this->getResourceID());
}
public function queueForActivation() {
if ($this->getID()) {
throw new Exception(
"Only new leases may be queued for activation!");
}
$this->setStatus(DrydockLeaseStatus::STATUS_PENDING);
$this->save();
$task = PhabricatorWorker::scheduleTask(
'DrydockAllocatorWorker',
$this->getID());
// NOTE: Scheduling the task might execute it in-process, if we're running
// from a CLI script. Reload the lease to make sure we have the most
// up-to-date information. Normally, this has no effect.
$this->reload();
$this->setTaskID($task->getID());
$this->save();
return $this;
}
public function release() {
$this->assertActive();
$this->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
$this->save();
$this->resource = null;
return $this;
}
public function isActive() {
switch ($this->status) {
case DrydockLeaseStatus::STATUS_ACTIVE:
case DrydockLeaseStatus::STATUS_ACQUIRING:
return true;
}
return false;
}
private function assertActive() {
if (!$this->isActive()) {
throw new Exception(
"Lease is not active! You can not interact with resources through ".
"an inactive lease.");
}
}
public static function waitForLeases(array $leases) {
assert_instances_of($leases, 'DrydockLease');
$task_ids = array_filter(mpull($leases, 'getTaskID'));
PhabricatorWorker::waitForTasks($task_ids);
$unresolved = $leases;
while (true) {
foreach ($unresolved as $key => $lease) {
$lease->reload();
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
unset($unresolved[$key]);
break;
case DrydockLeaseStatus::STATUS_RELEASED:
throw new Exception("Lease has already been released!");
case DrydockLeaseStatus::STATUS_EXPIRED:
throw new Exception("Lease has already expired!");
case DrydockLeaseStatus::STATUS_BROKEN:
throw new Exception("Lease has been broken!");
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRING:
break;
default:
throw new Exception("Unknown status??");
}
}
if ($unresolved) {
sleep(1);
} else {
break;
}
}
foreach ($leases as $lease) {
$lease->attachResource($lease->loadResource());
}
}
public function waitUntilActive() {
if (!$this->getID()) {
$this->queueForActivation();
}
self::waitForLeases(array($this));
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
- return $this->getResource()->getPolicy($capability);
+ if ($this->getResource()) {
+ return $this->getResource()->getPolicy($capability);
+ }
+ return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- return $this->getResource()->hasAutomaticCapability($capability, $viewer);
+ if ($this->getResource()) {
+ return $this->getResource()->hasAutomaticCapability($capability, $viewer);
+ }
+ return false;
}
public function describeAutomaticCapability($capability) {
return pht('Leases inherit policies from the resources they lease.');
}
}
diff --git a/src/applications/drydock/storage/DrydockLog.php b/src/applications/drydock/storage/DrydockLog.php
index b6384f2dac..a34114e321 100644
--- a/src/applications/drydock/storage/DrydockLog.php
+++ b/src/applications/drydock/storage/DrydockLog.php
@@ -1,56 +1,66 @@
<?php
final class DrydockLog extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourceID;
protected $leaseID;
protected $epoch;
protected $message;
private $resource = self::ATTACHABLE;
+ private $lease = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function attachResource(DrydockResource $resource = null) {
$this->resource = $resource;
return $this;
}
public function getResource() {
return $this->assertAttached($this->resource);
}
+ public function attachLease(DrydockLease $lease = null) {
+ $this->lease = $lease;
+ return $this;
+ }
+
+ public function getLease() {
+ return $this->assertAttached($this->lease);
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
- if (!$this->getResource()) {
- return PhabricatorPolicies::getMostOpenPolicy();
+ if ($this->getResource()) {
+ return $this->getResource()->getPolicy($capability);
}
- return $this->getResource()->getPolicy($capability);
+ return $this->getLease()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- if (!$this->getResource()) {
- return false;
+ if ($this->getResource()) {
+ return $this->getResource()->hasAutomaticCapability($capability, $viewer);
}
- return $this->getResource()->hasAutomaticCapability($capability, $viewer);
+ return $this->getLease()->hasAutomaticCapability($capability, $viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Logs inherit the policy of their resources.');
}
}
diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php
index bb9b9ebdb5..ba2e6a8012 100644
--- a/src/applications/drydock/storage/DrydockResource.php
+++ b/src/applications/drydock/storage/DrydockResource.php
@@ -1,117 +1,121 @@
<?php
final class DrydockResource extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $id;
protected $phid;
protected $blueprintPHID;
protected $status;
protected $type;
protected $name;
protected $attributes = array();
protected $capabilities = array();
protected $ownerPHID;
private $blueprint;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attributes' => self::SERIALIZATION_JSON,
'capabilities' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(DrydockPHIDTypeResource::TYPECONST);
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
public function getAttributesForTypeSpec(array $attribute_names) {
return array_select_keys($this->attributes, $attribute_names);
}
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
return $this;
}
public function getCapability($key, $default = null) {
return idx($this->capbilities, $key, $default);
}
public function getInterface(DrydockLease $lease, $type) {
return $this->getBlueprint()->getInterface($this, $lease, $type);
}
public function getBlueprint() {
+ // TODO: Policy stuff.
if (empty($this->blueprint)) {
$blueprint = id(new DrydockBlueprint())
->loadOneWhere('phid = %s', $this->blueprintPHID);
$this->blueprint = $blueprint->getImplementation();
}
return $this->blueprint;
}
public function closeResource() {
$this->openTransaction();
- $leases = id(new DrydockLease())->loadAllWhere(
- 'resourceID = %d AND status IN (%Ld)',
- $this->getID(),
- array(
- DrydockLeaseStatus::STATUS_PENDING,
- DrydockLeaseStatus::STATUS_ACTIVE,
- ));
+ $statuses = array(
+ DrydockLeaseStatus::STATUS_PENDING,
+ DrydockLeaseStatus::STATUS_ACTIVE,
+ );
+
+ $leases = id(new DrydockLeaseQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withResourceIDs(array($this->getID()))
+ ->withStatuses($statuses)
+ ->execute();
foreach ($leases as $lease) {
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_PENDING:
$message = pht('Breaking pending lease (resource closing).');
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
break;
case DrydockLeaseStatus::STATUS_ACTIVE:
$message = pht('Releasing active lease (resource closing).');
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
break;
}
DrydockBlueprintImplementation::writeLog($this, $lease, $message);
$lease->save();
}
$this->setStatus(DrydockResourceStatus::STATUS_CLOSED);
$this->save();
$this->saveTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php
index dfc481b6a0..6c2064c587 100644
--- a/src/applications/drydock/worker/DrydockAllocatorWorker.php
+++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php
@@ -1,176 +1,183 @@
<?php
final class DrydockAllocatorWorker extends PhabricatorWorker {
private $lease;
public function getMaximumRetryCount() {
// TODO: Allow Drydock allocations to retry. For now, every failure is
// permanent and most of them are because I am bad at programming, so fail
// fast rather than ending up in limbo.
return 0;
}
private function loadLease() {
if (empty($this->lease)) {
- $lease = id(new DrydockLease())->load($this->getTaskData());
+ $lease = id(new DrydockLeaseQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withIDs(array($this->getTaskData()))
+ ->executeOne();
if (!$lease) {
throw new PhabricatorWorkerPermanentFailureException(
- "No such lease!");
+ pht("No such lease %d!", $this->getTaskData()));
}
$this->lease = $lease;
}
return $this->lease;
}
private function logToDrydock($message) {
DrydockBlueprintImplementation::writeLog(
null,
$this->loadLease(),
$message);
}
protected function doWork() {
$lease = $this->loadLease();
$this->logToDrydock('Allocating Lease');
try {
$this->allocateLease($lease);
} catch (Exception $ex) {
// TODO: We should really do this when archiving the task, if we've
// suffered a permanent failure. But we don't have hooks for that yet
// and always fail after the first retry right now, so this is
// functionally equivalent.
$lease->reload();
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_PENDING) {
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
$lease->save();
}
throw $ex;
}
}
private function loadAllBlueprints() {
- $instances = id(new DrydockBlueprint())->loadAll();
+ $viewer = PhabricatorUser::getOmnipotentUser();
+ $instances = id(new DrydockBlueprintQuery())
+ ->setViewer($viewer)
+ ->execute();
$blueprints = array();
foreach ($instances as $instance) {
$blueprints[$instance->getPHID()] = $instance;
}
return $blueprints;
}
private function allocateLease(DrydockLease $lease) {
$type = $lease->getResourceType();
$blueprints = $this->loadAllBlueprints();
+ // TODO: Policy stuff.
$pool = id(new DrydockResource())->loadAllWhere(
'type = %s AND status = %s',
$lease->getResourceType(),
DrydockResourceStatus::STATUS_OPEN);
$this->logToDrydock(
pht('Found %d Open Resource(s)', count($pool)));
$candidates = array();
foreach ($pool as $key => $candidate) {
if (!isset($blueprints[$candidate->getBlueprintPHID()])) {
unset($pool[$key]);
continue;
}
$blueprint = $blueprints[$candidate->getBlueprintPHID()];
$implementation = $blueprint->getImplementation();
if ($implementation->filterResource($candidate, $lease)) {
$candidates[] = $candidate;
}
}
$this->logToDrydock(pht('%d Open Resource(s) Remain', count($candidates)));
$resource = null;
if ($candidates) {
shuffle($candidates);
foreach ($candidates as $candidate_resource) {
$blueprint = $blueprints[$candidate_resource->getBlueprintPHID()]
->getImplementation();
if ($blueprint->allocateLease($candidate_resource, $lease)) {
$resource = $candidate_resource;
break;
}
}
}
if (!$resource) {
$blueprints = DrydockBlueprintImplementation
::getAllBlueprintImplementationsForResource($type);
$this->logToDrydock(
pht('Found %d Blueprints', count($blueprints)));
foreach ($blueprints as $key => $candidate_blueprint) {
if (!$candidate_blueprint->isEnabled()) {
unset($blueprints[$key]);
continue;
}
}
$this->logToDrydock(
pht('%d Blueprints Enabled', count($blueprints)));
foreach ($blueprints as $key => $candidate_blueprint) {
if (!$candidate_blueprint->canAllocateMoreResources($pool)) {
unset($blueprints[$key]);
continue;
}
}
$this->logToDrydock(
pht('%d Blueprints Can Allocate', count($blueprints)));
if (!$blueprints) {
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
$lease->save();
$this->logToDrydock(
"There are no resources of type '{$type}' available, and no ".
"blueprints which can allocate new ones.");
return;
}
// TODO: Rank intelligently.
shuffle($blueprints);
$blueprint = head($blueprints);
$resource = $blueprint->allocateResource($lease);
if (!$blueprint->allocateLease($resource, $lease)) {
// TODO: This "should" happen only if we lost a race with another lease,
// which happened to acquire this resource immediately after we
// allocated it. In this case, the right behavior is to retry
// immediately. However, other things like a blueprint allocating a
// resource it can't actually allocate the lease on might be happening
// too, in which case we'd just allocate infinite resources. Probably
// what we should do is test for an active or allocated lease and retry
// if we find one (although it might have already been released by now)
// and fail really hard ("your configuration is a huge broken mess")
// otherwise. But just throw for now since this stuff is all edge-casey.
// Alternatively we could bring resources up in a "BESPOKE" status
// and then switch them to "OPEN" only after the allocating lease gets
// its grubby mitts on the resource. This might make more sense but
// is a bit messy.
throw new Exception("Lost an allocation race?");
}
}
$blueprint = $resource->getBlueprint();
$blueprint->acquireLease($resource, $lease);
}
}
diff --git a/src/applications/harbormaster/step/LeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/LeaseHostBuildStepImplementation.php
index cd230aa9b6..3814c148a0 100644
--- a/src/applications/harbormaster/step/LeaseHostBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/LeaseHostBuildStepImplementation.php
@@ -1,85 +1,87 @@
<?php
final class LeaseHostBuildStepImplementation
extends BuildStepImplementation {
public function getName() {
return pht('Lease Host');
}
public function getGenericDescription() {
return pht('Obtain a lease on a Drydock host for performing builds.');
}
public function getDescription() {
$settings = $this->getSettings();
return pht(
'Obtain a lease on a Drydock host whose platform is \'%s\' and store '.
'the resulting lease in a host artifact called \'%s\'.',
$settings['platform'],
$settings['name']);
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
$settings = $this->getSettings();
// Create the lease.
- $lease = new DrydockLease();
- $lease->setResourceType('host');
- $lease->setAttributes(
- array('platform' => $settings['platform']));
- $lease->queueForActivation();
+ $lease = id(new DrydockLease())
+ ->setResourceType('host')
+ ->setAttributes(
+ array(
+ 'platform' => $settings['platform'],
+ ))
+ ->queueForActivation();
// Wait until the lease is fulfilled.
// TODO: This will throw an exception if the lease can't be fulfilled;
// we should treat that as build failure not build error.
$lease->waitUntilActive();
// Create the associated artifact.
$artifact = $build->createArtifact(
$build_target,
$settings['name'],
HarbormasterBuildArtifact::TYPE_HOST);
$artifact->setArtifactData(array(
'drydock-lease' => $lease->getID()));
$artifact->save();
}
public function getArtifactMappings() {
$settings = $this->getSettings();
return array(
$settings['name'] => HarbormasterBuildArtifact::TYPE_HOST);
}
public function validateSettings() {
$settings = $this->getSettings();
if ($settings['name'] === null || !is_string($settings['name'])) {
return false;
}
if ($settings['platform'] === null || !is_string($settings['platform'])) {
return false;
}
return true;
}
public function getSettingDefinitions() {
return array(
'name' => array(
'name' => 'Artifact Name',
'description' =>
'The name of the artifact to reference in future build steps.',
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
'platform' => array(
'name' => 'Platform',
'description' => 'The platform of the host.',
'type' => BuildStepImplementation::SETTING_TYPE_STRING));
}
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
index 0511c526a9..4854e64b5d 100644
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
@@ -1,146 +1,147 @@
<?php
final class HarbormasterBuildArtifact extends HarbormasterDAO
implements PhabricatorPolicyInterface {
protected $buildTargetPHID;
protected $artifactType;
protected $artifactIndex;
protected $artifactKey;
protected $artifactData = array();
private $buildTarget = self::ATTACHABLE;
const TYPE_FILE = 'file';
const TYPE_HOST = 'host';
public static function initializeNewBuildArtifact(
HarbormasterBuildTarget $build_target) {
return id(new HarbormasterBuildArtifact())
->setBuildTargetPHID($build_target->getPHID());
}
public function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'artifactData' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function attachBuildTarget(HarbormasterBuildTarget $build_target) {
$this->buildTarget = $build_target;
return $this;
}
public function getBuildTarget() {
return $this->assertAttached($this->buildTarget);
}
public function setArtifactKey($build_phid, $key) {
$this->artifactIndex =
PhabricatorHash::digestForIndex($build_phid.$key);
$this->artifactKey = $key;
return $this;
}
public function getObjectItemView(PhabricatorUser $viewer) {
$data = $this->getArtifactData();
switch ($this->getArtifactType()) {
case self::TYPE_FILE:
$handle = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($data)
->executeOne();
return id(new PHUIObjectItemView())
->setObjectName(pht('File'))
->setHeader($handle->getFullName())
->setHref($handle->getURI());
case self::TYPE_HOST:
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs(array($data["drydock-lease"]))
->execute();
$lease = $leases[$data["drydock-lease"]];
return id(new PHUIObjectItemView())
->setObjectName(pht('Drydock Lease'))
->setHeader($lease->getID())
->setHref('/drydock/lease/'.$lease->getID());
default:
return null;
}
}
public function loadDrydockLease() {
if ($this->getArtifactType() !== self::TYPE_HOST) {
throw new Exception(
"`loadDrydockLease` may only be called on host artifacts.");
}
$data = $this->getArtifactData();
// FIXME: Is there a better way of doing this?
+ // TODO: Policy stuff, etc.
$lease = id(new DrydockLease())->load(
$data['drydock-lease']);
if ($lease === null) {
throw new Exception("Associated Drydock lease not found!");
}
$resource = id(new DrydockResource())->load(
$lease->getResourceID());
if ($resource === null) {
throw new Exception("Associated Drydock resource not found!");
}
$lease->attachResource($resource);
return $lease;
}
public function loadPhabricatorFile() {
if ($this->getArtifactType() !== self::TYPE_FILE) {
throw new Exception(
"`loadPhabricatorFile` may only be called on file artifacts.");
}
$data = $this->getArtifactData();
// The data for TYPE_FILE is an array with a single PHID in it.
$phid = $data["filePHID"];
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($phid))
->executeOne();
if ($file === null) {
throw new Exception("Associated file not found!");
}
return $file;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getBuildTarget()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBuildTarget()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht(
'Users must be able to see a buildable to see its artifacts.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 3, 12:14 AM (6 h, 5 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
432985
Default Alt Text
(70 KB)

Event Timeline