Differential D10547 Diff 25906 src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
Show First 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | protected function executeAllocateResource( | ||||
// We need to retrieve this as we need to use it for both importing the | // We need to retrieve this as we need to use it for both importing the | ||||
// key and looking up the ID for the resource attributes. | // key and looking up the ID for the resource attributes. | ||||
$credential = id(new PassphraseCredentialQuery()) | $credential = id(new PassphraseCredentialQuery()) | ||||
->setViewer(PhabricatorUser::getOmnipotentUser()) | ->setViewer(PhabricatorUser::getOmnipotentUser()) | ||||
->withPHIDs(array($this->getDetail('keypair'))) | ->withPHIDs(array($this->getDetail('keypair'))) | ||||
->executeOne(); | ->executeOne(); | ||||
if ($credential === null) { | |||||
throw new Exception('Specified credential does not exist!'); | |||||
} | |||||
$this->log(pht( | $this->log(pht( | ||||
'Using credential %d to allocate.', | 'Using credential %d to allocate.', | ||||
$credential->getID())); | $credential->getID())); | ||||
$winrm_auth_id = null; | |||||
if ($this->getDetail('protocol') === 'winrm') { | |||||
$winrm_auth = id(new PassphraseCredentialQuery()) | |||||
->setViewer(PhabricatorUser::getOmnipotentUser()) | |||||
->withPHIDs(array($this->getDetail('winrm-auth'))) | |||||
->executeOne(); | |||||
if ($winrm_auth === null) { | |||||
throw new Exception( | |||||
'Specified credential for WinRM auth does not exist!'); | |||||
} | |||||
$winrm_auth_id = $winrm_auth->getID(); | |||||
$this->log(pht( | |||||
'Using credential %d to authenticate over WinRM.', | |||||
$winrm_auth->getID())); | |||||
} | |||||
try { | try { | ||||
$existing_keys = $this->getAWSEC2Future() | $existing_keys = $this->getAWSEC2Future() | ||||
->setRawAWSQuery( | ->setRawAWSQuery( | ||||
'DescribeKeyPairs', | 'DescribeKeyPairs', | ||||
array( | array( | ||||
'KeyName.0' => $this->getAWSKeyPairName())) | 'KeyName.0' => $this->getAWSKeyPairName())) | ||||
->resolve(); | ->resolve(); | ||||
Show All 36 Lines | protected function executeAllocateResource( | ||||
$i = 0; | $i = 0; | ||||
$security_groups = explode(',', $this->getDetail('security-group-ids')); | $security_groups = explode(',', $this->getDetail('security-group-ids')); | ||||
foreach ($security_groups as $security_group) { | foreach ($security_groups as $security_group) { | ||||
$settings['SecurityGroupId.'.$i] = $security_group; | $settings['SecurityGroupId.'.$i] = $security_group; | ||||
$i++; | $i++; | ||||
} | } | ||||
if (!$this->getDetail('skip-ssh-setup-windows')) { | if (!$this->getDetail('skip-setup-windows')) { | ||||
if ($this->getDetail('platform') === 'windows') { | if ($this->getDetail('platform') === 'windows') { | ||||
$this->log(pht('Enabled SSH automatic configuration for Windows.')); | $this->log(pht('Enabled SSH automatic configuration for Windows.')); | ||||
$settings['UserData'] = id(new WindowsZeroConf()) | $settings['UserData'] = id(new WindowsZeroConf()) | ||||
->getEncodedUserData($credential); | ->getEncodedUserData($credential, $this->getDetail('protocol')); | ||||
} | } | ||||
} | } | ||||
if ($this->getDetail('spot-enabled') && | if ($this->getDetail('spot-enabled') && | ||||
$this->getDetail('spot-price') !== null) { | $this->getDetail('spot-price') !== null) { | ||||
$this->log(pht( | $this->log(pht( | ||||
'Spot price allocation is enabled, at a price of %f.', | 'Spot price allocation is enabled, at a price of %f.', | ||||
▲ Show 20 Lines • Show All 256 Lines • ▼ Show 20 Lines | protected function executeAllocateResource( | ||||
// we wait for the instance to start. | // we wait for the instance to start. | ||||
$blueprint = $this->getInstance(); | $blueprint = $this->getInstance(); | ||||
$resource | $resource | ||||
->setName($instance_id) | ->setName($instance_id) | ||||
->setStatus(DrydockResourceStatus::STATUS_PENDING) | ->setStatus(DrydockResourceStatus::STATUS_PENDING) | ||||
->setAttributes(array( | ->setAttributes(array( | ||||
'instance-id' => $instance_id, | 'instance-id' => $instance_id, | ||||
'platform' => $this->getDetail('platform'), | 'platform' => $this->getDetail('platform'), | ||||
'protocol' => $this->getDetail('protocol'), | |||||
'path' => $this->getDetail('storage-path'), | 'path' => $this->getDetail('storage-path'), | ||||
'credential' => $credential->getID(), | 'credential' => $credential->getID(), | ||||
'winrm-auth' => $winrm_auth_id, | |||||
'aws-status' => 'Instance Requested')) | 'aws-status' => 'Instance Requested')) | ||||
->save(); | ->save(); | ||||
$this->log(pht( | $this->log(pht( | ||||
'Updated the Drydock resource with the instance information.')); | 'Updated the Drydock resource with the instance information.')); | ||||
$this->log(pht( | $this->log(pht( | ||||
'Tagging the instance with blueprint name and resource ID.')); | 'Tagging the instance with blueprint name and resource ID.')); | ||||
▲ Show 20 Lines • Show All 192 Lines • ▼ Show 20 Lines | if ($this->getDetail('allocate-elastic-ip')) { | ||||
$reservation = $result->reservationSet->item[0]; | $reservation = $result->reservationSet->item[0]; | ||||
$instance = $reservation->instancesSet->item[0]; | $instance = $reservation->instancesSet->item[0]; | ||||
$address = (string)$instance->privateIpAddress; | $address = (string)$instance->privateIpAddress; | ||||
} | } | ||||
// Update address and port attributes. | // Update address and port attributes. | ||||
$resource->setAttribute('host', $address); | $resource->setAttribute('host', $address); | ||||
if ($resource->getAttribute('protocol') === 'winrm') { | |||||
$resource->setAttribute('port', 5985); | |||||
} else { | |||||
$resource->setAttribute('port', 22); | $resource->setAttribute('port', 22); | ||||
} | |||||
$resource->save(); | $resource->save(); | ||||
$protocol_name = ''; | |||||
if ($resource->getAttribute('protocol') === 'winrm') { | |||||
$protocol_name = 'WinRM'; | |||||
} else { | |||||
$protocol_name = 'SSH'; | |||||
} | |||||
$this->log(pht( | $this->log(pht( | ||||
'Waiting for a successful SSH connection')); | 'Waiting for a successful %s connection', $protocol_name)); | ||||
// Wait until we get a successful SSH connection. | // Wait until we get a successful connection. | ||||
$ssh = id(new DrydockSSHCommandInterface()) | $ssh = $this->getInterface($resource, $lease, 'command'); | ||||
->setConfiguration(array( | |||||
'host' => $resource->getAttribute('host'), | |||||
'port' => $resource->getAttribute('port'), | |||||
'credential' => $resource->getAttribute('credential'), | |||||
'platform' => $resource->getAttribute('platform'))); | |||||
$ssh->setConnectTimeout(60); | |||||
$ssh->setExecTimeout(60); | $ssh->setExecTimeout(60); | ||||
if ($resource->getAttribute('protocol') === 'ssh') { | |||||
$ssh->setConnectTimeout(60); | |||||
} | |||||
$resource->setAttribute( | $resource->setAttribute( | ||||
'aws-status', | 'aws-status', | ||||
'Waiting for successful SSH connection'); | 'Waiting for successful %s connection', $protocol_name); | ||||
$resource->save(); | $resource->save(); | ||||
while (true) { | while (true) { | ||||
try { | try { | ||||
$this->log(pht( | $this->log(pht( | ||||
'Attempting to connect to \'%s\' via SSH', | 'Attempting to connect to \'%s\' via %s', | ||||
$instance_id)); | $instance_id, | ||||
$protocol_name)); | |||||
$ssh_future = $ssh->getExecFuture('echo "test"'); | $ssh_future = $ssh->getExecFuture('echo "test"'); | ||||
$ssh_future->resolvex(); | $ssh_future->resolvex(); | ||||
if ($ssh_future->getWasKilledByTimeout()) { | if ($ssh_future->getWasKilledByTimeout()) { | ||||
throw new Exception('SSH execution timed out.'); | throw new Exception('%s execution timed out.', $protocol_name); | ||||
} | } | ||||
break; | break; | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
// Make sure the instance hasn't been terminated or shutdown while | // Make sure the instance hasn't been terminated or shutdown while | ||||
// we've been trying to connect. | // we've been trying to connect. | ||||
$result = $this->getAWSEC2Future() | $result = $this->getAWSEC2Future() | ||||
->setRawAWSQuery( | ->setRawAWSQuery( | ||||
'DescribeInstances', | 'DescribeInstances', | ||||
array( | array( | ||||
'InstanceId.0' => $instance_id)) | 'InstanceId.0' => $instance_id)) | ||||
->resolve(); | ->resolve(); | ||||
$reservation = $result->reservationSet->item[0]; | $reservation = $result->reservationSet->item[0]; | ||||
$instance = $reservation->instancesSet->item[0]; | $instance = $reservation->instancesSet->item[0]; | ||||
$instance_state = (string)$instance->instanceState->name; | $instance_state = (string)$instance->instanceState->name; | ||||
$this->log(pht( | $this->log(pht( | ||||
'SSH connection not yet ready; instance is in state \'%s\'', | '%s connection not yet ready; instance is in state \'%s\'', | ||||
$protocol_name, | |||||
$instance_state)); | $instance_state)); | ||||
if ($instance_state === 'shutting-down' || | if ($instance_state === 'shutting-down' || | ||||
$instance_state === 'terminated') { | $instance_state === 'terminated') { | ||||
$this->log(pht( | $this->log(pht( | ||||
'Instance has ended up in state \'%s\' while waiting for an '. | 'Instance has ended up in state \'%s\' while waiting for an '. | ||||
'SSH connection', | '%s connection', | ||||
$instance_state)); | $instance_state, | ||||
$protocol_name)); | |||||
// Deallocate and release the public IP address if we allocated one. | // Deallocate and release the public IP address if we allocated one. | ||||
if ($resource->getAttribute('eip-allocated')) { | if ($resource->getAttribute('eip-allocated')) { | ||||
try { | try { | ||||
$this->getAWSEC2Future() | $this->getAWSEC2Future() | ||||
->setRawAWSQuery( | ->setRawAWSQuery( | ||||
'DisassociateAddress', | 'DisassociateAddress', | ||||
array( | array( | ||||
▲ Show 20 Lines • Show All 236 Lines • ▼ Show 20 Lines | final class DrydockAmazonEC2HostBlueprintImplementation | ||||
public function getInterface( | public function getInterface( | ||||
DrydockResource $resource, | DrydockResource $resource, | ||||
DrydockLease $lease, | DrydockLease $lease, | ||||
$type) { | $type) { | ||||
switch ($type) { | switch ($type) { | ||||
case 'command': | case 'command': | ||||
if ($resource->getAttribute('protocol') === 'ssh') { | |||||
return id(new DrydockSSHCommandInterface()) | return id(new DrydockSSHCommandInterface()) | ||||
->setConfiguration(array( | ->setConfiguration(array( | ||||
'host' => $resource->getAttribute('host'), | 'host' => $resource->getAttribute('host'), | ||||
'port' => $resource->getAttribute('port'), | 'port' => $resource->getAttribute('port'), | ||||
'credential' => $resource->getAttribute('credential'), | 'credential' => $resource->getAttribute('credential'), | ||||
'platform' => $resource->getAttribute('platform'))) | 'platform' => $resource->getAttribute('platform'),)) | ||||
->setWorkingDirectory($lease->getAttribute('path')); | ->setWorkingDirectory($lease->getAttribute('path')); | ||||
} else if ($resource->getAttribute('protocol') === 'winrm') { | |||||
return id(new DrydockWinRMCommandInterface()) | |||||
->setConfiguration(array( | |||||
'host' => $resource->getAttribute('host'), | |||||
'port' => $resource->getAttribute('port'), | |||||
'credential' => $resource->getAttribute('winrm-auth'), | |||||
'platform' => $resource->getAttribute('platform'),)) | |||||
->setWorkingDirectory($lease->getAttribute('path')); | |||||
} else { | |||||
throw new Exception('Unsupported protocol for remoting'); | |||||
} | |||||
case 'filesystem': | case 'filesystem': | ||||
return id(new DrydockSFTPFilesystemInterface()) | return id(new DrydockSFTPFilesystemInterface()) | ||||
->setConfiguration(array( | ->setConfiguration(array( | ||||
'host' => $resource->getAttribute('host'), | 'host' => $resource->getAttribute('host'), | ||||
'port' => $resource->getAttribute('port'), | 'port' => $resource->getAttribute('port'), | ||||
'credential' => $resource->getAttribute('credential'))); | 'credential' => $resource->getAttribute('credential'))); | ||||
} | } | ||||
Show All 34 Lines | return array( | ||||
'caption' => pht('e.g. %s', 't2.micro') | 'caption' => pht('e.g. %s', 't2.micro') | ||||
), | ), | ||||
'platform' => array( | 'platform' => array( | ||||
'name' => pht('Platform Name'), | 'name' => pht('Platform Name'), | ||||
'type' => 'text', | 'type' => 'text', | ||||
'required' => true, | 'required' => true, | ||||
'caption' => pht('e.g. %s or %s', 'windows', 'linux') | 'caption' => pht('e.g. %s or %s', 'windows', 'linux') | ||||
), | ), | ||||
'protocol' => array( | |||||
'name' => pht('Protocol'), | |||||
'type' => 'select', | |||||
'required' => true, | |||||
'options' => array( | |||||
'ssh' => 'SSH', | |||||
'winrm' => 'WinRM (Windows platform only)',), | |||||
), | |||||
'winrm-auth' => array( | |||||
'name' => pht('WinRM Credentials'), | |||||
'type' => 'credential', | |||||
'credential.provides' | |||||
=> PassphraseCredentialTypePassword::PROVIDES_TYPE, | |||||
'caption' => pht( | |||||
'This is only required if the protocol is set to WinRM.'), | |||||
), | |||||
'subnet-id' => array( | 'subnet-id' => array( | ||||
'name' => pht('VPC Subnet'), | 'name' => pht('VPC Subnet'), | ||||
'type' => 'text', | 'type' => 'text', | ||||
'required' => true, | 'required' => true, | ||||
'caption' => pht('e.g. %s', 'subnet-2a67439b') | 'caption' => pht('e.g. %s', 'subnet-2a67439b') | ||||
), | ), | ||||
'security-group-ids' => array( | 'security-group-ids' => array( | ||||
'name' => pht('VPC Security Groups'), | 'name' => pht('VPC Security Groups'), | ||||
Show All 26 Lines | return array( | ||||
'caption' => pht( | 'caption' => pht( | ||||
'When instances are placed in a VPC, and are not placed behind '. | 'When instances are placed in a VPC, and are not placed behind '. | ||||
'a NAT, an elastic IP may be required in order to establish '. | 'a NAT, an elastic IP may be required in order to establish '. | ||||
'outbound internet connections. Enable this option if elastic IP '. | 'outbound internet connections. Enable this option if elastic IP '. | ||||
'allocation is only enabled for this purpose, and you want to '. | 'allocation is only enabled for this purpose, and you want to '. | ||||
'connect to the machine on it\'s private IP address (because of '. | 'connect to the machine on it\'s private IP address (because of '. | ||||
'firewall rules).'), | 'firewall rules).'), | ||||
), | ), | ||||
'skip-ssh-setup-windows' => array( | 'skip-setup-windows' => array( | ||||
'name' => pht('Skip SSH setup on Windows'), | 'name' => pht('Skip setup on Windows'), | ||||
'type' => 'bool', | 'type' => 'bool', | ||||
'caption' => pht( | 'caption' => pht( | ||||
'If SSH is already configured on a Windows AMI, check this option. '. | 'If SSH or WinRM is already configured on a Windows AMI, check '. | ||||
'By default, Phabricator will automatically install and configure '. | 'this option. By default, Phabricator will automatically install '. | ||||
'SSH on the Windows image.') | 'and configure SSH or WinRM on the Windows image (depending on the '. | ||||
'protocol chosen).'), | |||||
), | ), | ||||
'spot' => array( | 'spot' => array( | ||||
'name' => pht('Spot Instances'), | 'name' => pht('Spot Instances'), | ||||
'type' => 'header' | 'type' => 'header' | ||||
), | ), | ||||
'spot-enabled' => array( | 'spot-enabled' => array( | ||||
'name' => pht('Use Spot Instances'), | 'name' => pht('Use Spot Instances'), | ||||
'type' => 'bool', | 'type' => 'bool', | ||||
Show All 31 Lines |