Path: blob/master/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
12256 views
<?php12final class DrydockManagementLeaseWorkflow3extends DrydockManagementWorkflow {45protected function didConstruct() {6$this7->setName('lease')8->setSynopsis(pht('Lease a resource.'))9->setArguments(10array(11array(12'name' => 'type',13'param' => 'resource_type',14'help' => pht('Resource type.'),15),16array(17'name' => 'until',18'param' => 'time',19'help' => pht('Set lease expiration time.'),20),21array(22'name' => 'attributes',23'param' => 'file',24'help' => pht(25'JSON file with lease attributes. Use "-" to read attributes '.26'from stdin.'),27),28array(29'name' => 'count',30'param' => 'N',31'default' => 1,32'help' => pht('Lease a given number of identical resources.'),33),34array(35'name' => 'blueprint',36'param' => 'identifier',37'repeat' => true,38'help' => pht('Lease resources from a specific blueprint.'),39),40));41}4243public function execute(PhutilArgumentParser $args) {44$viewer = $this->getViewer();4546$resource_type = $args->getArg('type');47if (!phutil_nonempty_string($resource_type)) {48throw new PhutilArgumentUsageException(49pht(50'Specify a resource type with "--type".'));51}5253$until = $args->getArg('until');54if (phutil_nonempty_string($until)) {55$until = strtotime($until);56if ($until <= 0) {57throw new PhutilArgumentUsageException(58pht(59'Unable to parse argument to "--until".'));60}61}6263$count = $args->getArgAsInteger('count');64if ($count < 1) {65throw new PhutilArgumentUsageException(66pht(67'Value provided to "--count" must be a nonzero, positive '.68'number.'));69}7071$attributes_file = $args->getArg('attributes');72if (phutil_nonempty_string($attributes_file)) {73if ($attributes_file == '-') {74echo tsprintf(75"%s\n",76pht('Reading JSON attributes from stdin...'));77$data = file_get_contents('php://stdin');78} else {79$data = Filesystem::readFile($attributes_file);80}8182$attributes = phutil_json_decode($data);83} else {84$attributes = array();85}8687$filter_identifiers = $args->getArg('blueprint');88if ($filter_identifiers) {89$filter_blueprints = $this->getBlueprintFilterMap($filter_identifiers);90} else {91$filter_blueprints = array();92}9394$blueprint_phids = null;9596$leases = array();97for ($idx = 0; $idx < $count; $idx++) {98$lease = id(new DrydockLease())99->setResourceType($resource_type);100101$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();102$lease->setAuthorizingPHID($drydock_phid);103104if ($attributes) {105$lease->setAttributes($attributes);106}107108if ($blueprint_phids === null) {109$blueprint_phids = $this->newAllowedBlueprintPHIDs(110$lease,111$filter_blueprints);112}113114$lease->setAllowedBlueprintPHIDs($blueprint_phids);115116if ($until) {117$lease->setUntil($until);118}119120// If something fatals or the user interrupts the process (for example,121// with "^C"), release the lease. We'll cancel this below, if the lease122// actually activates.123$lease->setReleaseOnDestruction(true);124125$leases[] = $lease;126}127128// TODO: This would probably be better handled with PhutilSignalRouter,129// but it currently doesn't route SIGINT. We're initializing it to setup130// SIGTERM handling and make eventual migration easier.131$router = PhutilSignalRouter::getRouter();132pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt'));133134$t_start = microtime(true);135136137echo tsprintf(138"%s\n\n",139pht('Leases queued for activation:'));140141foreach ($leases as $lease) {142$lease->queueForActivation();143144echo tsprintf(145" __%s__\n",146PhabricatorEnv::getProductionURI($lease->getURI()));147}148149echo tsprintf(150"\n%s\n\n",151pht('Waiting for daemons to activate leases...'));152153foreach ($leases as $lease) {154$this->waitUntilActive($lease);155}156157// Now that we've survived activation and the lease is good, make it158// durable.159foreach ($leases as $lease) {160$lease->setReleaseOnDestruction(false);161}162163$t_end = microtime(true);164165echo tsprintf(166"\n%s\n\n",167pht(168'Activation complete. Leases are permanent until manually '.169'released with:'));170171foreach ($leases as $lease) {172echo tsprintf(173" %s\n",174pht('$ ./bin/drydock release-lease --id %d', $lease->getID()));175}176177echo tsprintf(178"\n%s\n",179pht(180'Leases activated in %sms.',181new PhutilNumber((int)(($t_end - $t_start) * 1000))));182183return 0;184}185186public function didReceiveInterrupt($signo) {187// Doing this makes us run destructors, particularly the "release on188// destruction" trigger on the lease.189exit(128 + $signo);190}191192private function waitUntilActive(DrydockLease $lease) {193$viewer = $this->getViewer();194195$log_cursor = 0;196$log_types = DrydockLogType::getAllLogTypes();197198$is_active = false;199while (!$is_active) {200$lease->reload();201202$pager = id(new AphrontCursorPagerView())203->setBeforeID($log_cursor);204205// While we're waiting, show the user any logs which the daemons have206// generated to give them some clue about what's going on.207$logs = id(new DrydockLogQuery())208->setViewer($viewer)209->withLeasePHIDs(array($lease->getPHID()))210->executeWithCursorPager($pager);211if ($logs) {212$logs = mpull($logs, null, 'getID');213ksort($logs);214$log_cursor = last_key($logs);215}216217foreach ($logs as $log) {218$type_key = $log->getType();219if (isset($log_types[$type_key])) {220$type_object = id(clone $log_types[$type_key])221->setLog($log)222->setViewer($viewer);223224$log_data = $log->getData();225226$type = $type_object->getLogTypeName();227$data = $type_object->renderLogForText($log_data);228} else {229$type = pht('Unknown ("%s")', $type_key);230$data = null;231}232233echo tsprintf(234"(Lease #%d) <%s> %B\n",235$lease->getID(),236$type,237$data);238}239240$status = $lease->getStatus();241242switch ($status) {243case DrydockLeaseStatus::STATUS_ACTIVE:244$is_active = true;245break;246case DrydockLeaseStatus::STATUS_RELEASED:247throw new Exception(pht('Lease has already been released!'));248case DrydockLeaseStatus::STATUS_DESTROYED:249throw new Exception(pht('Lease has already been destroyed!'));250case DrydockLeaseStatus::STATUS_BROKEN:251throw new Exception(pht('Lease has been broken!'));252case DrydockLeaseStatus::STATUS_PENDING:253case DrydockLeaseStatus::STATUS_ACQUIRED:254break;255default:256throw new Exception(257pht(258'Lease has unknown status "%s".',259$status));260}261262if ($is_active) {263break;264} else {265sleep(1);266}267}268}269270private function getBlueprintFilterMap(array $identifiers) {271$viewer = $this->getViewer();272273$query = id(new DrydockBlueprintQuery())274->setViewer($viewer)275->withIdentifiers($identifiers);276277$blueprints = $query->execute();278$blueprints = mpull($blueprints, null, 'getPHID');279280$map = $query->getIdentifierMap();281282$seen = array();283foreach ($identifiers as $identifier) {284if (!isset($map[$identifier])) {285throw new PhutilArgumentUsageException(286pht(287'Blueprint "%s" could not be loaded. Try a blueprint ID or '.288'PHID.',289$identifier));290}291292$blueprint = $map[$identifier];293294$blueprint_phid = $blueprint->getPHID();295if (isset($seen[$blueprint_phid])) {296throw new PhutilArgumentUsageException(297pht(298'Blueprint "%s" is specified more than once (as "%s" and "%s").',299$blueprint->getBlueprintName(),300$seen[$blueprint_phid],301$identifier));302}303304$seen[$blueprint_phid] = true;305}306307return mpull($map, null, 'getPHID');308}309310private function newAllowedBlueprintPHIDs(311DrydockLease $lease,312array $filter_blueprints) {313assert_instances_of($filter_blueprints, 'DrydockBlueprint');314315$viewer = $this->getViewer();316317$impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease);318319if (!$impls) {320throw new PhutilArgumentUsageException(321pht(322'No known blueprint class can ever allocate the specified '.323'lease. Check that the resource type is spelled correctly.'));324}325326$classes = array_keys($impls);327328$blueprints = id(new DrydockBlueprintQuery())329->setViewer($viewer)330->withBlueprintClasses($classes)331->withDisabled(false)332->execute();333334if (!$blueprints) {335throw new PhutilArgumentUsageException(336pht(337'No enabled blueprints exist with a blueprint class that can '.338'plausibly allocate resources to satisfy the requested lease.'));339}340341$phids = mpull($blueprints, 'getPHID');342343if ($filter_blueprints) {344$allowed_map = array_fuse($phids);345$filter_map = mpull($filter_blueprints, null, 'getPHID');346347foreach ($filter_map as $filter_phid => $blueprint) {348if (!isset($allowed_map[$filter_phid])) {349throw new PhutilArgumentUsageException(350pht(351'Specified blueprint "%s" is not capable of satisfying the '.352'configured lease.',353$blueprint->getBlueprintName()));354}355}356357$phids = mpull($filter_blueprints, 'getPHID');358}359360return $phids;361}362363}364365366