1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Make various Drydock improvements

Summary:
Tightens up a bunch of stuff:

  - In `drydock lease`, pull and print logs so the user can see what's happening.
  - Remove `DrydockAllocator`, which was a dumb class that did nothing. Move the tiny amount of logic it held directly to `DrydockLease`.
  - Move `resourceType` from worker task metadata directly to `DrydockLease`. Other things (like the web UI) can be more informative with this information available.
  - Pass leases to `allocateResource()`. We always allocate in response to a lease activation request, and the lease often has vital information. This also allows us to associate logs with leases correctly.

Test Plan: Ran `drydock lease --type host` and saw it perform a host allocation in EC2.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2015

Differential Revision: https://secure.phabricator.com/D3870
This commit is contained in:
epriestley 2012-11-01 16:53:17 -07:00
parent 3844be3f83
commit 89b37f0357
12 changed files with 116 additions and 110 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
ADD resourceType VARCHAR(128) NOT NULL COLLATE utf8_bin;

View file

@ -411,8 +411,7 @@ phutil_register_library_map(array(
'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php', 'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php',
'DiffusionView' => 'applications/diffusion/view/DiffusionView.php', 'DiffusionView' => 'applications/diffusion/view/DiffusionView.php',
'DivinerListController' => 'applications/diviner/controller/DivinerListController.php', 'DivinerListController' => 'applications/diviner/controller/DivinerListController.php',
'DrydockAllocator' => 'applications/drydock/allocator/DrydockAllocator.php', 'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php',
'DrydockAllocatorWorker' => 'applications/drydock/allocator/DrydockAllocatorWorker.php',
'DrydockApacheWebrootBlueprint' => 'applications/drydock/blueprint/webroot/DrydockApacheWebrootBlueprint.php', 'DrydockApacheWebrootBlueprint' => 'applications/drydock/blueprint/webroot/DrydockApacheWebrootBlueprint.php',
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
'DrydockBlueprint' => 'applications/drydock/blueprint/DrydockBlueprint.php', 'DrydockBlueprint' => 'applications/drydock/blueprint/DrydockBlueprint.php',

View file

@ -1,59 +0,0 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DrydockAllocator {
private $resourceType;
private $lease;
public function setResourceType($resource_type) {
$this->resourceType = $resource_type;
return $this;
}
public function getResourceType() {
return $this->resourceType;
}
public function getPendingLease() {
if (!$this->lease) {
$lease = new DrydockLease();
$lease->setStatus(DrydockLeaseStatus::STATUS_PENDING);
$lease->save();
$this->lease = $lease;
}
return $lease;
}
public function allocate() {
$lease = $this->getPendingLease();
$data = array(
'type' => $this->getResourceType(),
'lease' => $lease->getID(),
);
$task = PhabricatorWorker::scheduleTask('DrydockAllocatorWorker', $data);
$lease->setTaskID($task->getID());
return $lease;
}
}

View file

@ -33,13 +33,6 @@ abstract class DrydockBlueprint {
return; return;
} }
protected function getAllocator($type) {
$allocator = new DrydockAllocator();
$allocator->setResourceType($type);
return $allocator;
}
final public function acquireLease( final public function acquireLease(
DrydockResource $resource, DrydockResource $resource,
DrydockLease $lease) { DrydockLease $lease) {
@ -101,13 +94,18 @@ abstract class DrydockBlueprint {
return false; return false;
} }
protected function executeAllocateResource() { protected function executeAllocateResource(DrydockLease $lease) {
throw new Exception("This blueprint can not allocate resources!"); throw new Exception("This blueprint can not allocate resources!");
} }
final public function allocateResource() { final public function allocateResource(DrydockLease $lease) {
$this->activeLease = $lease;
$this->activeResource = null;
$this->log('Allocating Resource');
try { try {
$resource = $this->executeAllocateResource(); $resource = $this->executeAllocateResource($lease);
} catch (Exception $ex) { } catch (Exception $ex) {
$this->logException($ex); $this->logException($ex);
$this->activeResource = null; $this->activeResource = null;

View file

@ -22,7 +22,7 @@ final class DrydockEC2HostBlueprint extends DrydockRemoteHostBlueprint {
return true; return true;
} }
public function executeAllocateResource() { public function executeAllocateResource(DrydockLease $lease) {
$resource = $this->newResourceTemplate('EC2 Host'); $resource = $this->newResourceTemplate('EC2 Host');
$resource->setStatus(DrydockResourceStatus::STATUS_ALLOCATING); $resource->setStatus(DrydockResourceStatus::STATUS_ALLOCATING);
@ -41,7 +41,7 @@ final class DrydockEC2HostBlueprint extends DrydockRemoteHostBlueprint {
$instance_id = (string)$xml->instancesSet[0]->item[0]->instanceId[0]; $instance_id = (string)$xml->instancesSet[0]->item[0]->instanceId[0];
$this->log('Started Instance: {$instance_id}'); $this->log("Started Instance: {$instance_id}");
$resource->setAttribute('instance.id', $instance_id); $resource->setAttribute('instance.id', $instance_id);
$resource->save(); $resource->save();

View file

@ -27,15 +27,16 @@ abstract class DrydockPhabricatorApplicationBlueprint
return true; return true;
} }
public function executeAllocateResource() { public function executeAllocateResource(DrydockLease $lease) {
$resource = $this->newResourceTemplate('Phabricator'); $resource = $this->newResourceTemplate('Phabricator');
$resource->setStatus(DrydockResourceStatus::STATUS_ALLOCATING); $resource->setStatus(DrydockResourceStatus::STATUS_ALLOCATING);
$resource->save(); $resource->save();
$allocator = $this->getAllocator('host'); $host = id(new DrydockLease())
$host = $allocator->allocate(); ->setResourceType('host')
->queueForActivation();
$cmd = $host->waitUntilActive()->getInterface('command'); $cmd = $host->waitUntilActive()->getInterface('command');

View file

@ -76,7 +76,7 @@ EOSETUP
$lease->save(); $lease->save();
} }
public function executeAllocateResource() { public function executeAllocateResource(DrydockLease $lease) {
$resource = $this->newResourceTemplate('Apache'); $resource = $this->newResourceTemplate('Apache');

View file

@ -31,7 +31,7 @@ final class DrydockManagementLeaseWorkflow
'help' => 'Resource type.', 'help' => 'Resource type.',
), ),
array( array(
'name' => 'spec', 'name' => 'attributes',
'param' => 'name=value,...', 'param' => 'name=value,...',
'help' => 'Resource specficiation.', 'help' => 'Resource specficiation.',
), ),
@ -47,19 +47,18 @@ final class DrydockManagementLeaseWorkflow
"Specify a resource type with `--type`."); "Specify a resource type with `--type`.");
} }
$spec = $args->getArg('spec'); $attributes = $args->getArg('attributes');
if ($spec) { if ($attributes) {
$options = new PhutilSimpleOptions(); $options = new PhutilSimpleOptions();
$spec = $options->parse($spec); $attributes = $options->parse($attributes);
} }
$allocator = new DrydockAllocator(); $lease = new DrydockLease();
$allocator->setResourceType($resource_type); $lease->setResourceType($resource_type);
if ($spec) { if ($attributes) {
// TODO: Shove this in there. $lease->setAttributes($attributes);
} }
$lease->queueForActivation();
$lease = $allocator->allocate();
$root = dirname(phutil_get_library_root('phabricator')); $root = dirname(phutil_get_library_root('phabricator'));
$wait = new ExecFuture( $wait = new ExecFuture(
@ -67,14 +66,25 @@ final class DrydockManagementLeaseWorkflow
$root.'/scripts/drydock/drydock_control.php', $root.'/scripts/drydock/drydock_control.php',
$lease->getID()); $lease->getID());
$cursor = 0;
foreach (Futures(array($wait))->setUpdateInterval(1) as $key => $future) { foreach (Futures(array($wait))->setUpdateInterval(1) as $key => $future) {
if ($future) { if ($future) {
$future->resolvex(); $future->resolvex();
break; break;
} }
// TODO: Pull logs. $logs = id(new DrydockLogQuery())
$console->writeErr("Working...\n"); ->withLeaseIDs(array($lease->getID()))
->withAfterID($cursor)
->setOrder(DrydockLogQuery::ORDER_ID)
->execute();
if ($logs) {
foreach ($logs as $log) {
$console->writeErr("%s\n", $log->getMessage());
}
$cursor = max(mpull($logs, 'getID'));
}
} }
$console->writeOut("Acquired Lease %s\n", $lease->getID()); $console->writeOut("Acquired Lease %s\n", $lease->getID());

View file

@ -18,8 +18,13 @@
final class DrydockLogQuery extends PhabricatorOffsetPagedQuery { final class DrydockLogQuery extends PhabricatorOffsetPagedQuery {
const ORDER_EPOCH = 'order-epoch';
const ORDER_ID = 'order-id';
private $resourceIDs; private $resourceIDs;
private $leaseIDs; private $leaseIDs;
private $afterID;
private $order = self::ORDER_EPOCH;
public function withResourceIDs(array $ids) { public function withResourceIDs(array $ids) {
$this->resourceIDs = $ids; $this->resourceIDs = $ids;
@ -31,26 +36,32 @@ final class DrydockLogQuery extends PhabricatorOffsetPagedQuery {
return $this; return $this;
} }
public function setOrder($order) {
$this->order = $order;
return $this;
}
public function withAfterID($id) {
$this->afterID = $id;
return $this;
}
public function execute() { public function execute() {
$table = new DrydockLog(); $table = new DrydockLog();
$conn_r = $table->establishConnection('r'); $conn_r = $table->establishConnection('r');
$where = $this->buildWhereClause($conn_r);
$order = $this->buildOrderClause($conn_r);
$limit = $this->buildLimitClause($conn_r);
$data = queryfx_all( $data = queryfx_all(
$conn_r, $conn_r,
'SELECT log.* FROM %T log %Q %Q %Q', 'SELECT log.* FROM %T log %Q %Q %Q',
$table->getTableName(), $table->getTableName(),
$where, $this->buildWhereClause($conn_r),
$order, $this->buildOrderClause($conn_r),
$limit); $this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data); return $table->loadAllFromArray($data);
} }
private function buildWhereClause($conn_r) { private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array(); $where = array();
if ($this->resourceIDs) { if ($this->resourceIDs) {
@ -67,11 +78,25 @@ final class DrydockLogQuery extends PhabricatorOffsetPagedQuery {
$this->leaseIDs); $this->leaseIDs);
} }
if ($this->afterID) {
$where[] = qsprintf(
$conn_r,
'id > %d',
$this->afterID);
}
return $this->formatWhereClause($where); return $this->formatWhereClause($where);
} }
private function buildOrderClause($conn_r) { private function buildOrderClause(AphrontDatabaseConnection $conn_r) {
switch ($this->order) {
case self::ORDER_EPOCH:
return 'ORDER BY log.epoch DESC'; return 'ORDER BY log.epoch DESC';
case self::ORDER_ID:
return 'ORDER BY id ASC';
default:
throw new Exception("Unknown order '{$this->order}'!");
}
} }
} }

View file

@ -20,10 +20,11 @@ final class DrydockLease extends DrydockDAO {
protected $phid; protected $phid;
protected $resourceID; protected $resourceID;
protected $resourceType;
protected $until; protected $until;
protected $ownerPHID; protected $ownerPHID;
protected $attributes = array(); protected $attributes = array();
protected $status; protected $status = DrydockLeaseStatus::STATUS_PENDING;
protected $taskID; protected $taskID;
private $resource; private $resource;
@ -76,10 +77,37 @@ final class DrydockLease extends DrydockDAO {
$this->getResourceID()); $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();
// NOTE: Prevent a race where some eager worker quickly grabs the task
// before we can save the Task ID.
$this->openTransaction();
$this->beginReadLocking();
$this->reload();
$task = PhabricatorWorker::scheduleTask(
'DrydockAllocatorWorker',
$this->getID());
$this->setTaskID($task->getID());
$this->save();
$this->endReadLocking();
$this->saveTransaction();
return $this;
}
public function release() { public function release() {
// TODO: Insert a cleanup task into the taskmaster queue.
$this->setStatus(DrydockLeaseStatus::STATUS_RELEASED); $this->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
$this->save(); $this->save();

View file

@ -19,20 +19,18 @@
final class DrydockAllocatorWorker extends PhabricatorWorker { final class DrydockAllocatorWorker extends PhabricatorWorker {
protected function doWork() { protected function doWork() {
$data = $this->getTaskData(); $lease_id = $this->getTaskData();
$lease = id(new DrydockLease())->loadOneWhere( $lease = id(new DrydockLease())->load($lease_id);
'id = %d',
$data['lease']);
if (!$lease) { if (!$lease) {
return; return;
} }
$type = $data['type']; $type = $lease->getResourceType();
$candidates = id(new DrydockResource())->loadAllWhere( $candidates = id(new DrydockResource())->loadAllWhere(
'type = %s AND status = %s', 'type = %s AND status = %s',
$type, $lease->getResourceType(),
DrydockResourceStatus::STATUS_OPEN); DrydockResourceStatus::STATUS_OPEN);
if ($candidates) { if ($candidates) {
@ -64,7 +62,7 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
shuffle($blueprints); shuffle($blueprints);
$blueprint = head($blueprints); $blueprint = head($blueprints);
$resource = $blueprint->allocateResource(); $resource = $blueprint->allocateResource($lease);
} }
$blueprint = $resource->getBlueprint(); $blueprint = $resource->getBlueprint();

View file

@ -1024,6 +1024,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('drydocktaskid.sql'), 'name' => $this->getPatchPath('drydocktaskid.sql'),
), ),
'drydockresoucetype.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('drydockresourcetype.sql'),
),
); );
} }