Page MenuHomePhabricator

D14117.diff
No OneTemporary

D14117.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -797,6 +797,7 @@
'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php',
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php',
+ 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php',
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
@@ -845,7 +846,6 @@
'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php',
'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php',
- 'DrydockManagementCreateResourceWorkflow' => 'applications/drydock/management/DrydockManagementCreateResourceWorkflow.php',
'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php',
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
@@ -4502,6 +4502,7 @@
'DoorkeeperTagView' => 'AphrontView',
'DoorkeeperTagsController' => 'PhabricatorController',
'DrydockAllocatorWorker' => 'PhabricatorWorker',
+ 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
'DrydockBlueprint' => array(
'DrydockDAO',
@@ -4564,7 +4565,6 @@
'DrydockLogQuery' => 'DrydockQuery',
'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow',
- 'DrydockManagementCreateResourceWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
@@ -0,0 +1,234 @@
+<?php
+
+final class DrydockAlmanacServiceHostBlueprintImplementation
+ extends DrydockBlueprintImplementation {
+
+ private $services;
+ private $freeBindings;
+
+ public function isEnabled() {
+ $almanac_app = 'PhabricatorAlmanacApplication';
+ return PhabricatorApplication::isClassInstalled($almanac_app);
+ }
+
+ public function getBlueprintName() {
+ return pht('Almanac Hosts');
+ }
+
+ public function getDescription() {
+ return pht(
+ 'Allows Drydock to lease existing hosts defined in an Almanac service '.
+ 'pool.');
+ }
+
+ public function canAnyBlueprintEverAllocateResourceForLease(
+ DrydockLease $lease) {
+ return true;
+ }
+
+ public function canEverAllocateResourceForLease(
+ DrydockBlueprint $blueprint,
+ DrydockLease $lease) {
+ $services = $this->loadServices($blueprint);
+ $bindings = $this->loadAllBindings($services);
+
+ if (!$bindings) {
+ // If there are no devices bound to the services for this blueprint,
+ // we can not allocate resources.
+ return false;
+ }
+
+ return true;
+ }
+
+ public function canAllocateResourceForLease(
+ DrydockBlueprint $blueprint,
+ DrydockLease $lease) {
+
+ // We will only allocate one resource per unique device bound to the
+ // services for this blueprint. Make sure we have a free device somewhere.
+ $free_bindings = $this->loadFreeBindings($blueprint);
+ if (!$free_bindings) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function allocateResource(
+ DrydockBlueprint $blueprint,
+ DrydockLease $lease) {
+
+ $free_bindings = $this->loadFreeBindings($blueprint);
+ shuffle($free_bindings);
+
+ $exceptions = array();
+ foreach ($free_bindings as $binding) {
+ $device = $binding->getDevice();
+ $device_name = $device->getName();
+
+ $resource = $this->newResourceTemplate($blueprint, $device_name)
+ ->setActivateWhenAllocated(true)
+ ->setAttribute('almanacServicePHID', $binding->getServicePHID())
+ ->setAttribute('almanacBindingPHID', $binding->getPHID());
+
+ // TODO: This algorithm can race, and the "free" binding may not be
+ // free by the time we acquire it. Do slot-locking here if that works
+ // out, or some other kind of locking if it does not.
+
+ try {
+ return $resource->allocateResource(DrydockResourceStatus::STATUS_OPEN);
+ } catch (Exception $ex) {
+ $exceptions[] = $ex;
+ }
+ }
+
+ throw new PhutilAggregateException(
+ pht('Unable to allocate any binding as a resource.'),
+ $exceptions);
+ }
+
+ public function canAcquireLeaseOnResource(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ // TODO: We'll currently lease each resource an unlimited number of times,
+ // but should stop doing that.
+
+ return true;
+ }
+
+ public function acquireLease(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ // TODO: Once we have limit rules, we should perform slot locking (or other
+ // kinds of locking) here.
+
+ $lease
+ ->setActivateWhenAcquired(true)
+ ->acquireOnResource($resource);
+ }
+
+ public function getType() {
+ return 'host';
+ }
+
+ public function getInterface(
+ DrydockResource $resource,
+ DrydockLease $lease,
+ $type) {
+ // TODO: Actually do stuff here, this needs work and currently makes this
+ // entire exercise pointless.
+ }
+
+ public function getFieldSpecifications() {
+ return array(
+ 'almanacServicePHIDs' => array(
+ 'name' => pht('Almanac Services'),
+ 'type' => 'datasource',
+ 'datasource.class' => 'AlmanacServiceDatasource',
+ 'datasource.parameters' => array(
+ 'serviceClasses' => $this->getAlmanacServiceClasses(),
+ ),
+ 'required' => true,
+ ),
+ 'credentialPHID' => array(
+ 'name' => pht('Credentials'),
+ 'type' => 'credential',
+ 'credential.provides' =>
+ PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE,
+ 'credential.type' =>
+ PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,
+ ),
+ ) + parent::getFieldSpecifications();
+ }
+
+ private function loadServices(DrydockBlueprint $blueprint) {
+ if (!$this->services) {
+ $service_phids = $blueprint->getFieldValue('almanacServicePHIDs');
+ if (!$service_phids) {
+ throw new Exception(
+ pht(
+ 'This blueprint ("%s") does not define any Almanac Service PHIDs.',
+ $blueprint->getBlueprintName()));
+ }
+
+ $viewer = PhabricatorUser::getOmnipotentUser();
+ $services = id(new AlmanacServiceQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($service_phids)
+ ->withServiceClasses($this->getAlmanacServiceClasses())
+ ->needBindings(true)
+ ->execute();
+ $services = mpull($services, null, 'getPHID');
+
+ if (count($services) != count($service_phids)) {
+ $missing_phids = array_diff($service_phids, array_keys($services));
+ throw new Exception(
+ pht(
+ 'Some of the Almanac Services defined by this blueprint '.
+ 'could not be loaded. They may be invalid, no longer exist, '.
+ 'or be of the wrong type: %s.',
+ implode(', ', $missing_phids)));
+ }
+
+ $this->services = $services;
+ }
+
+ return $this->services;
+ }
+
+ private function loadAllBindings(array $services) {
+ assert_instances_of($services, 'AlmanacService');
+ $bindings = array_mergev(mpull($services, 'getBindings'));
+ return mpull($bindings, null, 'getPHID');
+ }
+
+ private function loadFreeBindings(DrydockBlueprint $blueprint) {
+ if ($this->freeBindings === null) {
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $pool = id(new DrydockResourceQuery())
+ ->setViewer($viewer)
+ ->withBlueprintPHIDs(array($blueprint->getPHID()))
+ ->withStatuses(
+ array(
+ DrydockResourceStatus::STATUS_PENDING,
+ DrydockResourceStatus::STATUS_OPEN,
+ DrydockResourceStatus::STATUS_CLOSED,
+ ))
+ ->execute();
+
+ $allocated_phids = array();
+ foreach ($pool as $resource) {
+ $allocated_phids[] = $resource->getAttribute('almanacDevicePHID');
+ }
+ $allocated_phids = array_fuse($allocated_phids);
+
+ $services = $this->loadServices($blueprint);
+ $bindings = $this->loadAllBindings($services);
+
+ $free = array();
+ foreach ($bindings as $binding) {
+ if (empty($allocated_phids[$binding->getPHID()])) {
+ $free[] = $binding;
+ }
+ }
+
+ $this->freeBindings = $free;
+ }
+
+ return $this->freeBindings;
+ }
+
+ private function getAlmanacServiceClasses() {
+ return array(
+ 'AlmanacDrydockPoolServiceType',
+ );
+ }
+
+
+}
diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -60,10 +60,6 @@
return array();
}
- public function getDetail($key, $default = null) {
- return $this->getInstance()->getDetail($key, $default);
- }
-
/* -( Lease Acquisition )-------------------------------------------------- */
@@ -86,171 +82,29 @@
* @return bool True if the resource and lease are compatible.
* @task lease
*/
- abstract public function canAllocateLeaseOnResource(
+ abstract public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
- * @task lease
- */
- final public function allocateLease(
- DrydockResource $resource,
- DrydockLease $lease) {
-
- $scope = $this->pushActiveScope($resource, $lease);
-
- $this->log(pht('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(pht('Allocated Lease'));
- } else {
- $resource->killTransaction();
- $this->log(pht('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(pht('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(pht('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.
+ * Acquire a lease. Allows resources to peform setup as leases are brought
+ * online.
*
* If acquisition fails, throw an exception.
*
- * @param DrydockResource Resource to acquire a lease on.
- * @param DrydockLease Lease to acquire.
- * @return void
+ * @param DrydockBlueprint Blueprint which built the resource.
+ * @param DrydockResource Resource to acquire a lease on.
+ * @param DrydockLease Requested lease.
+ * @return void
+ * @task lease
*/
- abstract protected function executeAcquireLease(
+ abstract public function acquireLease(
+ DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
-
-
final public function releaseLease(
DrydockResource $resource,
DrydockLease $lease) {
@@ -352,6 +206,7 @@
* @param DrydockLease Requested lease.
* @return bool True if this blueprint appears likely to be able to allocate
* a suitable resource.
+ * @task resource
*/
abstract public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
@@ -369,34 +224,12 @@
* @param DrydockBlueprint The blueprint which should allocate a resource.
* @param DrydockLease Requested lease.
* @return DrydockResource Allocated resource.
+ * @task resource
*/
- abstract protected function executeAllocateResource(
+ abstract public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease);
- final public function allocateResource(
- DrydockBlueprint $blueprint,
- DrydockLease $lease) {
-
- $scope = $this->pushActiveScope(null, $lease);
-
- $this->log(
- pht(
- "Blueprint '%s': Allocating Resource for '%s'",
- $this->getBlueprintClass(),
- $lease->getLeaseName()));
-
- try {
- $resource = $this->executeAllocateResource($blueprint, $lease);
- $this->validateAllocatedResource($resource);
- } catch (Exception $ex) {
- $this->logException($ex);
- throw $ex;
- }
-
- return $resource;
- }
-
/* -( Logging )------------------------------------------------------------ */
@@ -454,14 +287,15 @@
return idx(self::getAllBlueprintImplementations(), $class);
}
- protected function newResourceTemplate($name) {
+ protected function newResourceTemplate(
+ DrydockBlueprint $blueprint,
+ $name) {
+
$resource = id(new DrydockResource())
- ->setBlueprintPHID($this->getInstance()->getPHID())
- ->setBlueprintClass($this->getBlueprintClass())
+ ->setBlueprintPHID($blueprint->getPHID())
->setType($this->getType())
->setStatus(DrydockResourceStatus::STATUS_PENDING)
- ->setName($name)
- ->save();
+ ->setName($name);
$this->activeResource = $resource;
@@ -473,39 +307,6 @@
return $resource;
}
- /**
- * Sanity checks that the blueprint is implemented properly.
- */
- private function validateAllocatedResource($resource) {
- $blueprint = $this->getBlueprintClass();
-
- if (!($resource instanceof DrydockResource)) {
- throw new Exception(
- pht(
- "Blueprint '%s' is not properly implemented: %s must return an ".
- "object of type %s or throw, but returned something else.",
- $blueprint,
- 'executeAllocateResource()',
- 'DrydockResource'));
- }
-
- $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(
- pht(
- "Blueprint '%s' is not properly implemented: %s must return a %s ".
- "with status '%s', but returned one with status '%s'.",
- $blueprint,
- 'executeAllocateResource()',
- 'DrydockResource',
- $req_name,
- $current_name));
- }
- }
-
private function pushActiveScope(
DrydockResource $resource = null,
DrydockLease $lease = null) {
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -35,7 +35,7 @@
return true;
}
- public function canAllocateLeaseOnResource(
+ public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
@@ -47,15 +47,7 @@
return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo));
}
- protected function shouldAllocateLease(
- DrydockResource $resource,
- DrydockLease $lease,
- array $other_leases) {
- // TODO: These checks are out of date.
- return !$other_leases;
- }
-
- protected function executeAllocateResource(
+ public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
@@ -105,6 +97,7 @@
$this->log(pht('Complete.'));
$resource = $this->newResourceTemplate(
+ $blueprint,
pht(
'Working Copy (%s)',
$repository->getCallsign()));
@@ -117,7 +110,8 @@
return $resource;
}
- protected function executeAcquireLease(
+ public function acquireLease(
+ DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
return;
diff --git a/src/applications/drydock/constants/DrydockLeaseStatus.php b/src/applications/drydock/constants/DrydockLeaseStatus.php
--- a/src/applications/drydock/constants/DrydockLeaseStatus.php
+++ b/src/applications/drydock/constants/DrydockLeaseStatus.php
@@ -3,7 +3,7 @@
final class DrydockLeaseStatus extends DrydockConstants {
const STATUS_PENDING = 0;
- const STATUS_ACQUIRING = 5;
+ const STATUS_ACQUIRED = 5;
const STATUS_ACTIVE = 1;
const STATUS_RELEASED = 2;
const STATUS_BROKEN = 3;
@@ -12,7 +12,7 @@
public static function getNameForStatus($status) {
$map = array(
self::STATUS_PENDING => pht('Pending'),
- self::STATUS_ACQUIRING => pht('Acquiring'),
+ self::STATUS_ACQUIRED => pht('Acquired'),
self::STATUS_ACTIVE => pht('Active'),
self::STATUS_RELEASED => pht('Released'),
self::STATUS_BROKEN => pht('Broken'),
@@ -25,7 +25,7 @@
public static function getAllStatuses() {
return array(
self::STATUS_PENDING,
- self::STATUS_ACQUIRING,
+ self::STATUS_ACQUIRED,
self::STATUS_ACTIVE,
self::STATUS_RELEASED,
self::STATUS_BROKEN,
diff --git a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php
--- a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php
+++ b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php
@@ -40,4 +40,8 @@
return;
}
+ public function getBlueprintFieldValue() {
+ return $this->getProxy()->getFieldValue();
+ }
+
}
diff --git a/src/applications/drydock/customfield/DrydockBlueprintCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCustomField.php
--- a/src/applications/drydock/customfield/DrydockBlueprintCustomField.php
+++ b/src/applications/drydock/customfield/DrydockBlueprintCustomField.php
@@ -1,4 +1,8 @@
<?php
abstract class DrydockBlueprintCustomField
- extends PhabricatorCustomField {}
+ extends PhabricatorCustomField {
+
+ abstract public function getBlueprintFieldValue();
+
+}
diff --git a/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php b/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php
deleted file mode 100644
--- a/src/applications/drydock/management/DrydockManagementCreateResourceWorkflow.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-
-final class DrydockManagementCreateResourceWorkflow
- extends DrydockManagementWorkflow {
-
- protected function didConstruct() {
- $this
- ->setName('create-resource')
- ->setSynopsis(pht('Create a resource manually.'))
- ->setArguments(
- array(
- array(
- 'name' => 'name',
- 'param' => 'resource_name',
- 'help' => pht('Resource name.'),
- ),
- array(
- 'name' => 'blueprint',
- 'param' => 'blueprint_id',
- 'help' => pht('Blueprint ID.'),
- ),
- array(
- 'name' => 'attributes',
- 'param' => 'name=value,...',
- 'help' => pht('Resource attributes.'),
- ),
- ));
- }
-
- public function execute(PhutilArgumentParser $args) {
- $console = PhutilConsole::getConsole();
-
- $resource_name = $args->getArg('name');
- if (!$resource_name) {
- throw new PhutilArgumentUsageException(
- pht(
- 'Specify a resource name with `%s`.',
- '--name'));
- }
-
- $blueprint_id = $args->getArg('blueprint');
- if (!$blueprint_id) {
- throw new PhutilArgumentUsageException(
- pht(
- 'Specify a blueprint ID with `%s`.',
- '--blueprint'));
- }
-
- $attributes = $args->getArg('attributes');
- if ($attributes) {
- $options = new PhutilSimpleOptions();
- $options->setCaseSensitive(true);
- $attributes = $options->parse($attributes);
- }
-
- $viewer = $this->getViewer();
-
- $blueprint = id(new DrydockBlueprintQuery())
- ->setViewer($viewer)
- ->withIDs(array($blueprint_id))
- ->executeOne();
- if (!$blueprint) {
- throw new PhutilArgumentUsageException(
- pht('Specified blueprint does not exist.'));
- }
-
- $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("%s\n", pht('Created Resource %s', $resource->getID()));
- return 0;
- }
-
-}
diff --git a/src/applications/drydock/query/DrydockLeaseSearchEngine.php b/src/applications/drydock/query/DrydockLeaseSearchEngine.php
--- a/src/applications/drydock/query/DrydockLeaseSearchEngine.php
+++ b/src/applications/drydock/query/DrydockLeaseSearchEngine.php
@@ -74,7 +74,7 @@
'statuses',
array(
DrydockLeaseStatus::STATUS_PENDING,
- DrydockLeaseStatus::STATUS_ACQUIRING,
+ DrydockLeaseStatus::STATUS_ACQUIRED,
DrydockLeaseStatus::STATUS_ACTIVE,
));
case 'all':
diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php
--- a/src/applications/drydock/storage/DrydockBlueprint.php
+++ b/src/applications/drydock/storage/DrydockBlueprint.php
@@ -1,5 +1,9 @@
<?php
+/**
+ * @task resource Allocating Resources
+ * @task lease Acquiring Leases
+ */
final class DrydockBlueprint extends DrydockDAO
implements
PhabricatorApplicationTransactionInterface,
@@ -14,6 +18,7 @@
private $implementation = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
+ private $fields = null;
public static function initializeNewBlueprint(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
@@ -68,27 +73,96 @@
return $this;
}
+ public function getFieldValue($key) {
+ $key = "std:drydock:core:{$key}";
+ $fields = $this->loadCustomFields();
+
+ $field = idx($fields, $key);
+ if (!$field) {
+ throw new Exception(
+ pht(
+ 'Unknown blueprint field "%s"!',
+ $key));
+ }
+
+ return $field->getBlueprintFieldValue();
+ }
+
+ private function loadCustomFields() {
+ if ($this->fields === null) {
+ $field_list = PhabricatorCustomField::getObjectFields(
+ $this,
+ PhabricatorCustomField::ROLE_VIEW);
+ $field_list->readFieldsFromStorage($this);
+
+ $this->fields = $field_list->getFields();
+ }
+ return $this->fields;
+ }
+
+
+/* -( Allocating Resources )----------------------------------------------- */
+
+
+ /**
+ * @task resource
+ */
public function canEverAllocateResourceForLease(DrydockLease $lease) {
return $this->getImplementation()->canEverAllocateResourceForLease(
$this,
$lease);
}
+
+ /**
+ * @task resource
+ */
public function canAllocateResourceForLease(DrydockLease $lease) {
return $this->getImplementation()->canAllocateResourceForLease(
$this,
$lease);
}
- public function canAllocateLeaseOnResource(
+
+ /**
+ * @task resource
+ */
+ public function allocateResource(DrydockLease $lease) {
+ return $this->getImplementation()->allocateResource(
+ $this,
+ $lease);
+ }
+
+
+/* -( Acquiring Leases )--------------------------------------------------- */
+
+
+ /**
+ * @task lease
+ */
+ public function canAcquireLeaseOnResource(
DrydockResource $resource,
DrydockLease $lease) {
- return $this->getImplementation()->canAllocateLeaseOnResource(
+ return $this->getImplementation()->canAcquireLeaseOnResource(
$this,
$resource,
$lease);
}
+
+ /**
+ * @task lease
+ */
+ public function acquireLease(
+ DrydockResource $resource,
+ DrydockLease $lease) {
+ return $this->getImplementation()->acquireLease(
+ $this,
+ $resource,
+ $lease);
+ }
+
+
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php
--- a/src/applications/drydock/storage/DrydockLease.php
+++ b/src/applications/drydock/storage/DrydockLease.php
@@ -13,6 +13,8 @@
private $resource = self::ATTACHABLE;
private $releaseOnDestruction;
+ private $isAcquired = false;
+ private $activateWhenAcquired = false;
/**
* Flag this lease to be released when its destructor is called. This is
@@ -133,8 +135,8 @@
public function isActive() {
switch ($this->status) {
+ case DrydockLeaseStatus::STATUS_ACQUIRED:
case DrydockLeaseStatus::STATUS_ACTIVE:
- case DrydockLeaseStatus::STATUS_ACQUIRING:
return true;
}
return false;
@@ -171,7 +173,7 @@
case DrydockLeaseStatus::STATUS_BROKEN:
throw new Exception(pht('Lease has been broken!'));
case DrydockLeaseStatus::STATUS_PENDING:
- case DrydockLeaseStatus::STATUS_ACQUIRING:
+ case DrydockLeaseStatus::STATUS_ACQUIRED:
break;
default:
throw new Exception(pht('Unknown status??'));
@@ -199,6 +201,53 @@
return $this;
}
+ public function setActivateWhenAcquired($activate) {
+ $this->activateWhenAcquired = true;
+ return $this;
+ }
+
+ public function acquireOnResource(DrydockResource $resource) {
+ $expect_status = DrydockLeaseStatus::STATUS_PENDING;
+ $actual_status = $this->getStatus();
+ if ($actual_status != $expect_status) {
+ throw new Exception(
+ pht(
+ 'Trying to acquire a lease on a resource which is in the wrong '.
+ 'state: status must be "%s", actually "%s".',
+ $expect_status,
+ $actual_status));
+ }
+
+ if ($this->activateWhenAcquired) {
+ $new_status = DrydockLeaseStatus::STATUS_ACTIVE;
+ } else {
+ $new_status = DrydockLeaseStatus::STATUS_PENDING;
+ }
+
+ if ($new_status === DrydockLeaseStatus::STATUS_ACTIVE) {
+ if ($resource->getStatus() === DrydockResourceStatus::STATUS_PENDING) {
+ throw new Exception(
+ pht(
+ 'Trying to acquire an active lease on a pending resource. '.
+ 'You can not immediately activate leases on resources which '.
+ 'need time to start up.'));
+ }
+ }
+
+ $this
+ ->setResourceID($resource->getID())
+ ->setStatus($new_status)
+ ->save();
+
+ $this->isAcquired = true;
+
+ return $this;
+ }
+
+ public function isAcquiredLease() {
+ return $this->isAcquired;
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php
--- a/src/applications/drydock/storage/DrydockResource.php
+++ b/src/applications/drydock/storage/DrydockResource.php
@@ -15,6 +15,8 @@
protected $ownerPHID;
private $blueprint = self::ATTACHABLE;
+ private $isAllocated = false;
+ private $activateWhenAllocated = false;
protected function getConfiguration() {
return array(
@@ -73,10 +75,47 @@
return $this;
}
- public function canAllocateLease(DrydockLease $lease) {
- return $this->getBlueprint()->canAllocateLeaseOnResource(
- $this,
- $lease);
+ public function setActivateWhenAllocated($activate) {
+ $this->activateWhenAllocated = $activate;
+ return $this;
+ }
+
+ public function allocateResource($status) {
+ if ($this->getID()) {
+ throw new Exception(
+ pht(
+ 'Trying to allocate a resource which has already been persisted. '.
+ 'Only new resources may be allocated.'));
+ }
+
+ $expect_status = DrydockResourceStatus::STATUS_PENDING;
+ $actual_status = $this->getStatus();
+ if ($actual_status != $expect_status) {
+ throw new Exception(
+ pht(
+ 'Trying to allocate a resource from the wrong status. Status must '.
+ 'be "%s", actually "%s".',
+ $expect_status,
+ $actual_status));
+ }
+
+ if ($this->activateWhenAllocated) {
+ $new_status = DrydockResourceStatus::STATUS_OPEN;
+ } else {
+ $new_status = DrydockResourceStatus::STATUS_PENDING;
+ }
+
+ $this
+ ->setStatus($new_status)
+ ->save();
+
+ $this->didAllocate = true;
+
+ return $this;
+ }
+
+ public function isAllocatedResource() {
+ return $this->isAllocated;
}
public function closeResource() {
diff --git a/src/applications/drydock/worker/DrydockAllocatorWorker.php b/src/applications/drydock/worker/DrydockAllocatorWorker.php
--- a/src/applications/drydock/worker/DrydockAllocatorWorker.php
+++ b/src/applications/drydock/worker/DrydockAllocatorWorker.php
@@ -1,5 +1,10 @@
<?php
+/**
+ * @task allocate Allocator
+ * @task resource Managing Resources
+ * @task lease Managing Leases
+ */
final class DrydockAllocatorWorker extends PhabricatorWorker {
private function getViewer() {
@@ -27,10 +32,22 @@
protected function doWork() {
$lease = $this->loadLease();
- $this->allocateLease($lease);
+ $this->allocateAndAcquireLease($lease);
}
- private function allocateLease(DrydockLease $lease) {
+
+/* -( Allocator )---------------------------------------------------------- */
+
+
+ /**
+ * Find or build a resource which can satisfy a given lease request, then
+ * acquire the lease.
+ *
+ * @param DrydockLease Requested lease.
+ * @return void
+ * @task allocator
+ */
+ private function allocateAndAcquireLease(DrydockLease $lease) {
$blueprints = $this->loadBlueprintsForAllocatingLease($lease);
// If we get nothing back, that means no blueprint is defined which can
@@ -72,7 +89,8 @@
$exceptions = array();
foreach ($usable_blueprints as $blueprint) {
try {
- $resources[] = $blueprint->allocateResource($lease);
+ $resources[] = $this->allocateResource($blueprint, $lease);
+
// Bail after allocating one resource, we don't need any more than
// this.
break;
@@ -106,7 +124,7 @@
$allocated = false;
foreach ($resources as $resource) {
try {
- $blueprint->allocateLease($resource, $lease);
+ $this->acquireLease($resource, $lease);
$allocated = true;
break;
} catch (Exception $ex) {
@@ -129,6 +147,86 @@
/**
+ * Get all the @{class:DrydockBlueprintImplementation}s which can possibly
+ * build a resource to satisfy a lease.
+ *
+ * This method returns blueprints which might, at some time, be able to
+ * build a resource which can satisfy the lease. They may not be able to
+ * build that resource right now.
+ *
+ * @param DrydockLease Requested lease.
+ * @return list<DrydockBlueprintImplementation> List of qualifying blueprint
+ * implementations.
+ * @task allocator
+ */
+ private function loadBlueprintImplementationsForAllocatingLease(
+ DrydockLease $lease) {
+
+ $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
+
+ $keep = array();
+ foreach ($impls as $key => $impl) {
+ // Don't use disabled blueprint types.
+ if (!$impl->isEnabled()) {
+ continue;
+ }
+
+ // Don't use blueprint types which can't allocate the correct kind of
+ // resource.
+ if ($impl->getType() != $lease->getResourceType()) {
+ continue;
+ }
+
+ if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
+ continue;
+ }
+
+ $keep[$key] = $impl;
+ }
+
+ return $keep;
+ }
+
+
+ /**
+ * Get all the concrete @{class:DrydockBlueprint}s which can possibly
+ * build a resource to satisfy a lease.
+ *
+ * @param DrydockLease Requested lease.
+ * @return list<DrydockBlueprint> List of qualifying blueprints.
+ * @task allocator
+ */
+ private function loadBlueprintsForAllocatingLease(
+ DrydockLease $lease) {
+ $viewer = $this->getViewer();
+
+ $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease);
+ if (!$impls) {
+ return array();
+ }
+
+ // TODO: When blueprints can be disabled, this query should ignore disabled
+ // blueprints.
+
+ $blueprints = id(new DrydockBlueprintQuery())
+ ->setViewer($viewer)
+ ->withBlueprintClasses(array_keys($impls))
+ ->execute();
+
+ $keep = array();
+ foreach ($blueprints as $key => $blueprint) {
+ if (!$blueprint->canEverAllocateResourceForLease($lease)) {
+ continue;
+ }
+
+ $keep[$key] = $blueprint;
+ }
+
+ return $keep;
+ }
+
+
+ /**
* Load a list of all resources which a given lease can possibly be
* allocated against.
*
@@ -137,6 +235,7 @@
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Resources which may be able to allocate
* the lease.
+ * @task allocator
*/
private function loadResourcesForAllocatingLease(
array $blueprints,
@@ -157,7 +256,9 @@
$keep = array();
foreach ($resources as $key => $resource) {
- if (!$resource->canAllocateLease($lease)) {
+ $blueprint = $resource->getBlueprint();
+
+ if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {
continue;
}
@@ -169,12 +270,40 @@
/**
+ * Remove blueprints which are too heavily allocated to build a resource for
+ * a lease from a list of blueprints.
+ *
+ * @param list<DrydockBlueprint> List of blueprints.
+ * @return list<DrydockBlueprint> List with blueprints that can not allocate
+ * a resource for the lease right now removed.
+ * @task allocator
+ */
+ private function removeOverallocatedBlueprints(
+ array $blueprints,
+ DrydockLease $lease) {
+ assert_instances_of($blueprints, 'DrydockBlueprint');
+
+ $keep = array();
+ foreach ($blueprints as $key => $blueprint) {
+ if (!$blueprint->canAllocateResourceForLease($lease)) {
+ continue;
+ }
+
+ $keep[$key] = $blueprint;
+ }
+
+ return $keep;
+ }
+
+
+ /**
* Rank blueprints by suitability for building a new resource for a
* particular lease.
*
* @param list<DrydockBlueprint> List of blueprints.
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprint> Ranked list of blueprints.
+ * @task allocator
*/
private function rankBlueprints(array $blueprints, DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
@@ -193,6 +322,7 @@
* @param list<DrydockResource> List of resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Ranked list of resources.
+ * @task allocator
*/
private function rankResources(array $resources, DrydockLease $lease) {
assert_instances_of($resources, 'DrydockResource');
@@ -205,107 +335,142 @@
}
+/* -( Managing Resources )------------------------------------------------- */
+
+
/**
- * Get all the concrete @{class:DrydockBlueprint}s which can possibly
- * build a resource to satisfy a lease.
+ * Perform an actual resource allocation with a particular blueprint.
*
+ * @param DrydockBlueprint The blueprint to allocate a resource from.
* @param DrydockLease Requested lease.
- * @return list<DrydockBlueprint> List of qualifying blueprints.
+ * @return DrydockResource Allocated resource.
+ * @task resource
*/
- private function loadBlueprintsForAllocatingLease(
+ private function allocateResource(
+ DrydockBlueprint $blueprint,
DrydockLease $lease) {
- $viewer = $this->getViewer();
+ $resource = $blueprint->allocateResource($lease);
+ $this->validateAllocatedResource($resource);
+ return $resource;
+ }
- $impls = $this->loadBlueprintImplementationsForAllocatingLease($lease);
- if (!$impls) {
- return array();
+
+ /**
+ * Check that the resource a blueprint allocated is roughly the sort of
+ * object we expect.
+ *
+ * @param DrydockBlueprint Blueprint which built the resource.
+ * @param wild Thing which the blueprint claims is a valid resource.
+ * @param DrydockLease Lease the resource was allocated for.
+ * @return void
+ * @task resource
+ */
+ private function validateAllocatedResource(
+ DrydockBlueprint $blueprint,
+ $resource,
+ DrydockLease $lease) {
+ $blueprint = $this->getBlueprintClass();
+
+ if (!($resource instanceof DrydockResource)) {
+ throw new Exception(
+ pht(
+ 'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.
+ 'return an object of type %s or throw, but returned something else.',
+ $blueprint->getBlueprintName(),
+ $blueprint->getClassName(),
+ 'allocateResource()',
+ 'DrydockResource'));
}
- // TODO: When blueprints can be disabled, this query should ignore disabled
- // blueprints.
+ if (!$resource->isAllocatedResource()) {
+ throw new Exception(
+ pht(
+ 'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
+ 'must actually allocate the resource it returns.',
+ $blueprint->getBlueprintName(),
+ $blueprint->getClassName(),
+ 'allocateResource()'));
+ }
- $blueprints = id(new DrydockBlueprintQuery())
- ->setViewer($viewer)
- ->withBlueprintClasses(array_keys($impls))
- ->execute();
+ $resource_type = $resource->getType();
+ $lease_type = $lease->getResourceType();
- $keep = array();
- foreach ($blueprints as $key => $blueprint) {
- if (!$blueprint->canEverAllocateResourceForLease($lease)) {
- continue;
- }
+ if ($resource_type !== $lease_type) {
+ // TODO: Destroy the resource here?
- $keep[$key] = $blueprint;
+ throw new Exception(
+ pht(
+ 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
+ 'built a resource of type "%s" to satisfy a lease requesting a '.
+ 'resource of type "%s".',
+ $blueprint->getBlueprintName(),
+ $blueprint->getClassName(),
+ $resource_type,
+ $lease_type));
}
-
- return $keep;
}
+/* -( Managing Leases )---------------------------------------------------- */
+
+
/**
- * Get all the @{class:DrydockBlueprintImplementation}s which can possibly
- * build a resource to satisfy a lease.
+ * Perform an actual lease acquisition on a particular resource.
*
- * This method returns blueprints which might, at some time, be able to
- * build a resource which can satisfy the lease. They may not be able to
- * build that resource right now.
- *
- * @param DrydockLease Requested lease.
- * @return list<DrydockBlueprintImplementation> List of qualifying blueprint
- * implementations.
+ * @param DrydockResource Resource to acquire a lease on.
+ * @param DrydockLease Lease to acquire.
+ * @return void
+ * @task lease
*/
- private function loadBlueprintImplementationsForAllocatingLease(
+ private function acquireLease(
+ DrydockResource $resource,
DrydockLease $lease) {
- $impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
+ $blueprint = $resource->getBlueprint();
+ $blueprint->acquireLease($resource, $lease);
- $keep = array();
- foreach ($impls as $key => $impl) {
- // Don't use disabled blueprint types.
- if (!$impl->isEnabled()) {
- continue;
- }
-
- // Don't use blueprint types which can't allocate the correct kind of
- // resource.
- if ($impl->getType() != $lease->getResourceType()) {
- continue;
- }
-
- if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
- continue;
- }
-
- $keep[$key] = $impl;
- }
-
- return $keep;
+ $this->validateAcquiredLease($blueprint, $resource, $lease);
}
/**
- * Remove blueprints which are too heavily allocated to build a resource for
- * a lease from a list of blueprints.
+ * Make sure that a lease was really acquired properly.
*
- * @param list<DrydockBlueprint> List of blueprints.
- * @param list<DrydockBlueprint> List with fully allocated blueprints
- * removed.
+ * @param DrydockBlueprint Blueprint which created the resource.
+ * @param DrydockResource Resource which was acquired.
+ * @param DrydockLease The lease which was supposedly acquired.
+ * @return void
+ * @task lease
*/
- private function removeOverallocatedBlueprints(
- array $blueprints,
+ private function validateAcquiredLease(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
DrydockLease $lease) {
- assert_instances_of($blueprints, 'DrydockBlueprint');
-
- $keep = array();
- foreach ($blueprints as $key => $blueprint) {
- if (!$blueprint->canAllocateResourceForLease($lease)) {
- continue;
- }
- $keep[$key] = $blueprint;
+ if (!$lease->isAcquiredLease()) {
+ throw new Exception(
+ pht(
+ 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
+ 'returned from "%s" without acquiring a lease.',
+ $blueprint->getBlueprintName(),
+ $blueprint->getClassName(),
+ 'acquireLease()'));
}
- return $keep;
+ $lease_id = $lease->getResourceID();
+ $resource_id = $resource->getID();
+
+ if ($lease_id !== $resource_id) {
+ // TODO: Destroy the lease?
+ throw new Exception(
+ pht(
+ 'Blueprint "%s" (of type "%s") is not properly implemented: it '.
+ 'returned from "%s" with a lease acquired on the wrong resource.',
+ $blueprint->getBlueprintName(),
+ $blueprint->getClassName(),
+ 'acquireLease()'));
+ }
}
+
}
diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php
--- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php
+++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php
@@ -31,7 +31,7 @@
throw new Exception(
pht(
'Credential has noncreateable type "%s"!',
- $credential->getCredentialType()));
+ $type_const));
}
$credential = PassphraseCredential::initializeNewCredential($viewer)

File Metadata

Mime Type
text/plain
Expires
Thu, Oct 24, 5:54 PM (3 w, 5 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/mm/tv/4i4gamjgokg23gj6
Default Alt Text
D14117.diff (47 KB)

Event Timeline