public function LaunchServer(DBServer $DBServer, Scalr_Server_LaunchOptions $launchOptions = null)
{
$runInstanceRequest = new RunInstancesRequestData(isset($launchOptions->imageId) ? $launchOptions->imageId : null, 1, 1);
$environment = $DBServer->GetEnvironmentObject();
$governance = new \Scalr_Governance($DBServer->envId);
$placementData = null;
$noSecurityGroups = false;
if (!$launchOptions) {
$launchOptions = new Scalr_Server_LaunchOptions();
$dbFarmRole = $DBServer->GetFarmRoleObject();
$DBRole = $dbFarmRole->GetRoleObject();
$runInstanceRequest->setMonitoring($dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_ENABLE_CW_MONITORING));
$image = $DBRole->__getNewRoleObject()->getImage(SERVER_PLATFORMS::EC2, $dbFarmRole->CloudLocation);
$launchOptions->imageId = $image->imageId;
if ($DBRole->isScalarized) {
if (!$image->getImage()->isScalarized && $image->getImage()->hasCloudInit) {
$useCloudInit = true;
}
}
// Need OS Family to get block device mapping for OEL roles
$launchOptions->osFamily = $image->getImage()->getOs()->family;
$launchOptions->cloudLocation = $dbFarmRole->CloudLocation;
$akiId = $DBServer->GetProperty(EC2_SERVER_PROPERTIES::AKIID);
if (!$akiId) {
$akiId = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_AKI_ID);
}
if ($akiId) {
$runInstanceRequest->kernelId = $akiId;
}
$ariId = $DBServer->GetProperty(EC2_SERVER_PROPERTIES::ARIID);
if (!$ariId) {
$ariId = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_ARI_ID);
}
if ($ariId) {
$runInstanceRequest->ramdiskId = $ariId;
}
$iType = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::INSTANCE_TYPE);
$launchOptions->serverType = $iType;
// Check governance of instance types
$types = $governance->getValue('ec2', Scalr_Governance::INSTANCE_TYPE);
if (count($types) > 0) {
if (!in_array($iType, $types)) {
throw new Exception(sprintf("Instance type '%s' was prohibited to use by scalr account owner", $iType));
}
}
$iamProfileArn = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_IAM_INSTANCE_PROFILE_ARN);
if ($iamProfileArn) {
$iamInstanceProfile = new IamInstanceProfileRequestData($iamProfileArn);
$runInstanceRequest->setIamInstanceProfile($iamInstanceProfile);
}
if ($dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_EBS_OPTIMIZED) == 1) {
$runInstanceRequest->ebsOptimized = true;
} else {
$runInstanceRequest->ebsOptimized = false;
}
// Custom user-data (base.custom_user_data)
$u_data = '';
foreach ($DBServer->GetCloudUserData() as $k => $v) {
$u_data .= "{$k}={$v};";
}
$u_data = trim($u_data, ";");
if (!empty($useCloudInit)) {
$customUserData = file_get_contents(APPPATH . "/templates/services/cloud_init/config.tpl");
} else {
$customUserData = $dbFarmRole->GetSetting('base.custom_user_data');
}
if ($customUserData) {
$repos = $DBServer->getScalarizrRepository();
$userData = str_replace(array('{SCALR_USER_DATA}', '{RPM_REPO_URL}', '{DEB_REPO_URL}'), array($u_data, $repos['rpm_repo_url'], $repos['deb_repo_url']), $customUserData);
} else {
$userData = $u_data;
}
if ($DBRole->isScalarized) {
$runInstanceRequest->userData = base64_encode($userData);
}
$vpcId = $dbFarmRole->GetFarmObject()->GetSetting(Entity\FarmSetting::EC2_VPC_ID);
if ($vpcId) {
if ($DBRole->hasBehavior(ROLE_BEHAVIORS::VPC_ROUTER)) {
$networkInterface = new InstanceNetworkInterfaceSetRequestData();
$networkInterface->networkInterfaceId = $dbFarmRole->GetSetting(Scalr_Role_Behavior_Router::ROLE_VPC_NID);
$networkInterface->deviceIndex = 0;
$networkInterface->deleteOnTermination = false;
$runInstanceRequest->setNetworkInterface($networkInterface);
$noSecurityGroups = true;
} else {
$vpcSubnetId = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_VPC_SUBNET_ID);
// VPC Support v2
if ($vpcSubnetId && substr($vpcSubnetId, 0, 6) != 'subnet') {
$subnets = json_decode($vpcSubnetId);
$servers = $DBServer->GetFarmRoleObject()->GetServersByFilter(array("status" => array(SERVER_STATUS::RUNNING, SERVER_STATUS::INIT, SERVER_STATUS::PENDING)));
$subnetsDistribution = array();
foreach ($servers as $cDbServer) {
if ($cDbServer->serverId != $DBServer->serverId) {
$subnetsDistribution[$cDbServer->GetProperty(EC2_SERVER_PROPERTIES::SUBNET_ID)]++;
}
}
$sCount = 1000000;
foreach ($subnets as $subnet) {
if ((int) $subnetsDistribution[$subnet] <= $sCount) {
$sCount = (int) $subnetsDistribution[$subnet];
$selectedSubnetId = $subnet;
}
}
} else {
$vpcInternetAccess = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_VPC_INTERNET_ACCESS);
if (!$vpcSubnetId) {
$aws = $environment->aws($launchOptions->cloudLocation);
$subnet = $this->AllocateNewSubnet($aws->ec2, $vpcId, $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_VPC_AVAIL_ZONE), 24);
try {
$subnet->createTags(array(array('key' => "scalr-id", 'value' => SCALR_ID), array('key' => "scalr-sn-type", 'value' => $vpcInternetAccess), array('key' => "Name", 'value' => 'Scalr System Subnet')));
} catch (Exception $e) {
}
try {
$routeTableId = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_VPC_ROUTING_TABLE_ID);
\Scalr::getContainer()->logger('VPC')->warn(new FarmLogMessage($DBServer, "Internet access: {$vpcInternetAccess}"));
if (!$routeTableId) {
if ($vpcInternetAccess == Scalr_Role_Behavior_Router::INTERNET_ACCESS_OUTBOUND) {
$routerRole = $DBServer->GetFarmObject()->GetFarmRoleByBehavior(ROLE_BEHAVIORS::VPC_ROUTER);
if (!$routerRole) {
if (\Scalr::config('scalr.instances_connection_policy') != 'local') {
throw new Exception("Outbound access require VPC router role in farm");
}
}
$networkInterfaceId = $routerRole->GetSetting(Scalr_Role_Behavior_Router::ROLE_VPC_NID);
\Scalr::getContainer()->logger('EC2')->warn(new FarmLogMessage($DBServer, "Requesting outbound routing table. NID: {$networkInterfaceId}"));
$routeTableId = $this->getRoutingTable($vpcInternetAccess, $aws, $networkInterfaceId, $vpcId);
\Scalr::getContainer()->logger('EC2')->warn(new FarmLogMessage($DBServer, "Routing table ID: {$routeTableId}"));
} elseif ($vpcInternetAccess == Scalr_Role_Behavior_Router::INTERNET_ACCESS_FULL) {
$routeTableId = $this->getRoutingTable($vpcInternetAccess, $aws, null, $vpcId);
}
}
$aws->ec2->routeTable->associate($routeTableId, $subnet->subnetId);
} catch (Exception $e) {
\Scalr::getContainer()->logger('EC2')->warn(new FarmLogMessage($DBServer, "Removing allocated subnet, due to routing table issues"));
$aws->ec2->subnet->delete($subnet->subnetId);
throw $e;
}
$selectedSubnetId = $subnet->subnetId;
$dbFarmRole->SetSetting(Entity\FarmRoleSetting::AWS_VPC_SUBNET_ID, $selectedSubnetId, Entity\FarmRoleSetting::TYPE_LCL);
} else {
$selectedSubnetId = $vpcSubnetId;
}
}
if ($selectedSubnetId) {
$networkInterface = new InstanceNetworkInterfaceSetRequestData();
$networkInterface->deviceIndex = 0;
$networkInterface->deleteOnTermination = true;
//
//Check network private or public
//
// We don't need public IP for private subnets
$info = $this->listSubnets($environment, $launchOptions->cloudLocation, $vpcId, true, $selectedSubnetId);
if ($info && $info['type'] == 'public') {
$networkInterface->associatePublicIpAddress = true;
}
$networkInterface->subnetId = $selectedSubnetId;
$staticPrivateIpsMap = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_PRIVATE_IPS_MAP);
if (!empty($staticPrivateIpsMap)) {
$map = @json_decode($staticPrivateIpsMap, true);
if (array_key_exists((int) $DBServer->index, $map)) {
$networkInterface->privateIpAddress = $map[$DBServer->index];
}
}
$aws = $environment->aws($launchOptions->cloudLocation);
$sgroups = $this->GetServerSecurityGroupsList($DBServer, $aws->ec2, $vpcId, $governance, $launchOptions->osFamily);
$networkInterface->setSecurityGroupId($sgroups);
$runInstanceRequest->setNetworkInterface($networkInterface);
$noSecurityGroups = true;
//$runInstanceRequest->subnetId = $selectedSubnetId;
} else {
throw new Exception("Unable to define subnetId for role in VPC");
}
}
}
$rootDevice = json_decode($DBServer->GetFarmRoleObject()->GetSetting(\Scalr_Role_Behavior::ROLE_BASE_ROOT_DEVICE_CONFIG), true);
if ($rootDevice && $rootDevice['settings']) {
$rootDeviceSettings = $rootDevice['settings'];
}
$instanceInitiatedShutdownBehavior = $dbFarmRole->GetSetting(Entity\FarmRoleSetting::AWS_SHUTDOWN_BEHAVIOR);
} else {
$instanceInitiatedShutdownBehavior = null;
$runInstanceRequest->userData = base64_encode(trim($launchOptions->userData));
}
$aws = $environment->aws($launchOptions->cloudLocation);
if (!$vpcId) {
$vpcId = $this->getDefaultVpc($environment, $launchOptions->cloudLocation);
}
// Set AMI, AKI and ARI ids
$runInstanceRequest->imageId = $launchOptions->imageId;
$runInstanceRequest->instanceInitiatedShutdownBehavior = $instanceInitiatedShutdownBehavior ?: 'terminate';
if (!$noSecurityGroups) {
foreach ($this->GetServerSecurityGroupsList($DBServer, $aws->ec2, $vpcId, $governance, $launchOptions->osFamily) as $sgroup) {
$runInstanceRequest->appendSecurityGroupId($sgroup);
}
if (!$runInstanceRequest->subnetId) {
// Set availability zone
if (!$launchOptions->availZone) {
$avail_zone = $this->GetServerAvailZone($DBServer, $aws->ec2, $launchOptions);
if ($avail_zone) {
$placementData = new PlacementResponseData($avail_zone);
}
} else {
$placementData = new PlacementResponseData($launchOptions->availZone);
}
}
}
$runInstanceRequest->minCount = 1;
$runInstanceRequest->maxCount = 1;
// Set instance type
$runInstanceRequest->instanceType = $launchOptions->serverType;
if ($rootDeviceSettings) {
$ebs = new EbsBlockDeviceData(array_key_exists(FarmRoleStorageConfig::SETTING_EBS_SIZE, $rootDeviceSettings) ? $rootDeviceSettings[FarmRoleStorageConfig::SETTING_EBS_SIZE] : null, null, array_key_exists(FarmRoleStorageConfig::SETTING_EBS_TYPE, $rootDeviceSettings) ? $rootDeviceSettings[FarmRoleStorageConfig::SETTING_EBS_TYPE] : null, array_key_exists(FarmRoleStorageConfig::SETTING_EBS_IOPS, $rootDeviceSettings) ? $rootDeviceSettings[FarmRoleStorageConfig::SETTING_EBS_IOPS] : null, true, null);
$deviceName = !empty($rootDeviceSettings[FarmRoleStorageConfig::SETTING_EBS_DEVICE_NAME]) ? $rootDeviceSettings[FarmRoleStorageConfig::SETTING_EBS_DEVICE_NAME] : '/dev/sda1';
$rootBlockDevice = new BlockDeviceMappingData($deviceName, null, null, $ebs);
$runInstanceRequest->appendBlockDeviceMapping($rootBlockDevice);
}
foreach ($this->GetBlockDeviceMapping($launchOptions->serverType) as $bdm) {
$runInstanceRequest->appendBlockDeviceMapping($bdm);
}
$placementData = $this->GetPlacementGroupData($launchOptions->serverType, $DBServer, $placementData);
if ($placementData !== null) {
$runInstanceRequest->setPlacement($placementData);
}
$skipKeyValidation = false;
$sshKey = new SshKey();
$farmId = NULL;
if ($DBServer->status == SERVER_STATUS::TEMPORARY) {
$keyName = "SCALR-ROLESBUILDER-" . SCALR_ID;
if (!$sshKey->loadGlobalByName($DBServer->envId, SERVER_PLATFORMS::EC2, $launchOptions->cloudLocation, $keyName)) {
$keyName = "SCALR-ROLESBUILDER-" . SCALR_ID . "-{$DBServer->envId}";
}
} else {
$keyName = $governance->getValue(SERVER_PLATFORMS::EC2, \Scalr_Governance::AWS_KEYPAIR);
if ($keyName) {
$skipKeyValidation = true;
} else {
$keyName = "FARM-{$DBServer->farmId}-" . SCALR_ID;
$farmId = $DBServer->farmId;
$oldKeyName = "FARM-{$DBServer->farmId}";
if ($sshKey->loadGlobalByName($DBServer->envId, SERVER_PLATFORMS::EC2, $launchOptions->cloudLocation, $oldKeyName)) {
$keyName = $oldKeyName;
$skipKeyValidation = true;
}
}
}
if (!$skipKeyValidation && !$sshKey->loadGlobalByName($DBServer->envId, SERVER_PLATFORMS::EC2, $launchOptions->cloudLocation, $keyName)) {
$result = $aws->ec2->keyPair->create($keyName);
if ($result->keyMaterial) {
$sshKey->farmId = $farmId;
$sshKey->envId = $DBServer->envId;
$sshKey->type = SshKey::TYPE_GLOBAL;
$sshKey->platform = SERVER_PLATFORMS::EC2;
$sshKey->cloudLocation = $launchOptions->cloudLocation;
$sshKey->cloudKeyName = $keyName;
$sshKey->privateKey = $result->keyMaterial;
$sshKey->generatePublicKey();
$sshKey->save();
}
}
$runInstanceRequest->keyName = $keyName;
try {
$result = $aws->ec2->instance->run($runInstanceRequest);
} catch (Exception $e) {
if (stristr($e->getMessage(), "The key pair") && stristr($e->getMessage(), "does not exist")) {
$sshKey->delete();
throw $e;
}
if (stristr($e->getMessage(), "The requested configuration is currently not supported for this AMI")) {
\Scalr::getContainer()->logger(__CLASS__)->fatal(sprintf("Unsupported configuration: %s", json_encode($runInstanceRequest)));
}
if (stristr($e->getMessage(), "The requested Availability Zone is no longer supported") || stristr($e->getMessage(), "is not supported in your requested Availability Zone") || stristr($e->getMessage(), "capacity in the Availability Zone you requested") || stristr($e->getMessage(), "Our system will be working on provisioning additional capacity") || stristr($e->getMessage(), "is currently constrained and we are no longer accepting new customer requests")) {
$availZone = $runInstanceRequest->getPlacement() ? $runInstanceRequest->getPlacement()->availabilityZone : null;
if ($availZone) {
$DBServer->GetEnvironmentObject()->setPlatformConfig(array("aws.{$launchOptions->cloudLocation}.{$availZone}.unavailable" => time()));
}
throw $e;
} else {
throw $e;
}
}
if ($result->instancesSet->get(0)->instanceId) {
$instanceTypeInfo = $this->getInstanceType($runInstanceRequest->instanceType, $environment, $launchOptions->cloudLocation);
/* @var $instanceTypeInfo CloudInstanceType */
$DBServer->SetProperties([\EC2_SERVER_PROPERTIES::REGION => $launchOptions->cloudLocation, \EC2_SERVER_PROPERTIES::AVAIL_ZONE => $result->instancesSet->get(0)->placement->availabilityZone, \EC2_SERVER_PROPERTIES::INSTANCE_ID => $result->instancesSet->get(0)->instanceId, \EC2_SERVER_PROPERTIES::AMIID => $runInstanceRequest->imageId, \EC2_SERVER_PROPERTIES::VPC_ID => $result->instancesSet->get(0)->vpcId, \EC2_SERVER_PROPERTIES::SUBNET_ID => $result->instancesSet->get(0)->subnetId, \EC2_SERVER_PROPERTIES::ARCHITECTURE => $result->instancesSet->get(0)->architecture, \SERVER_PROPERTIES::INFO_INSTANCE_VCPUS => isset($instanceTypeInfo['vcpus']) ? $instanceTypeInfo['vcpus'] : null]);
$DBServer->setOsType($result->instancesSet->get(0)->platform ? $result->instancesSet->get(0)->platform : 'linux');
$DBServer->cloudLocation = $launchOptions->cloudLocation;
$DBServer->cloudLocationZone = $result->instancesSet->get(0)->placement->availabilityZone;
$DBServer->update(['type' => $runInstanceRequest->instanceType, 'instanceTypeName' => $runInstanceRequest->instanceType]);
$DBServer->imageId = $launchOptions->imageId;
// we set server history here
$DBServer->getServerHistory()->update(['cloudServerId' => $result->instancesSet->get(0)->instanceId]);
return $DBServer;
} else {
throw new Exception(sprintf(_("Cannot launch new instance. %s"), serialize($result)));
}
}