cloudfoundry-community/cyoi

View on GitHub
lib/cyoi/providers/clients/aws_provider_client.rb

Summary

Maintainability
B
6 hrs
Test Coverage
# Copyright (c) 2012-2013 Stark & Wayne, LLC

module Cyoi; module Providers; module Clients; end; end; end

require "ipaddr"
require "cyoi/providers/clients/fog_provider_client"
require "cyoi/providers/constants/aws_constants"

class Cyoi::Providers::Clients::AwsProviderClient < Cyoi::Providers::Clients::FogProviderClient
  include Cyoi::Providers::Constants::AwsConstants

  # Creates a bucket and makes it publicly read-only
  def create_blobstore(blobstore_name)
    super
    policy = {
      "Version" => "2008-10-17",
      "Statement" => [
        {
          "Sid" => "AddPerm",
          "Effect" => "Allow",
          "Principal" => {
            "AWS" => "*"
          },
          "Action" => "s3:GetObject",
          "Resource" => "arn:aws:s3:::#{blobstore_name}/*"
        }
      ]
    }
    fog_storage.put_bucket_policy(blobstore_name, policy)
  end

  # @return [Integer] megabytes of RAM for requested flavor of server
  def ram_for_server_flavor(server_flavor_id)
    if flavor = fog_compute_flavor(server_flavor_id)
      flavor[:ram]
    else
      raise "Unknown AWS flavor '#{server_flavor_id}'"
    end
  end

  # @return [Hash] e.g. { :bits => 0, :cores => 2, :disk => 0,
  #   :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
  # or nil if +server_flavor_id+ is not a supported flavor ID
  def fog_compute_flavor(server_flavor_id)
    aws_compute_flavors.find { |fl| fl[:id] == server_flavor_id }
  end

  # @return [Array] of [Hash] for each supported compute flavor
  # Example [Hash] { :bits => 0, :cores => 2, :disk => 0,
  #   :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
  def aws_compute_flavors
    Fog::Compute::AWS::FLAVORS
  end

  def aws_compute_flavor_ids
    aws_compute_flavors.map { |fl| fl[:id] }
  end

  # Provision an EC2 or VPC elastic IP addess.
  # * VPC - provision_public_ip_address(vpc: true)
  # * EC2 - provision_public_ip_address
  # @return [String] provisions a new public IP address in target region
  # TODO nil if none available
  def provision_public_ip_address(options={})
    if options.delete(:vpc)
      options[:domain] = "vpc"
    else
      options[:domain] = options.delete(:domain) || "standard"
    end
    address = fog_compute.addresses.create(options)
    address.public_ip
    # TODO catch error and return nil
  end

  def associate_ip_address_with_server(ip_address, server)
    address = fog_compute.addresses.get(ip_address)
    address.server = server
  end

  # @return [String] IP that is available for a new VM to use in a subnet
  # AWS reserves both the first four IP addresses and the last IP address in each subnet CIDR block.
  # They're not available for you to use.
  def next_available_ip_in_subnet(subnet)
    return nil if subnet.available_ip_address_count.to_i < 1
    ip = IPAddr.new(subnet.cidr_block)
    4.times { ip = ip.succ }
    skip_ips = ip_addresses_assigned_to_servers
    while skip_ips.include?(ip.to_s)
      ip = ip.succ
    end
    ip.to_s
  end

  def ip_addresses_assigned_to_servers
    fog_compute.servers.map {|s| s.private_ip_address}
  end


  def create_vpc(name, cidr_block)
    vpc = fog_compute.vpcs.create(name: name, cidr_block: cidr_block)
    vpc.id
  end

  # @return [boolean] true if target OpenStack running Neutron networks
  def networks?
    vpcs.size > 0
  end

  def vpcs
    fog_compute.vpcs
  end

  def subnets
    fog_compute.subnets
  end

  # Creates a VPC subnet
  # @return [String] the subnet_id
  def create_subnet(vpc_id, cidr_block)
    subnet = fog_compute.subnets.create(vpc_id: vpc_id, cidr_block: cidr_block)
    subnet.subnet_id
  end

  def create_internet_gateway(vpc_id)
    gateway = fog_compute.internet_gateways.create(vpc_id: vpc_id)
    gateway.id
  end

  def ip_permissions(sg)
    sg.ip_permissions
  end

  def port_open?(ip_permissions, port_range, protocol, ip_range)
    ip_permissions && ip_permissions.find do |ip|
     ip["ipProtocol"] == protocol \
     && ip["ipRanges"].detect { |range| range["cidrIp"] == ip_range } \
     && ip["fromPort"] <= port_range.min \
     && ip["toPort"] >= port_range.max
    end
  end

  def authorize_port_range(sg, port_range, protocol, ip_range)
    sg.authorize_port_range(port_range, {:ip_protocol => protocol, :cidr_ip => ip_range})
  end

  def find_server_device(server, device)
    server.volumes.all.find {|v| v.device == device}
  end

  def create_and_attach_volume(name, disk_size, server, device)
    volume = fog_compute.volumes.create(
        size: disk_size,
        name: name,
        description: '',
        device: device,
        availability_zone: server.availability_zone)
    # TODO: the following works in fog 1.9.0+ (but which has a bug in bootstrap)
    # https://github.com/fog/fog/issues/1516
    #
    # volume.wait_for { volume.status == 'available' }
    # volume.attach(server.id, "/dev/vdc")
    # volume.wait_for { volume.status == 'in-use' }
    #
    # Instead, using:
    volume.server = server
  end

  # Ubuntu 14.04
  def trusty_image_id(region=nil)
    region = fog_compute.region
    # http://cloud-images.ubuntu.com/locator/ec2/
    # version: 14.04 LTS
    # arch: amd64
    # instance type: ebs-ssd (not hvm)
    # Using release 20140927
    image_id = case region.to_s
    when "ap-northeast-1"
      "ami-df4b60de"
    when "ap-northeast-2"
      "ami-09dc1267"
    when "ap-southeast-1"
      "ami-2ce7c07e"
    when "eu-west-1"
      "ami-f6b11181"
    when "sa-east-1"
      "ami-71d2676c"
    when "us-east-1"
      "ami-98aa1cf0"
    when "us-west-1"
      "ami-736e6536"
    when "eu-central-1"
      "ami-423c0a5f"
    when "cn-north-1"
      "ami-e642d0df"
    when "ap-southeast-2"
      "ami-1f117325"
    when "us-west-2"
      "ami-37501207"
    end
    image_id || raise("Please add Ubuntu 14.04 64bit (EBS SSD) AMI image id to aws.rb#trusty_image_id method for region '#{region}'")
  end

  def bootstrap(new_attributes = {})
    new_attributes[:image_id] ||= trusty_image_id(fog_compute.region)
    vpc = new_attributes[:subnet_id]

    server = fog_compute.servers.new(new_attributes)

    unless new_attributes[:key_name]
      raise "please provide :key_name attribute"
    end
    unless private_key_path = new_attributes.delete(:private_key_path)
      raise "please provide :private_key_path attribute"
    end

    if vpc
      # TODO setup security group on new server
    else
      # make sure port 22 is open in the first security group
      security_group = fog_compute.security_groups.get(server.groups.first)
      authorized = security_group.ip_permissions.detect do |ip_permission|
        ip_permission['ipRanges'].first && ip_permission['ipRanges'].first['cidrIp'] == '0.0.0.0/0' &&
        ip_permission['fromPort'] == 22 &&
        ip_permission['ipProtocol'] == 'tcp' &&
        ip_permission['toPort'] == 22
      end
      unless authorized
        security_group.authorize_port_range(22..22)
      end
    end

    server.save
    unless Fog.mocking?
      server.wait_for { ready? }
      server.setup(:keys => [private_key_path])
    end
    server
  end

  def fog_storage
    @fog_storage ||= begin
      configuration = Fog.symbolize_credentials(attributes.credentials)
      configuration[:provider] = "AWS"
      Fog::Storage.new(configuration)
    end
  end

  # Fog::Network does not exist for aws, use Fog::Compute instead
  def fog_network
    fog_compute
  end

  # Construct a Fog::Compute object
  # Uses +attributes+ which normally originates from +settings.provider+
  def setup_fog_connection
    configuration = Fog.symbolize_credentials(attributes.credentials)
    configuration[:provider] = "AWS"
    configuration[:region] = attributes.region
    @fog_compute = Fog::Compute.new(configuration)
  end
end