class Bosh::AwsCloud::InstanceManager

Constants

InstanceStorageMap

Public Class Methods

new(region, registry, elb, az_selector, logger) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 73
def initialize(region, registry, elb, az_selector, logger)
  @region = region
  @registry = registry
  @elb = elb
  @az_selector = az_selector
  @logger = logger
end

Public Instance Methods

create(agent_id, stemcell_id, resource_pool, networks_spec, disk_locality, environment, options) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 81
def create(agent_id, stemcell_id, resource_pool, networks_spec, disk_locality, environment, options)
  instance_params, block_device_info = build_instance_params(stemcell_id, resource_pool, networks_spec, disk_locality, options)

  @logger.info("Creating new instance with: #{instance_params.inspect}")

  aws_instance = create_aws_instance(instance_params, resource_pool)

  instance = Instance.new(aws_instance, @registry, @elb, @logger)

  begin
    # We need to wait here for the instance to be running, as if we are going to
    # attach to a load balancer, the instance must be running.
    instance.wait_for_running
    instance.attach_to_load_balancers(resource_pool['elbs'] || [])
  rescue => e
    @logger.warn("Failed to configure instance '#{instance.id}': #{e.inspect}")
    begin
      instance.terminate
    rescue => e
      @logger.error("Failed to terminate mis-configured instance '#{instance.id}': #{e.inspect}")
    end
    raise
  end

  block_device_agent_info = block_device_info
                              .group_by { |v| v[:bosh_type] }
                              .map { |type, devices| {type => devices.map { |device| {"path" => device[:device_name]} }} }
                              .inject({}) { |a, b| a.merge(b) }

  return instance, block_device_agent_info
end
find(instance_id) click to toggle source

@param [String] instance_id EC2 instance id

# File lib/cloud/aws/instance_manager.rb, line 114
def find(instance_id)
  Instance.new(@region.instances[instance_id], @registry, @elb, @logger)
end

Private Instance Methods

block_device_mapping(virtualization_type, resource_pool) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 180
def block_device_mapping(virtualization_type, resource_pool)
  ephemeral_disk_options = resource_pool.fetch("ephemeral_disk", {})

  requested_size = ephemeral_disk_options['size'] || 0
  actual_size = ephemeral_disk_options['size'] || 10 * 1024

  ephemeral_volume_properties = VolumeProperties.new(
    size: actual_size,
    type: ephemeral_disk_options['type'],
    iops: ephemeral_disk_options['iops'],
  )

  ephemeral_volume_properties.validate!

  instance_type = resource_pool.fetch('instance_type', 'unspecified')
  raw_instance_storage = resource_pool.fetch('raw_instance_storage', false)

  local_disk_info = InstanceManager::InstanceStorageMap[instance_type]
  if raw_instance_storage && local_disk_info.nil?
    raise Bosh::Clouds::CloudError, "raw_instance_storage requested for instance type '#{instance_type}' that does not have instance storage"
  end

  if raw_instance_storage || local_disk_info.nil? || local_disk_info.size < (requested_size / 1024.0).ceil
    @logger.debug('Use EBS storage to create the virtual machine')
    block_device_mapping_param = InstancesCreatePresenter.new(ephemeral_volume_properties).present
  else
    @logger.debug('Use instance storage to create the virtual machine')
    block_device_mapping_param = default_ephemeral_disk_mapping
  end

  block_device_mapping_param[0][:bosh_type] = 'ephemeral'

  if raw_instance_storage
    next_device = first_raw_ephemeral_device(virtualization_type)
    for i in 0..local_disk_info.count - 1 do
      block_device_mapping_param << {
        virtual_name: "ephemeral#{i}",
        device_name: next_device,
        bosh_type: "raw_ephemeral",
      }
      next_device = next_device.next
    end
  end

  if (resource_pool.has_key?('root_disk'))
    root_disk_size_in_mb = resource_pool['root_disk']['size']
    root_disk_type = resource_pool['root_disk'].fetch('type', 'standard')
    root_disk_iops = resource_pool['root_disk']['iops']
    root_disk_volume_properties = VolumeProperties.new(
      size: root_disk_size_in_mb,
      type: root_disk_type,
      iops: root_disk_iops
    )
    root_disk_volume_properties.validate!

    root_device = {
      :volume_size => (root_disk_size_in_mb / 1024.0).ceil,
      :volume_type => root_disk_type,
      :delete_on_termination => true,
    }

    if root_disk_type == 'io1' && root_disk_iops > 0
      root_device[:iops] = root_disk_iops
    end

    if virtualization_type == :hvm
      block_device_mapping_param << {
        device_name: "/dev/xvda",
        ebs: root_device
      }
    else
      block_device_mapping_param << {
        device_name: "/dev/sda",
        ebs: root_device
      }
    end
  end


  block_device_mapping_param
