Changeset View
Changeset View
Standalone View
Standalone View
src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
Show All 20 Lines | $this | ||||
), | ), | ||||
array( | array( | ||||
'name' => 'attributes', | 'name' => 'attributes', | ||||
'param' => 'file', | 'param' => 'file', | ||||
'help' => pht( | 'help' => pht( | ||||
'JSON file with lease attributes. Use "-" to read attributes '. | 'JSON file with lease attributes. Use "-" to read attributes '. | ||||
'from stdin.'), | 'from stdin.'), | ||||
), | ), | ||||
array( | |||||
'name' => 'count', | |||||
'param' => 'N', | |||||
'default' => 1, | |||||
'help' => pht('Lease a given number of identical resources.'), | |||||
), | |||||
array( | |||||
'name' => 'blueprint', | |||||
'param' => 'identifier', | |||||
'repeat' => true, | |||||
'help' => pht('Lease resources from a specific blueprint.'), | |||||
), | |||||
)); | )); | ||||
} | } | ||||
public function execute(PhutilArgumentParser $args) { | public function execute(PhutilArgumentParser $args) { | ||||
$viewer = $this->getViewer(); | $viewer = $this->getViewer(); | ||||
$resource_type = $args->getArg('type'); | $resource_type = $args->getArg('type'); | ||||
if (!$resource_type) { | if (!phutil_nonempty_string($resource_type)) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'Specify a resource type with `%s`.', | 'Specify a resource type with "--type".')); | ||||
'--type')); | |||||
} | } | ||||
$until = $args->getArg('until'); | $until = $args->getArg('until'); | ||||
if (strlen($until)) { | if (phutil_nonempty_string($until)) { | ||||
$until = strtotime($until); | $until = strtotime($until); | ||||
if ($until <= 0) { | if ($until <= 0) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'Unable to parse argument to "%s".', | 'Unable to parse argument to "--until".')); | ||||
'--until')); | |||||
} | } | ||||
} | } | ||||
$count = $args->getArgAsInteger('count'); | |||||
if ($count < 1) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'Value provided to "--count" must be a nonzero, positive '. | |||||
'number.')); | |||||
} | |||||
$attributes_file = $args->getArg('attributes'); | $attributes_file = $args->getArg('attributes'); | ||||
if (strlen($attributes_file)) { | if (phutil_nonempty_string($attributes_file)) { | ||||
if ($attributes_file == '-') { | if ($attributes_file == '-') { | ||||
echo tsprintf( | echo tsprintf( | ||||
"%s\n", | "%s\n", | ||||
'Reading JSON attributes from stdin...'); | pht('Reading JSON attributes from stdin...')); | ||||
$data = file_get_contents('php://stdin'); | $data = file_get_contents('php://stdin'); | ||||
} else { | } else { | ||||
$data = Filesystem::readFile($attributes_file); | $data = Filesystem::readFile($attributes_file); | ||||
} | } | ||||
$attributes = phutil_json_decode($data); | $attributes = phutil_json_decode($data); | ||||
} else { | } else { | ||||
$attributes = array(); | $attributes = array(); | ||||
} | } | ||||
$filter_identifiers = $args->getArg('blueprint'); | |||||
if ($filter_identifiers) { | |||||
$filter_blueprints = $this->getBlueprintFilterMap($filter_identifiers); | |||||
} else { | |||||
$filter_blueprints = array(); | |||||
} | |||||
$blueprint_phids = null; | |||||
$leases = array(); | |||||
for ($idx = 0; $idx < $count; $idx++) { | |||||
$lease = id(new DrydockLease()) | $lease = id(new DrydockLease()) | ||||
->setResourceType($resource_type); | ->setResourceType($resource_type); | ||||
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); | $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); | ||||
$lease->setAuthorizingPHID($drydock_phid); | $lease->setAuthorizingPHID($drydock_phid); | ||||
if ($attributes) { | if ($attributes) { | ||||
$lease->setAttributes($attributes); | $lease->setAttributes($attributes); | ||||
} | } | ||||
// TODO: This is not hugely scalable, although this is a debugging workflow | if ($blueprint_phids === null) { | ||||
// so maybe it's fine. Do we even need `bin/drydock lease` in the long run? | $blueprint_phids = $this->newAllowedBlueprintPHIDs( | ||||
$all_blueprints = id(new DrydockBlueprintQuery()) | $lease, | ||||
->setViewer($viewer) | $filter_blueprints); | ||||
->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); | |||||
$lease->setAllowedBlueprintPHIDs($blueprint_phids); | |||||
if ($until) { | if ($until) { | ||||
$lease->setUntil($until); | $lease->setUntil($until); | ||||
} | } | ||||
// If something fatals or the user interrupts the process (for example, | // If something fatals or the user interrupts the process (for example, | ||||
// with "^C"), release the lease. We'll cancel this below, if the lease | // with "^C"), release the lease. We'll cancel this below, if the lease | ||||
// actually activates. | // actually activates. | ||||
$lease->setReleaseOnDestruction(true); | $lease->setReleaseOnDestruction(true); | ||||
$leases[] = $lease; | |||||
} | |||||
// TODO: This would probably be better handled with PhutilSignalRouter, | // TODO: This would probably be better handled with PhutilSignalRouter, | ||||
// but it currently doesn't route SIGINT. We're initializing it to setup | // but it currently doesn't route SIGINT. We're initializing it to setup | ||||
// SIGTERM handling and make eventual migration easier. | // SIGTERM handling and make eventual migration easier. | ||||
$router = PhutilSignalRouter::getRouter(); | $router = PhutilSignalRouter::getRouter(); | ||||
pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt')); | pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt')); | ||||
$t_start = microtime(true); | $t_start = microtime(true); | ||||
echo tsprintf( | |||||
"%s\n\n", | |||||
pht('Leases queued for activation:')); | |||||
foreach ($leases as $lease) { | |||||
$lease->queueForActivation(); | $lease->queueForActivation(); | ||||
echo tsprintf( | echo tsprintf( | ||||
"%s\n\n __%s__\n\n%s\n", | " __%s__\n", | ||||
pht('Queued lease for activation:'), | PhabricatorEnv::getProductionURI($lease->getURI())); | ||||
PhabricatorEnv::getProductionURI($lease->getURI()), | } | ||||
pht('Waiting for daemons to activate lease...')); | |||||
echo tsprintf( | |||||
"\n%s\n\n", | |||||
pht('Waiting for daemons to activate leases...')); | |||||
foreach ($leases as $lease) { | |||||
$this->waitUntilActive($lease); | $this->waitUntilActive($lease); | ||||
} | |||||
// Now that we've survived activation and the lease is good, make it | // Now that we've survived activation and the lease is good, make it | ||||
// durable. | // durable. | ||||
foreach ($leases as $lease) { | |||||
$lease->setReleaseOnDestruction(false); | $lease->setReleaseOnDestruction(false); | ||||
} | |||||
$t_end = microtime(true); | $t_end = microtime(true); | ||||
echo tsprintf( | echo tsprintf( | ||||
"%s\n\n %s\n\n%s\n", | "\n%s\n\n", | ||||
pht( | pht( | ||||
'Activation complete. This lease is permanent until manually '. | 'Activation complete. Leases are permanent until manually '. | ||||
'released with:'), | 'released with:')); | ||||
pht('$ ./bin/drydock release-lease --id %d', $lease->getID()), | |||||
foreach ($leases as $lease) { | |||||
echo tsprintf( | |||||
" %s\n", | |||||
pht('$ ./bin/drydock release-lease --id %d', $lease->getID())); | |||||
} | |||||
echo tsprintf( | |||||
"\n%s\n", | |||||
pht( | pht( | ||||
'Lease activated in %sms.', | 'Leases activated in %sms.', | ||||
new PhutilNumber((int)(($t_end - $t_start) * 1000)))); | new PhutilNumber((int)(($t_end - $t_start) * 1000)))); | ||||
return 0; | return 0; | ||||
} | } | ||||
public function didReceiveInterrupt($signo) { | public function didReceiveInterrupt($signo) { | ||||
// Doing this makes us run destructors, particularly the "release on | // Doing this makes us run destructors, particularly the "release on | ||||
// destruction" trigger on the lease. | // destruction" trigger on the lease. | ||||
Show All 37 Lines | while (!$is_active) { | ||||
$type = $type_object->getLogTypeName(); | $type = $type_object->getLogTypeName(); | ||||
$data = $type_object->renderLogForText($log_data); | $data = $type_object->renderLogForText($log_data); | ||||
} else { | } else { | ||||
$type = pht('Unknown ("%s")', $type_key); | $type = pht('Unknown ("%s")', $type_key); | ||||
$data = null; | $data = null; | ||||
} | } | ||||
echo tsprintf( | echo tsprintf( | ||||
"<%s> %B\n", | "(Lease #%d) <%s> %B\n", | ||||
$lease->getID(), | |||||
$type, | $type, | ||||
$data); | $data); | ||||
} | } | ||||
$status = $lease->getStatus(); | $status = $lease->getStatus(); | ||||
switch ($status) { | switch ($status) { | ||||
case DrydockLeaseStatus::STATUS_ACTIVE: | case DrydockLeaseStatus::STATUS_ACTIVE: | ||||
Show All 18 Lines | while (!$is_active) { | ||||
if ($is_active) { | if ($is_active) { | ||||
break; | break; | ||||
} else { | } else { | ||||
sleep(1); | sleep(1); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private function getBlueprintFilterMap(array $identifiers) { | |||||
$viewer = $this->getViewer(); | |||||
$query = id(new DrydockBlueprintQuery()) | |||||
->setViewer($viewer) | |||||
->withIdentifiers($identifiers); | |||||
$blueprints = $query->execute(); | |||||
$blueprints = mpull($blueprints, null, 'getPHID'); | |||||
$map = $query->getIdentifierMap(); | |||||
$seen = array(); | |||||
foreach ($identifiers as $identifier) { | |||||
if (!isset($map[$identifier])) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'Blueprint "%s" could not be loaded. Try a blueprint ID or '. | |||||
'PHID.', | |||||
$identifier)); | |||||
} | |||||
$blueprint = $map[$identifier]; | |||||
$blueprint_phid = $blueprint->getPHID(); | |||||
if (isset($seen[$blueprint_phid])) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'Blueprint "%s" is specified more than once (as "%s" and "%s").', | |||||
$blueprint->getBlueprintName(), | |||||
$seen[$blueprint_phid], | |||||
$identifier)); | |||||
} | |||||
$seen[$blueprint_phid] = true; | |||||
} | |||||
return mpull($map, null, 'getPHID'); | |||||
} | |||||
private function newAllowedBlueprintPHIDs( | |||||
DrydockLease $lease, | |||||
array $filter_blueprints) { | |||||
assert_instances_of($filter_blueprints, 'DrydockBlueprint'); | |||||
$viewer = $this->getViewer(); | |||||
$impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease); | |||||
if (!$impls) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'No known blueprint class can ever allocate the specified '. | |||||
'lease. Check that the resource type is spelled correctly.')); | |||||
} | |||||
$classes = array_keys($impls); | |||||
$blueprints = id(new DrydockBlueprintQuery()) | |||||
->setViewer($viewer) | |||||
->withBlueprintClasses($classes) | |||||
->withDisabled(false) | |||||
->execute(); | |||||
if (!$blueprints) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'No enabled blueprints exist with a blueprint class that can '. | |||||
'plausibly allocate resources to satisfy the requested lease.')); | |||||
} | |||||
$phids = mpull($blueprints, 'getPHID'); | |||||
if ($filter_blueprints) { | |||||
$allowed_map = array_fuse($phids); | |||||
$filter_map = mpull($filter_blueprints, null, 'getPHID'); | |||||
foreach ($filter_map as $filter_phid => $blueprint) { | |||||
if (!isset($allowed_map[$filter_phid])) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'Specified blueprint "%s" is not capable of satisfying the '. | |||||
'configured lease.', | |||||
$blueprint->getBlueprintName())); | |||||
} | |||||
} | |||||
$phids = mpull($filter_blueprints, 'getPHID'); | |||||
} | |||||
return $phids; | |||||
} | |||||
} | } |
Content licensed under Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA) unless otherwise noted; code licensed under Apache 2.0 or other open source licenses. · CC BY-SA 4.0 · Apache 2.0