diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 7371232620..08f33c6b5f 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -1,219 +1,221 @@ setName('lease') ->setSynopsis(pht('Lease a resource.')) ->setArguments( array( array( 'name' => 'type', 'param' => 'resource_type', 'help' => pht('Resource type.'), ), array( 'name' => 'until', 'param' => 'time', 'help' => pht('Set lease expiration time.'), ), array( 'name' => 'attributes', 'param' => 'file', 'help' => pht( 'JSON file with lease attributes. Use "-" to read attributes '. 'from stdin.'), ), )); } public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); $resource_type = $args->getArg('type'); if (!$resource_type) { throw new PhutilArgumentUsageException( pht( 'Specify a resource type with `%s`.', '--type')); } $until = $args->getArg('until'); if (strlen($until)) { $until = strtotime($until); if ($until <= 0) { throw new PhutilArgumentUsageException( pht( 'Unable to parse argument to "%s".', '--until')); } } $attributes_file = $args->getArg('attributes'); if (strlen($attributes_file)) { if ($attributes_file == '-') { echo tsprintf( "%s\n", 'Reading JSON attributes from stdin...'); $data = file_get_contents('php://stdin'); } else { $data = Filesystem::readFile($attributes_file); } $attributes = phutil_json_decode($data); } else { $attributes = array(); } $lease = id(new DrydockLease()) ->setResourceType($resource_type); $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); $lease->setAuthorizingPHID($drydock_phid); if ($attributes) { $lease->setAttributes($attributes); } // TODO: This is not hugely scalable, although this is a debugging workflow // so maybe it's fine. Do we even need `bin/drydock lease` in the long run? $all_blueprints = id(new DrydockBlueprintQuery()) ->setViewer($viewer) ->execute(); $allowed_phids = mpull($all_blueprints, 'getPHID'); if (!$allowed_phids) { throw new Exception( pht( 'No blueprints exist which can plausibly allocate resources to '. 'satisfy the requested lease.')); } $lease->setAllowedBlueprintPHIDs($allowed_phids); if ($until) { $lease->setUntil($until); } // If something fatals or the user interrupts the process (for example, // with "^C"), release the lease. We'll cancel this below, if the lease // actually activates. $lease->setReleaseOnDestruction(true); // TODO: This would probably be better handled with PhutilSignalRouter, // but it currently doesn't route SIGINT. We're initializing it to setup // SIGTERM handling and make eventual migration easier. $router = PhutilSignalRouter::getRouter(); pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt')); $t_start = microtime(true); $lease->queueForActivation(); echo tsprintf( "%s\n\n __%s__\n\n%s\n", pht('Queued lease for activation:'), PhabricatorEnv::getProductionURI($lease->getURI()), pht('Waiting for daemons to activate lease...')); $this->waitUntilActive($lease); // Now that we've survived activation and the lease is good, make it // durable. $lease->setReleaseOnDestruction(false); $t_end = microtime(true); echo tsprintf( "%s\n\n %s\n\n%s\n", pht( 'Activation complete. This lease is permanent until manually '. 'released with:'), pht('$ ./bin/drydock release-lease --id %d', $lease->getID()), pht( 'Lease activated in %sms.', new PhutilNumber((int)(($t_end - $t_start) * 1000)))); return 0; } public function didReceiveInterrupt($signo) { // Doing this makes us run destructors, particularly the "release on // destruction" trigger on the lease. exit(128 + $signo); } private function waitUntilActive(DrydockLease $lease) { $viewer = $this->getViewer(); $log_cursor = 0; $log_types = DrydockLogType::getAllLogTypes(); $is_active = false; while (!$is_active) { $lease->reload(); + $pager = id(new AphrontCursorPagerView()) + ->setBeforeID($log_cursor); + // While we're waiting, show the user any logs which the daemons have // generated to give them some clue about what's going on. $logs = id(new DrydockLogQuery()) ->setViewer($viewer) ->withLeasePHIDs(array($lease->getPHID())) - ->setBeforeID($log_cursor) - ->execute(); + ->executeWithCursorPager($pager); if ($logs) { $logs = mpull($logs, null, 'getID'); ksort($logs); $log_cursor = last_key($logs); } foreach ($logs as $log) { $type_key = $log->getType(); if (isset($log_types[$type_key])) { $type_object = id(clone $log_types[$type_key]) ->setLog($log) ->setViewer($viewer); $log_data = $log->getData(); $type = $type_object->getLogTypeName(); $data = $type_object->renderLogForText($log_data); } else { $type = pht('Unknown ("%s")', $type_key); $data = null; } echo tsprintf( "<%s> %B\n", $type, $data); } $status = $lease->getStatus(); switch ($status) { case DrydockLeaseStatus::STATUS_ACTIVE: $is_active = true; break; case DrydockLeaseStatus::STATUS_RELEASED: throw new Exception(pht('Lease has already been released!')); case DrydockLeaseStatus::STATUS_DESTROYED: throw new Exception(pht('Lease has already been destroyed!')); case DrydockLeaseStatus::STATUS_BROKEN: throw new Exception(pht('Lease has been broken!')); case DrydockLeaseStatus::STATUS_PENDING: case DrydockLeaseStatus::STATUS_ACQUIRED: break; default: throw new Exception( pht( 'Lease has unknown status "%s".', $status)); } if ($is_active) { break; } else { sleep(1); } } } } diff --git a/src/applications/drydock/query/DrydockLogQuery.php b/src/applications/drydock/query/DrydockLogQuery.php index b73ad371ae..80f47f584f 100644 --- a/src/applications/drydock/query/DrydockLogQuery.php +++ b/src/applications/drydock/query/DrydockLogQuery.php @@ -1,160 +1,173 @@ ids = $ids; + return $this; + } + public function withBlueprintPHIDs(array $phids) { $this->blueprintPHIDs = $phids; return $this; } public function withResourcePHIDs(array $phids) { $this->resourcePHIDs = $phids; return $this; } public function withLeasePHIDs(array $phids) { $this->leasePHIDs = $phids; return $this; } public function withOperationPHIDs(array $phids) { $this->operationPHIDs = $phids; return $this; } public function newResultObject() { return new DrydockLog(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function didFilterPage(array $logs) { $blueprint_phids = array_filter(mpull($logs, 'getBlueprintPHID')); if ($blueprint_phids) { $blueprints = id(new DrydockBlueprintQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($blueprint_phids) ->execute(); $blueprints = mpull($blueprints, null, 'getPHID'); } else { $blueprints = array(); } foreach ($logs as $key => $log) { $blueprint = null; $blueprint_phid = $log->getBlueprintPHID(); if ($blueprint_phid) { $blueprint = idx($blueprints, $blueprint_phid); } $log->attachBlueprint($blueprint); } $resource_phids = array_filter(mpull($logs, 'getResourcePHID')); if ($resource_phids) { $resources = id(new DrydockResourceQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($resource_phids) ->execute(); $resources = mpull($resources, null, 'getPHID'); } else { $resources = array(); } foreach ($logs as $key => $log) { $resource = null; $resource_phid = $log->getResourcePHID(); if ($resource_phid) { $resource = idx($resources, $resource_phid); } $log->attachResource($resource); } $lease_phids = array_filter(mpull($logs, 'getLeasePHID')); if ($lease_phids) { $leases = id(new DrydockLeaseQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($lease_phids) ->execute(); $leases = mpull($leases, null, 'getPHID'); } else { $leases = array(); } foreach ($logs as $key => $log) { $lease = null; $lease_phid = $log->getLeasePHID(); if ($lease_phid) { $lease = idx($leases, $lease_phid); } $log->attachLease($lease); } $operation_phids = array_filter(mpull($logs, 'getOperationPHID')); if ($operation_phids) { $operations = id(new DrydockRepositoryOperationQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($operation_phids) ->execute(); $operations = mpull($operations, null, 'getPHID'); } else { $operations = array(); } foreach ($logs as $key => $log) { $operation = null; $operation_phid = $log->getOperationPHID(); if ($operation_phid) { $operation = idx($operations, $operation_phid); } $log->attachOperation($operation); } return $logs; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ls)', + $this->ids); + } + if ($this->blueprintPHIDs !== null) { $where[] = qsprintf( $conn, 'blueprintPHID IN (%Ls)', $this->blueprintPHIDs); } if ($this->resourcePHIDs !== null) { $where[] = qsprintf( $conn, 'resourcePHID IN (%Ls)', $this->resourcePHIDs); } if ($this->leasePHIDs !== null) { $where[] = qsprintf( $conn, 'leasePHID IN (%Ls)', $this->leasePHIDs); } if ($this->operationPHIDs !== null) { $where[] = qsprintf( $conn, 'operationPHID IN (%Ls)', $this->operationPHIDs); } return $where; } }