end
build_instance_params(stemcell_id, resource_pool, networks_spec, disk_locality, options) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 120
def build_instance_params(stemcell_id, resource_pool, networks_spec, disk_locality, options)
  virtualization_type = @region.images[stemcell_id].virtualization_type
  block_device_info = block_device_mapping(virtualization_type, resource_pool)

  instance_params = {count: 1}
  instance_params[:image_id] = stemcell_id
  instance_params[:instance_type] = resource_pool["instance_type"]
  instance_params[:block_device_mappings] = block_device_info.map { |entry| entry.reject { |k| k == :bosh_type } }
  instance_params[:placement_group] = resource_pool["placement_group"] if resource_pool["placement_group"]
  instance_params[:dedicated_tenancy] = true if resource_pool["tenancy"] == "dedicated"

  set_user_data_parameter(instance_params, networks_spec)
  set_key_name_parameter(instance_params, resource_pool["key_name"], options["aws"]["default_key_name"])
  set_security_groups_parameter(instance_params, networks_spec, options["aws"]["default_security_groups"])
  set_vpc_parameters(instance_params, networks_spec)
  set_iam_instance_profile_parameter(instance_params, resource_pool["iam_instance_profile"], options["aws"]["default_iam_instance_profile"])
  set_availability_zone_parameter(
    instance_params,
    (disk_locality || []).map { |volume_id| @region.volumes[volume_id].availability_zone.to_s },
    resource_pool["availability_zone"],
    (instance_params[:subnet].availability_zone_name if instance_params[:subnet])
  )

  return instance_params, block_device_info
end
create_aws_instance(instance_params, resource_pool) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 153
def create_aws_instance(instance_params, resource_pool)
  if resource_pool["spot_bid_price"]
    begin
      return create_aws_spot_instance instance_params, resource_pool["spot_bid_price"]
    rescue Bosh::Clouds::VMCreationFailed => e
      raise unless resource_pool["spot_ondemand_fallback"]
    end
  end

  # Retry the create instance operation a couple of times if we are told that the IP
  # address is in use - it can happen when the director recreates a VM and AWS
  # is too slow to update its state when we have released the IP address and want to
  # realocate it again.
  errors = [AWS::EC2::Errors::InvalidIPAddress::InUse, AWS::EC2::Errors::RequestLimitExceeded]
  Bosh::Common.retryable(sleep: instance_create_wait_time, tries: 20, on: errors) do |tries, error|
    @logger.info("Launching on demand instance...")
    if error.class == AWS::EC2::Errors::InvalidIPAddress::InUse
      @logger.warn("IP address was in use: #{error}")
    end
    @region.instances.create(instance_params)
  end
end
create_aws_spot_instance(instance_params, spot_bid_price) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 146
def create_aws_spot_instance(instance_params, spot_bid_price)
  @logger.info("Launching spot instance...")
  spot_manager = Bosh::AwsCloud::SpotManager.new(@region)

  spot_manager.create(instance_params, spot_bid_price)
end
first_raw_ephemeral_device(virtualization_type) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 262
def first_raw_ephemeral_device(virtualization_type)
  case virtualization_type
    when :hvm
      '/dev/xvdba'
    when :paravirtual
      '/dev/sdc'
    else
      raise Bosh::Clouds::CloudError, "unknown virtualization type #{virtualization_type}"
  end
end
instance_create_wait_time() click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 176
def instance_create_wait_time
  30
end
is_security_group_id?(security_group) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 339
def is_security_group_id?(security_group)
  security_group.start_with?('sg-') && security_group.size == 11
end
set_availability_zone_parameter(instance_params, volume_zones, resource_pool_zone, subnet_zone) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 302
def set_availability_zone_parameter(instance_params, volume_zones, resource_pool_zone, subnet_zone)
  availability_zone = @az_selector.common_availability_zone(volume_zones, resource_pool_zone, subnet_zone)
  instance_params[:availability_zone] = availability_zone if availability_zone
end
set_iam_instance_profile_parameter(instance_params, resource_pool_iam_instance_profile, default_aws_iam_instance_profile) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 316
def set_iam_instance_profile_parameter(instance_params, resource_pool_iam_instance_profile, default_aws_iam_instance_profile)
  iam_instance_profile = resource_pool_iam_instance_profile || default_aws_iam_instance_profile
  instance_params[:iam_instance_profile] = iam_instance_profile unless iam_instance_profile.nil?
end
set_key_name_parameter(instance_params, resource_pool_key_name, default_aws_key_name) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 273
def set_key_name_parameter(instance_params, resource_pool_key_name, default_aws_key_name)
  key_name = resource_pool_key_name || default_aws_key_name
  instance_params[:key_name] = key_name unless key_name.nil?
end
set_security_groups_parameter(instance_params, networks_spec, default_security_groups) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 278
def set_security_groups_parameter(instance_params, networks_spec, default_security_groups)
  security_groups = extract_security_groups(networks_spec)
  if security_groups.empty?
    validate_and_prepare_security_groups_parameter(instance_params, default_security_groups)
  else
    validate_and_prepare_security_groups_parameter(instance_params, security_groups)
  end
end
set_user_data_parameter(instance_params, networks_spec) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 307
def set_user_data_parameter(instance_params, networks_spec)
  user_data = {registry: {endpoint: @registry.endpoint}}

  spec_with_dns = networks_spec.values.select { |spec| spec.has_key? "dns" }.first
  user_data[:dns] = {nameserver: spec_with_dns["dns"]} if spec_with_dns

  instance_params[:user_data] = Yajl::Encoder.encode(user_data)
end
set_vpc_parameters(instance_params, network_spec) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 287
def set_vpc_parameters(instance_params, network_spec)
  manual_network_spec = network_spec.values.select { |spec| ["manual", nil].include? spec["type"] }.first
  if manual_network_spec
    instance_params[:private_ip_address] = manual_network_spec["ip"]
  end

  subnet_network_spec = network_spec.values.select { |spec|
    ["manual", nil, "dynamic"].include?(spec["type"]) &&
      spec.fetch("cloud_properties", {}).has_key?("subnet")
  }.first
  if subnet_network_spec
    instance_params[:subnet] = @region.subnets[subnet_network_spec["cloud_properties"]["subnet"]]
  end
end
validate_and_prepare_security_groups_parameter(instance_params, security_groups) click to toggle source
# File lib/cloud/aws/instance_manager.rb, line 321
def validate_and_prepare_security_groups_parameter(instance_params, security_groups)
  return if security_groups.nil? || security_groups.empty?

  is_id = is_security_group_id?(security_groups.first)

  security_groups.drop(1).each do |security_group|
    unless is_security_group_id?(security_group) == is_id
      raise Bosh::Clouds::CloudError, 'security group names and ids can not be used together in security groups'
    end
  end

  if is_id
    instance_params[:security_group_ids] = security_groups
  else
    instance_params[:security_groups] = security_groups
  end
end