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:
		
							
								
								
									
										2
									
								
								resources/sql/patches/drydockresourcetype.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/patches/drydockresourcetype.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_drydock.drydock_lease | ||||||
|  |   ADD resourceType VARCHAR(128) NOT NULL COLLATE utf8_bin; | ||||||
| @@ -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', | ||||||
|   | |||||||
| @@ -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; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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()); | ||||||
|   | |||||||
| @@ -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}'!"); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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(); | ||||||
| @@ -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'), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley