Scalr\Modules\Platforms\Ec2\Ec2PlatformModule::LaunchServer PHP Method

LaunchServer() public method

See also: Scalr\Modules\PlatformModuleInterface::LaunchServer()
public LaunchServer ( DBServer $DBServer, Scalr_Server_LaunchOption\Scalr_Server_LaunchOptions $launchOptions = null )
$DBServer DBServer
$launchOptions Scalr_Server_LaunchOption\Scalr_Server_LaunchOptions
    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)));
        }
    }