1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Merge pull request #1971 from trobrock/better-digitalocean

Better digitalocean support
This commit is contained in:
Sergio Rubio 2013-07-29 14:05:49 -07:00
commit 9e1cacf7da
23 changed files with 210 additions and 111 deletions

1
.gitignore vendored
View file

@ -21,3 +21,4 @@ pkg
spec/credentials.yml
vendor/*
tags
tests/digitalocean/fixtures/

View file

@ -90,17 +90,46 @@ module Fog
params[:query].merge!(:api_key => @digitalocean_api_key)
params[:query].merge!(:client_id => @digitalocean_client_id)
response = @connection.request(params)
response = retry_event_lock { parse @connection.request(params) }
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
if response.body['status'] != 'OK'
raise Fog::Errors::Error.new response.body.to_s
case response.body['error_message']
when /No Droplets Found/
raise Fog::Errors::NotFound.new
else
raise Fog::Errors::Error.new response.body.to_s
end
end
end
response
end
private
def parse(response)
return response if response.body.empty?
response.body = Fog::JSON.decode(response.body)
response
end
def retry_event_lock
count = 0
reponse = nil
while count < 5
response = yield
if response.body && response.body['error_message'] =~ /There is already a pending event for the droplet/
count += 1
sleep count ** 3
else
break
end
end
response
end
end
end
end

View file

@ -7,18 +7,26 @@ module Fog
# A DigitalOcean Droplet
#
class Server < Fog::Compute::Server
identity :id
attribute :name
attribute :state, :aliases => 'status'
attribute :state, :aliases => 'status'
attribute :image_id
attribute :region_id
attribute :flavor_id, :aliases => 'size_id'
attribute :flavor_id, :aliases => 'size_id'
# Not documented in their API, but
# available nevertheless
attribute :ip_address
attribute :public_ip_address, :aliases => 'ip_address'
attribute :backups_active
attr_writer :ssh_keys
# Deprecated: Use public_ip_address instead.
def ip_address
Fog::Logger.warning("ip_address has been deprecated. Use public_ip_address instead")
public_ip_address
end
# Reboot the server (soft reboot).
#
# The preferred method of rebooting a server.
@ -52,7 +60,7 @@ module Fog
# Works as a power switch.
# The server consumes resources while powered off
# so you are still charged.
#
#
# @see https://www.digitalocean.com/community/questions/am-i-charged-while-my-droplet-is-in-a-powered-off-state
def stop
requires :id
@ -64,7 +72,7 @@ module Fog
# The server consumes resources while powered on
# so you will be charged.
#
# Each time a server is spun up, even if for a few seconds,
# Each time a server is spun up, even if for a few seconds,
# it is charged for an hour.
#
def start
@ -72,6 +80,25 @@ module Fog
service.power_on_server self.id
end
def setup(credentials = {})
requires :public_ip_address
require 'net/ssh'
commands = [
%{mkdir .ssh},
%{passwd -l #{username}},
%{echo "#{Fog::JSON.encode(Fog::JSON.sanitize(attributes))}" >> ~/attributes.json}
]
if public_key
commands << %{echo "#{public_key}" >> ~/.ssh/authorized_keys}
end
# wait for aws to be ready
wait_for { sshable?(credentials) }
Fog::SSH.new(public_ip_address, username, credentials).run(commands)
end
# Creates the server (not to be called directly).
#
# Usually called by Fog::Collection#create
@ -85,7 +112,7 @@ module Fog
# :image_id => image_id_here,
# :flavor_id => flavor_id_here,
# :region_id => region_id_here
#
#
# @return [Boolean]
def save
raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted?
@ -93,11 +120,13 @@ module Fog
options = {}
if attributes[:ssh_key_ids]
options[:ssh_key_ids] = attributes[:ssh_key_ids]
options[:ssh_key_ids] = attributes[:ssh_key_ids]
elsif @ssh_keys
options[:ssh_key_ids] = @ssh_keys.map(&:id)
end
data = service.create_server name,
flavor_id,
image_id,
data = service.create_server name,
flavor_id,
image_id,
region_id,
options
merge_attributes(data.body['droplet'])
@ -105,7 +134,7 @@ module Fog
end
# Destroy the server, freeing up the resources.
#
#
# DigitalOcean will stop charging you for the resources
# the server was using.
#
@ -115,9 +144,9 @@ module Fog
# IMPORTANT: As of 2013/01/31, you should wait some time to
# destroy the server after creating it. If you try to destroy
# the server too fast, the destroy event may be lost and the
# server will remain running and consuming resources, so
# server will remain running and consuming resources, so
# DigitalOcean will keep charging you.
# Double checked this with DigitalOcean staff and confirmed
# Double checked this with DigitalOcean staff and confirmed
# that it's the way it works right now.
#
# Double check the server has been destroyed!

View file

@ -6,16 +6,39 @@ module Fog
class DigitalOcean
class Servers < Fog::Collection
model Fog::Compute::DigitalOcean::Server
def all(filters = {})
load service.list_servers.body['droplets']
data = service.list_servers.body['droplets']
load(data)
end
def bootstrap(new_attributes = {})
server = new(new_attributes)
raise(ArgumentError, "public_key_path is required to configure the server.") unless new_attributes[:public_key_path]
raise(ArgumentError, "private_key_path is required to configure the server.") unless new_attributes[:private_key_path]
credential = Fog.respond_to?(:credential) && Fog.credential || :default
name = "fog_#{credential}"
ssh_key = service.ssh_keys.detect { |key| key.name == name }
if ssh_key.nil?
ssh_key = service.ssh_keys.create(
:name => name,
:ssh_pub_key => File.read(new_attributes[:public_key_path])
)
end
server.ssh_keys = [ssh_key]
server.save
server.wait_for { ready? }
server.setup :keys => [new_attributes[:private_key_path]]
server
end
def get(id)
if server = service.get_server_details(id).body['droplet']
new server
end
server = service.get_server_details(id).body['droplet']
new(server) if server
rescue Fog::Errors::NotFound
nil
end

View file

@ -15,9 +15,8 @@ module Fog
end
def get(uri)
if data = service.get_ssh_key(uri)
new(data.body)
end
data = service.get_ssh_key(uri).body['ssh_key']
new(data)
rescue Fog::Errors::NotFound
nil
end

View file

@ -6,9 +6,9 @@ module Fog
#
# FIXME: missing ssh keys support
#
def create_server( name,
size_id,
image_id,
def create_server( name,
size_id,
image_id,
region_id,
options = {} )
@ -20,9 +20,10 @@ module Fog
}
if options[:ssh_key_ids]
options[:ssh_key_ids] = options[:ssh_key_ids].join(",") if options[:ssh_key_ids].is_a? Array
query_hash[:ssh_key_ids] = options[:ssh_key_ids]
end
request(
:expects => [200],
:method => 'GET',
@ -35,9 +36,9 @@ module Fog
class Mock
def create_server( name,
size_id,
image_id,
def create_server( name,
size_id,
image_id,
region_id,
options = {} )
response = Excon::Response.new
@ -50,6 +51,7 @@ module Fog
"size_id" => size_id,
"image_id" => image_id,
"region_id" => region_id,
"ip_address" => "127.0.0.1",
"status" => 'active'
}

View file

@ -25,6 +25,9 @@ module Fog
"event_id" => Fog::Mock.random_numbers(1).to_i,
"status" => "OK"
}
server = self.data[:servers].reject! { |s| s['id'] == id }
response
end

View file

@ -1,6 +1,6 @@
module Fog
module Compute
class DigitalOcean
class DigitalOcean
class Real
def list_images(options = {})
@ -23,18 +23,18 @@ module Fog
"images" => [
# Sample image
{
"id" => 1601,
"name" => "CentOS 5.8 x64",
"id" => 1601,
"name" => "CentOS 5.8 x64",
"distribution" => "CentOS"
},
{
"id" => 1602,
"name" => "CentOS 5.8 x32",
"id" => 1602,
"name" => "CentOS 5.8 x32",
"distribution" => "CentOS"
},
{
"id" => 2676,
"name" => "Ubuntu 12.04 x64 Server",
"name" => "Ubuntu 12.04 x64",
"distribution" => "Ubuntu"
},

View file

@ -4,36 +4,43 @@ def service
Fog::Compute[:digitalocean]
end
def fog_test_server_attributes
image = service.images.find { |i| i.name == 'Ubuntu 12.04 x64' }
region = service.regions.find { |r| r.name == 'New York 1' }
flavor = service.flavors.find { |r| r.name == '512MB' }
{
:image_id => image.id,
:region_id => region.id,
:flavor_id => flavor.id
}
end
def fog_server_name
"fog-server-test"
end
# Create a long lived server for the tests
def fog_test_server
server = service.servers.find { |s| s.name == 'fog-test-server' }
server = service.servers.find { |s| s.name == fog_server_name }
unless server
image = service.images.find { |i| i.name == 'Ubuntu 12.04 x64 Server' }
region = service.regions.find { |r| r.name == 'New York 1' }
flavor = service.flavors.find { |r| r.name == '512MB' }
server = service.servers.create :name => 'fog-test-server',
:image_id => image.id,
:region_id => region.id,
:flavor_id => flavor.id
# Wait for the server to come up
begin
server.wait_for(120) { server.reload rescue nil; server.ready? }
rescue Fog::Errors::TimeoutError
# Server bootstrap took more than 120 secs!
end
server = service.servers.create({
:name => fog_server_name
}.merge(fog_test_server_attributes))
server.wait_for { ready? }
end
server
end
# Destroy the long lived server
def fog_test_server_destroy
server = service.servers.find { |s| s.name == 'fog-test-server' }
server = service.servers.find { |s| s.name == fog_server_name }
server.destroy if server
end
at_exit do
unless Fog.mocking? || Fog.credentials[:digitalocean_api_key].nil?
server = service.servers.find { |s| s.name == 'fog-test-server' }
server = service.servers.find { |s| s.name == fog_server_name }
if server
server.wait_for(120) do
reload rescue nil; ready?

View file

@ -2,14 +2,14 @@ Shindo.tests("Fog::Compute[:digitalocean] | flavor model", ['digitalocean', 'com
service = Fog::Compute[:digitalocean]
flavor = service.flavors.first
tests('The flavor model should') do
tests('have the action') do
test('reload') { flavor.respond_to? 'reload' }
end
tests('have attributes') do
model_attribute_hash = flavor.attributes
attributes = [
attributes = [
:id,
:name,
]

View file

@ -2,14 +2,14 @@ Shindo.tests("Fog::Compute[:digitalocean] | image model", ['digitalocean', 'comp
service = Fog::Compute[:digitalocean]
image = service.images.first
tests('The image model should') do
tests('have the action') do
test('reload') { image.respond_to? 'reload' }
end
tests('have attributes') do
model_attribute_hash = image.attributes
attributes = [
attributes = [
:id,
:name,
:distribution

View file

@ -2,14 +2,14 @@ Shindo.tests("Fog::Compute[:digitalocean] | region model", ['digitalocean', 'com
service = Fog::Compute[:digitalocean]
region = service.regions.first
tests('The region model should') do
tests('have the action') do
test('reload') { region.respond_to? 'reload' }
end
tests('have attributes') do
model_attribute_hash = region.attributes
attributes = [
attributes = [
:id,
:name,
]

View file

@ -16,6 +16,7 @@ Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'com
test(action) { server.respond_to? action }
end
end
tests('have attributes') do
model_attribute_hash = server.attributes
attributes = [
@ -23,10 +24,11 @@ Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'com
:name,
:state,
:backups_active,
:ip_address,
:public_ip_address,
:flavor_id,
:region_id,
:image_id
:image_id,
:ssh_keys=
]
tests("The server model should respond to") do
attributes.each do |attribute|
@ -34,12 +36,14 @@ Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'com
end
end
end
test('#reboot') do
pending if Fog.mocking?
server.reboot
server.wait_for { server.state == 'off' }
server.state == 'off'
end
test('#power_cycle') do
pending if Fog.mocking?
server.wait_for { server.ready? }
@ -47,16 +51,19 @@ Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'com
server.wait_for { server.state == 'off' }
server.state == 'off'
end
test('#stop') do
server.stop
server.wait_for { server.state == 'off' }
server.state == 'off'
end
test('#start') do
server.start
server.wait_for { ready? }
server.ready?
end
# DigitalOcean shutdown is unreliable
# so disable it in real mode for now
test('#shutdown') do
@ -67,6 +74,7 @@ Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'com
server.wait_for { server.state == 'off' }
server.state == 'off'
end
test('#update') do
begin
server.update
@ -74,11 +82,11 @@ Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'com
true
end
end
end
# restore server state
server.start
server.wait_for { ready? }
end

View file

@ -1,35 +1,21 @@
Shindo.tests('Fog::Compute[:digitalocean] | servers collection', ['digitalocean']) do
service = Fog::Compute[:digitalocean]
service = Fog::Compute[:digitalocean]
tests('The servers collection') do
servers = service.servers.all
server = fog_test_server
test('should NOT be empty') do
servers.reload
!servers.empty?
end
test('should be a kind of Fog::Compute::DigitalOcean::Servers') do
servers.kind_of? Fog::Compute::DigitalOcean::Servers
end
tests('should have Fog::Compute::DigitalOcean::Servers inside') do
servers.each do |s|
test { s.kind_of? Fog::Compute::DigitalOcean::Server }
end
end
tests('should be able to reload itself').succeeds { servers.reload }
tests('should be able to get a model') do
test('by instance id') do
servers.get(server.id).kind_of? Fog::Compute::DigitalOcean::Server
end
end
options = {
:name => "#{fog_server_name}-#{Time.now.to_i.to_s}"
}.merge fog_test_server_attributes
collection_tests(service.servers, options, true) do
@instance.wait_for { ready? }
end
tests("#bootstrap").succeeds do
pending if Fog.mocking?
@server = service.servers.bootstrap({
:public_key_path => File.join(File.dirname(__FILE__), '../../fixtures/id_rsa.pub'),
:private_key_path => File.join(File.dirname(__FILE__), '../../fixtures/id_rsa')
}.merge(options))
end
@server.destroy if @server
end

View file

@ -3,33 +3,37 @@ Shindo.tests("Fog::Compute[:digitalocean] | ssh_key model", ['digitalocean', 'co
service = Fog::Compute[:digitalocean]
tests('The ssh_key model should') do
test('#save') do
@key = service.ssh_keys.create :name => 'fookey',
:ssh_pub_key => 'fookey'
@key.is_a? Fog::Compute::DigitalOcean::SshKey
end
tests('have the action') do
test('reload') { @key.respond_to? 'reload' }
%w{
%w{
save
destroy
}.each do |action|
test(action) { @key.respond_to? action }
end
end
tests('have attributes') do
attributes = [
attributes = [
:id,
:name,
:ssh_pub_key
]
tests("The key model should respond to") do
attributes.each do |attribute|
test("#{attribute}") { @key.respond_to? attribute }
end
end
end
test('#destroy') do
@key.destroy
end

View file

@ -19,10 +19,14 @@ Shindo.tests('Fog::Compute[:digitalocean] | ssh_keys collection', ['digitalocean
tests('should be able to get a model') do
test('by instance id') do
service.ssh_keys.get(key.id).kind_of? Fog::Compute::DigitalOcean::SshKey
retrieved_key = service.ssh_keys.get(key.id)
test { retrieved_key.kind_of? Fog::Compute::DigitalOcean::SshKey }
test { retrieved_key.name == key.name }
end
end
key.destroy
end
end

View file

@ -5,7 +5,7 @@ Shindo.tests('Fog::Compute[:digitalocean] | create_server request', ['digitaloce
'name' => String,
'image_id' => Integer,
'size_id' => Integer,
'event_id' => Integer
'event_id' => Integer
}
service = Fog::Compute[:digitalocean]
@ -13,16 +13,13 @@ Shindo.tests('Fog::Compute[:digitalocean] | create_server request', ['digitaloce
tests('success') do
tests('#create_server').formats({'status' => 'OK', 'droplet' => @server_format}) do
image = service.images.find { |img| img.name == 'Ubuntu 12.04 x64 Server' }
image = service.images.find { |img| img.name == 'Ubuntu 12.04 x64' }
flavor = service.flavors.find { |f| f.name == '512MB' }
data = Fog::Compute[:digitalocean].create_server 'fog-test-server',
data = Fog::Compute[:digitalocean].create_server fog_server_name,
flavor.id,
image.id,
service.regions.first.id
data.body
end
end
end

View file

@ -7,9 +7,8 @@ Shindo.tests('Fog::Compute[:digitalocean] | destroy_server request', ['digitaloc
test('#destroy_server') do
service.destroy_server(server.id).body['status'] == 'OK'
service.servers.get(server.id) == nil
end
end
end

View file

@ -5,7 +5,7 @@ Shindo.tests('Fog::Compute[:digitalocean] | get_server_details request', ['digit
test('#get_server_details') do
server = fog_test_server
body = Fog::Compute[:digitalocean].get_server_details(server.id).body
body['droplet']['name'] == 'fog-test-server'
body['droplet']['name'] == fog_server_name
end
end

View file

@ -5,16 +5,19 @@ Shindo.tests('Fog::Compute[:digitalocean] | get_ssh_keys request', ['digitalocea
'name' => String,
'ssh_pub_key' => String,
}
service = Fog::Compute[:digitalocean]
tests('success') do
tests('#get_ssh_key') do
key = service.create_ssh_key 'fookey', 'ssh-dss FOO'
tests('format').data_matches_schema(@ssh_key_format) do
service.get_ssh_key(key.body['ssh_key']['id']).body['ssh_key']
end
service.destroy_ssh_key(key.body['ssh_key']['id'])
end
end

View file

@ -7,15 +7,18 @@ Shindo.tests('Fog::Compute[:digitalocean] | list_ssh_keys request', ['digitaloce
tests('success') do
key = service.create_ssh_key 'fookey', 'ssh-dss FOO'
tests('#list_ssh_keys') do
Fog::Compute[:digitalocean].create_ssh_key 'fookey', 'ssh-dss FOO'
Fog::Compute[:digitalocean].list_ssh_keys.body['ssh_keys'].each do |key|
service.list_ssh_keys.body['ssh_keys'].each do |key|
tests('format').data_matches_schema(@ssh_key_format) do
key
end
end
end
service.destroy_ssh_key(key.body['ssh_key']['id'])
end
end

View file

@ -21,8 +21,8 @@ def collection_tests(collection, params = {}, mocks_implemented = true)
pending if Fog.mocking? && !mocks_implemented
collection.all
end
if !Fog.mocking? || mocks_implemented
@identity = @instance.identity
@ -37,7 +37,7 @@ def collection_tests(collection, params = {}, mocks_implemented = true)
pending if Fog.mocking? && !mocks_implemented
[
'all?', 'any?', 'find', 'detect', 'collect', 'map',
'all?', 'any?', 'find', 'detect', 'collect', 'map',
'find_index', 'flat_map', 'collect_concat', 'group_by',
'none?', 'one?'
].each do |enum_method|
@ -74,7 +74,7 @@ def collection_tests(collection, params = {}, mocks_implemented = true)
@instance.destroy
end
end
tests('failure') do
if !Fog.mocking? || mocks_implemented

View file

@ -28,6 +28,8 @@ if Fog.mock?
:cloudstack_zone_id => 'c554c592-e09c-9df5-7688-4a32754a4305',
:clodo_api_key => 'clodo_api_key',
:clodo_username => 'clodo_username',
:digitalocean_api_key => 'digitalocean_api_key',
:digitalocean_client_id => 'digitalocean_client_id',
:dnsimple_email => 'dnsimple_email',
:dnsimple_password => 'dnsimple_password',
:dnsmadeeasy_api_key => 'dnsmadeeasy_api_key',