Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -644,6 +644,7 @@ 'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php', 'DrydockManagementWaitForLeaseWorkflow' => 'applications/drydock/management/DrydockManagementWaitForLeaseWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', + 'DrydockPreallocatedHostBlueprint' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', @@ -2928,6 +2929,7 @@ 'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWaitForLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhutilArgumentWorkflow', + 'DrydockPreallocatedHostBlueprint' => 'DrydockBlueprint', 'DrydockResource' => 'DrydockDAO', 'DrydockResourceCloseController' => 'DrydockController', 'DrydockResourceListController' => 'DrydockController', Index: src/applications/drydock/blueprint/DrydockBlueprint.php =================================================================== --- src/applications/drydock/blueprint/DrydockBlueprint.php +++ src/applications/drydock/blueprint/DrydockBlueprint.php @@ -273,6 +273,10 @@ return true; } + public function canAllocateResource(DrydockLease $lease) { + return true; + } + abstract protected function executeAllocateResource(DrydockLease $lease); Index: src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php =================================================================== --- src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php +++ src/applications/drydock/blueprint/DrydockLocalHostBlueprint.php @@ -7,6 +7,12 @@ return true; } + public function canAllocateResource(DrydockLease $lease) { + return + $lease->getAttribute('remote') !== null && + !filter_var($lease->getAttribute('remote'), FILTER_VALIDATE_BOOLEAN); + } + public function canAllocateMoreResources(array $pool) { assert_instances_of($pool, 'DrydockResource'); @@ -34,6 +40,8 @@ $resource = $this->newResourceTemplate('Host (localhost)'); $resource->setStatus(DrydockResourceStatus::STATUS_OPEN); $resource->setAttribute('path', $path); + $resource->setAttribute('remote', false); + $resource->setAttribute('preallocated', false); $resource->save(); return $resource; @@ -42,7 +50,7 @@ protected function canAllocateLease( DrydockResource $resource, DrydockLease $lease) { - return true; + return $this->canAllocateResource($lease); } protected function shouldAllocateLease( Index: src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php =================================================================== --- /dev/null +++ src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php @@ -0,0 +1,125 @@ +getAttribute('remote') !== null && + $lease->getAttribute('preallocated') !== null && + filter_var($lease->getAttribute('remote'), FILTER_VALIDATE_BOOLEAN) && + filter_var( + $lease->getAttribute('preallocated'), + FILTER_VALIDATE_BOOLEAN) && + $lease->getAttribute('host') !== null && + $lease->getAttribute('port') !== null && + $lease->getAttribute('user') !== null && + $lease->getAttribute('path') !== null && + $lease->getAttribute('platform') !== null; + } + + protected function executeAllocateResource(DrydockLease $lease) { + $host = $lease->getAttribute('host'); + + if ($host === null) { + // We shouldn't be able to get to here since we need the + // lease attributes to be specified. + throw new Exception("Something went seriously wrong!"); + } + + $resource = $this->newResourceTemplate('Host ('.$host.')'); + $resource->setStatus(DrydockResourceStatus::STATUS_OPEN); + $resource->setAttribute('host', $host); + $resource->setAttribute('port', $lease->getAttribute('port')); + $resource->setAttribute('user', $lease->getAttribute('user')); + $resource->setAttribute('ssh-keyfile', $lease->getAttribute('keyfile')); + $resource->setAttribute('path', $lease->getAttribute('path')); + $resource->setAttribute('platform', $lease->getAttribute('platform')); + $resource->setAttribute('remote', true); // TODO: Should these be strings? + $resource->setAttribute('preallocated', true); + $resource->save(); + + return $resource; + } + + protected function canAllocateLease( + DrydockResource $resource, + DrydockLease $lease) { + return + $this->canAllocateResource($lease) && + $lease->getAttribute('remote') !== null && + $lease->getAttribute('preallocated') !== null && + filter_var($lease->getAttribute('remote'), FILTER_VALIDATE_BOOLEAN) + === $resource->getAttribute('remote') && + filter_var( + $lease->getAttribute('preallocated'), + FILTER_VALIDATE_BOOLEAN) === $resource->getAttribute('preallocated') && + $lease->getAttribute('host') === $resource->getAttribute('host') && + $lease->getAttribute('port') === $resource->getAttribute('port') && + $lease->getAttribute('user') === $resource->getAttribute('user') && + $lease->getAttribute('path') === $resource->getAttribute('path') && + $lease->getAttribute('platform') === $resource->getAttribute('platform'); + } + + protected function shouldAllocateLease( + DrydockResource $resource, + DrydockLease $lease, + array $other_leases) { + return true; + } + + protected function executeAcquireLease( + DrydockResource $resource, + DrydockLease $lease) { + + // Similar to DrydockLocalHostBlueprint, we create a folder + // on the remote host that the lease can use. + + $lease_id = $lease->getID(); + + $full_path = $lease->getAttribute('path').$lease_id; + + $cmd = $lease->getInterface('command'); + + if ($lease->getAttribute('platform') !== 'windows') { + $cmd->execx('mkdir %s', $full_path); + } else { + // Windows is terrible. The mkdir command doesn't even support putting + // the path in quotes. IN QUOTES. ARGUHRGHUGHHGG!! Do some terribly + // inaccurate sanity checking since we can't safely escape the path. + if (preg_match('/^[A-Z]\\:\\\\[a-zA-Z0-9\\\\\\ ]/', $full_path) === 0) { + throw new Exception('Unsafe path detected for Windows platform.'); + } + $cmd->execx('mkdir %C', $full_path); + } + + $lease->setAttribute('path', $full_path); + } + + public function getType() { + return 'host'; + } + + public function getInterface( + DrydockResource $resource, + DrydockLease $lease, + $type) { + + switch ($type) { + case 'command': + return id(new DrydockSSHCommandInterface()) + ->setConfiguration(array( + 'host' => $resource->getAttribute('host'), + 'port' => $resource->getAttribute('port'), + 'user' => $resource->getAttribute('user'), + 'ssh-keyfile' => $resource->getAttribute('ssh-keyfile'), + 'platform' => $resource->getAttribute('platform'))); + } + + throw new Exception("No interface of type '{$type}'."); + } + +} Index: src/applications/drydock/interface/command/DrydockSSHCommandInterface.php =================================================================== --- src/applications/drydock/interface/command/DrydockSSHCommandInterface.php +++ src/applications/drydock/interface/command/DrydockSSHCommandInterface.php @@ -4,19 +4,41 @@ public function getExecFuture($command) { $argv = func_get_args(); - $argv = $this->applyWorkingDirectoryToArgv($argv); + + // This assumes there's a UNIX shell living at the other + // end of the connection, which isn't the case for Windows machines. + if ($this->getConfig('platform') !== 'windows') { + $argv = $this->applyWorkingDirectoryToArgv($argv); + } $full_command = call_user_func_array('csprintf', $argv); + if ($this->getConfig('platform') === 'windows') { + // On Windows platforms we need to execute cmd.exe explicitly since + // most commands are not really executables. + $full_command = 'C:\\Windows\\system32\\cmd.exe /C '.$full_command; + } + // NOTE: The "-t -t" is for psuedo-tty allocation so we can "sudo" on some // systems, but maybe more trouble than it's worth? - return new ExecFuture( - 'ssh -t -t -o StrictHostKeyChecking=no -i %s %s@%s -- %s', - $this->getConfig('ssh-keyfile'), - $this->getConfig('user'), - $this->getConfig('host'), - $full_command); + $keyfile = $this->getConfig('ssh-keyfile'); + if (!empty($keyfile)) { + return new ExecFuture( + 'ssh -t -t -o StrictHostKeyChecking=no -p %s -i %s %s@%s -- %s', + $this->getConfig('port'), + $this->getConfig('ssh-keyfile'), + $this->getConfig('user'), + $this->getConfig('host'), + $full_command); + } else { + return new ExecFuture( + 'ssh -t -t -o StrictHostKeyChecking=no -p %s %s@%s -- %s', + $this->getConfig('port'), + $this->getConfig('user'), + $this->getConfig('host'), + $full_command); + } } } Index: src/applications/drydock/worker/DrydockAllocatorWorker.php =================================================================== --- src/applications/drydock/worker/DrydockAllocatorWorker.php +++ src/applications/drydock/worker/DrydockAllocatorWorker.php @@ -112,6 +112,10 @@ unset($blueprints[$key]); continue; } + if (!$candidate_blueprint->canAllocateResource($lease)) { + unset($blueprints[$key]); + continue; + } } $this->logToDrydock(