blimpy 0.3.8 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Rakefile +5 -1
- data/features/cli/start.feature +0 -22
- data/features/liveries.feature +9 -0
- data/features/openstack.feature +26 -0
- data/features/step_definitions/cli_steps.rb +2 -2
- data/lib/blimpy.rb +4 -0
- data/lib/blimpy/box.rb +58 -21
- data/lib/blimpy/boxes/aws.rb +9 -1
- data/lib/blimpy/boxes/openstack.rb +148 -2
- data/lib/blimpy/cli.rb +2 -2
- data/lib/blimpy/engine.rb +2 -1
- data/lib/blimpy/fleet.rb +10 -3
- data/lib/blimpy/keys.rb +3 -8
- data/lib/blimpy/version.rb +1 -1
- data/spec/blimpy/box_spec.rb +5 -3
- data/spec/blimpy/boxes/openstack_spec.rb +283 -0
- data/spec/blimpy/keys_spec.rb +3 -3
- metadata +14 -10
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -16,7 +16,11 @@ namespace :cucumber do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
Cucumber::Rake::Task.new('integration') do |t|
|
19
|
-
t.cucumber_opts = cucumber_opts + ' --tags @slow'
|
19
|
+
t.cucumber_opts = cucumber_opts + ' --tags @slow --tags ~@openstack'
|
20
|
+
end
|
21
|
+
|
22
|
+
Cucumber::Rake::Task.new('openstack') do |t|
|
23
|
+
t.cucumber_opts = cucumber_opts + ' --tags @openstack'
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
data/features/cli/start.feature
CHANGED
@@ -14,7 +14,6 @@ Feature: Start a VM or cluster of VMs in the cloud
|
|
14
14
|
"""
|
15
15
|
Blimpy.fleet do |f|
|
16
16
|
f.add(:aws) do |host|
|
17
|
-
host.group = 'Simple'
|
18
17
|
host.name = 'Cucumber Host'
|
19
18
|
end
|
20
19
|
end
|
@@ -46,7 +45,6 @@ Feature: Start a VM or cluster of VMs in the cloud
|
|
46
45
|
"""
|
47
46
|
Blimpy.fleet do |f|
|
48
47
|
f.add(:aws) do |host|
|
49
|
-
host.group = 'Simple'
|
50
48
|
host.name = 'Cucumber Host'
|
51
49
|
end
|
52
50
|
end
|
@@ -72,23 +70,3 @@ Feature: Start a VM or cluster of VMs in the cloud
|
|
72
70
|
When I run `blimpy start`
|
73
71
|
Then the exit status should be 0
|
74
72
|
|
75
|
-
@slow @destroy @openstack @wip
|
76
|
-
Scenario: start with an OpenStack Blimpfile
|
77
|
-
Given I have the Blimpfile:
|
78
|
-
"""
|
79
|
-
Blimpy.fleet do |f|
|
80
|
-
f.add(:openstack) do |host|
|
81
|
-
host.name = 'Cucumber Host'
|
82
|
-
end
|
83
|
-
end
|
84
|
-
"""
|
85
|
-
When I run `blimpy start`
|
86
|
-
Then the exit status should be 0
|
87
|
-
And the output should contain:
|
88
|
-
"""
|
89
|
-
Up, up and away!
|
90
|
-
"""
|
91
|
-
And the output should contain:
|
92
|
-
"""
|
93
|
-
online at:
|
94
|
-
"""
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Feature: Craft machines based on a livery
|
2
|
+
In order to bootstrap a machine that looks how I expect it to
|
3
|
+
As a Blimpy user
|
4
|
+
When I specify a specific livery then that livery should provision
|
5
|
+
the host the way I would expect it to.
|
6
|
+
|
7
|
+
|
8
|
+
@slow @destroy @wip
|
9
|
+
Scenario: freebsd_puppet
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Provision machines on an OpenStack cluster
|
2
|
+
In order to use a private cloud, powered by OpenStack
|
3
|
+
As a Blimpy user
|
4
|
+
I should be able to tspin up machines on OpenStack the same way I am able to
|
5
|
+
on AWS
|
6
|
+
|
7
|
+
|
8
|
+
@slow @destroy @openstack
|
9
|
+
Scenario: Start with a functional Blimpfile
|
10
|
+
Given I have the Blimpfile:
|
11
|
+
"""
|
12
|
+
Blimpy.fleet do |f|
|
13
|
+
f.add(:openstack) do |host|
|
14
|
+
host.name = 'Cucumber Host'
|
15
|
+
host.image_id = '5e624061-65cc-4e67-b6c5-8e7ac6e38ea7' # Maps to our intenral 'lucid-server' image
|
16
|
+
host.region = 'test' # This is the "test" tenant
|
17
|
+
host.flavor = 'm1.tiny'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
"""
|
21
|
+
When I run `blimpy start`
|
22
|
+
Then the exit status should be 0
|
23
|
+
And the output should contain:
|
24
|
+
"""
|
25
|
+
online at:
|
26
|
+
"""
|
@@ -21,8 +21,8 @@ Given /^I have a single VM running$/ do
|
|
21
21
|
@name = 'Cucumber host'
|
22
22
|
@server_id = '0xdeadbeef'
|
23
23
|
File.open(File.join(d, "#{@server_id}.blimp"), 'w') do |f|
|
24
|
-
f.write("name: #{@name}\n")
|
25
|
-
f.write("dns: foo.bar\n")
|
24
|
+
f.write(":name: #{@name}\n")
|
25
|
+
f.write(":dns: foo.bar\n")
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
data/lib/blimpy.rb
CHANGED
@@ -13,6 +13,8 @@ module Blimpy
|
|
13
13
|
fleet
|
14
14
|
end
|
15
15
|
|
16
|
+
class UnknownError < Exception
|
17
|
+
end
|
16
18
|
class InvalidBlimpFileError < Exception
|
17
19
|
end
|
18
20
|
class InvalidRegionError < Exception
|
@@ -23,4 +25,6 @@ module Blimpy
|
|
23
25
|
end
|
24
26
|
class InvalidShipException < Exception
|
25
27
|
end
|
28
|
+
class UnsupportedFeatureException < Exception
|
29
|
+
end
|
26
30
|
end
|
data/lib/blimpy/box.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
1
3
|
require 'blimpy/helpers/state'
|
2
4
|
require 'blimpy/livery'
|
3
5
|
require 'blimpy/keys'
|
@@ -10,13 +12,14 @@ module Blimpy
|
|
10
12
|
|
11
13
|
attr_reader :allowed_regions, :region
|
12
14
|
attr_accessor :image_id, :flavor, :group, :ports
|
15
|
+
attr_accessor :dns, :internal_dns
|
13
16
|
attr_accessor :name, :tags, :fleet_id, :username, :livery
|
14
17
|
|
15
18
|
|
16
19
|
def self.from_instance_id(an_id, data)
|
17
|
-
return if data[
|
20
|
+
return if data[:type].nil?
|
18
21
|
|
19
|
-
name = data[
|
22
|
+
name = data[:type].to_sym
|
20
23
|
return unless Blimpy::Boxes.const_defined? name
|
21
24
|
|
22
25
|
klass = Blimpy::Boxes.const_get(name)
|
@@ -25,7 +28,7 @@ module Blimpy
|
|
25
28
|
return if server.nil?
|
26
29
|
|
27
30
|
box = klass.new(server)
|
28
|
-
box.
|
31
|
+
box.with_data(an_id, data)
|
29
32
|
box
|
30
33
|
end
|
31
34
|
|
@@ -49,22 +52,27 @@ module Blimpy
|
|
49
52
|
end
|
50
53
|
|
51
54
|
def online!
|
52
|
-
|
53
|
-
f.write("dns: #{@server.dns_name}\n")
|
54
|
-
f.write("internal_dns: #{@server.private_dns_name}\n")
|
55
|
-
end
|
55
|
+
write_state_file
|
56
56
|
end
|
57
57
|
|
58
58
|
def validate!
|
59
59
|
raise NotImplementedError, '#validate! should be defined in a subclass of Blimpy::Box'
|
60
60
|
end
|
61
61
|
|
62
|
+
def prestart
|
63
|
+
end
|
64
|
+
|
62
65
|
def start
|
63
66
|
ensure_state_folder
|
67
|
+
prestart
|
64
68
|
@server = create_host
|
69
|
+
poststart
|
65
70
|
write_state_file
|
66
71
|
end
|
67
72
|
|
73
|
+
def poststart
|
74
|
+
end
|
75
|
+
|
68
76
|
def bootstrap
|
69
77
|
@exec_commands = false
|
70
78
|
unless livery.nil?
|
@@ -85,20 +93,42 @@ module Blimpy
|
|
85
93
|
end
|
86
94
|
end
|
87
95
|
|
96
|
+
def predestroy
|
97
|
+
end
|
98
|
+
|
88
99
|
def destroy
|
89
100
|
unless @server.nil?
|
101
|
+
predestroy
|
90
102
|
@server.destroy
|
103
|
+
postdestroy
|
91
104
|
File.unlink(state_file)
|
92
105
|
end
|
93
106
|
end
|
94
107
|
|
108
|
+
def postdestroy
|
109
|
+
end
|
110
|
+
|
111
|
+
def type
|
112
|
+
# We only really care about the class name as part of the Blimpy::Boxes
|
113
|
+
# module
|
114
|
+
self.class.to_s.split('::').last
|
115
|
+
end
|
116
|
+
|
117
|
+
def serializable_attributes
|
118
|
+
[:type, :name, :region, :dns, :internal_dns]
|
119
|
+
end
|
120
|
+
|
121
|
+
def immutable_attributes
|
122
|
+
[:type]
|
123
|
+
end
|
124
|
+
|
95
125
|
def write_state_file
|
126
|
+
data = {}
|
127
|
+
serializable_attributes.each do |attr|
|
128
|
+
data[attr] = self.send(attr)
|
129
|
+
end
|
96
130
|
File.open(state_file, 'w') do |f|
|
97
|
-
|
98
|
-
# module
|
99
|
-
f.write("type: #{self.class.to_s.split('::').last}\n")
|
100
|
-
f.write("name: #{@name}\n")
|
101
|
-
f.write("region: #{@region}\n")
|
131
|
+
f.write(data.to_yaml)
|
102
132
|
end
|
103
133
|
end
|
104
134
|
|
@@ -122,11 +152,13 @@ module Blimpy
|
|
122
152
|
|
123
153
|
|
124
154
|
def with_data(ship_id, data)
|
125
|
-
|
126
|
-
|
155
|
+
data.each do |key, value|
|
156
|
+
next if immutable_attributes.include? key.to_sym
|
157
|
+
self.send("#{key}=", value)
|
158
|
+
end
|
127
159
|
end
|
128
160
|
|
129
|
-
def
|
161
|
+
def dns
|
130
162
|
@dns ||= begin
|
131
163
|
if @server.nil?
|
132
164
|
'no name'
|
@@ -136,9 +168,14 @@ module Blimpy
|
|
136
168
|
end
|
137
169
|
end
|
138
170
|
|
139
|
-
def
|
140
|
-
|
141
|
-
|
171
|
+
def internal_dns
|
172
|
+
@internal_dns ||= begin
|
173
|
+
if @server.nil?
|
174
|
+
'no name'
|
175
|
+
else
|
176
|
+
@server.private_dns_name
|
177
|
+
end
|
178
|
+
end
|
142
179
|
end
|
143
180
|
|
144
181
|
def run_command(*args)
|
@@ -156,13 +193,13 @@ module Blimpy
|
|
156
193
|
args = ARGV[2 .. -1]
|
157
194
|
end
|
158
195
|
run_command('ssh', '-o', 'StrictHostKeyChecking=no',
|
159
|
-
'-l', username,
|
196
|
+
'-l', username, dns, *args)
|
160
197
|
end
|
161
198
|
|
162
199
|
def scp_file(filename, directory='')
|
163
200
|
filename = File.expand_path(filename)
|
164
201
|
run_command('scp', '-o', 'StrictHostKeyChecking=no',
|
165
|
-
filename, "#{username}@#{
|
202
|
+
filename, "#{username}@#{dns}:#{directory}", *ARGV[3..-1])
|
166
203
|
end
|
167
204
|
|
168
205
|
def bootstrap_livery
|
@@ -187,7 +224,7 @@ module Blimpy
|
|
187
224
|
'--exclude=.svn',
|
188
225
|
'--exclude=.blimpy.d',
|
189
226
|
'.',
|
190
|
-
"#{username}@#{
|
227
|
+
"#{username}@#{dns}:#{dir_name}/")
|
191
228
|
else
|
192
229
|
puts "Remote host has no rsync(1), falling back to copying a full tarball over"
|
193
230
|
tarball = Blimpy::Livery.tarball_directory(Dir.pwd)
|
data/lib/blimpy/boxes/aws.rb
CHANGED
@@ -42,10 +42,18 @@ module Blimpy::Boxes
|
|
42
42
|
|
43
43
|
private
|
44
44
|
|
45
|
+
def import_key
|
46
|
+
material = Blimpy::Keys.public_key
|
47
|
+
begin
|
48
|
+
fog.import_key_pair(Blimpy::Keys.key_name, material)
|
49
|
+
rescue Fog::Compute::AWS::Error => e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
45
53
|
def create_host
|
46
54
|
tags = @tags.merge({:Name => @name, :CreatedBy => 'Blimpy', :BlimpyFleetId => @fleet_id})
|
47
55
|
|
48
|
-
|
56
|
+
import_key
|
49
57
|
generated_group = Blimpy::SecurityGroups.ensure_group(fog, @ports + [22])
|
50
58
|
groups = [@group, generated_group].compact
|
51
59
|
fog.servers.create(:image_id => @image_id,
|
@@ -4,9 +4,155 @@ require 'blimpy/boxes'
|
|
4
4
|
module Blimpy::Boxes
|
5
5
|
class OpenStack < Blimpy::Box
|
6
6
|
def self.fog_server_for_instance(id, blimpdata)
|
7
|
-
region = blimpdata[
|
8
|
-
fog = Fog::Compute.new(:provider => 'OpenStack', :
|
7
|
+
region = blimpdata[:region]
|
8
|
+
fog = Fog::Compute.new(:provider => 'OpenStack', :openstack_tenant => region)
|
9
9
|
fog.servers.get(id)
|
10
10
|
end
|
11
|
+
|
12
|
+
class FloatingIp
|
13
|
+
attr_accessor :address, :id
|
14
|
+
|
15
|
+
def initialize(address, id)
|
16
|
+
@address = address
|
17
|
+
@id = id
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_yaml(*args)
|
21
|
+
{:address => address, :id => id}.to_yaml
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :key_name, :floating_ip
|
26
|
+
|
27
|
+
def initialize(server=nil)
|
28
|
+
super(server)
|
29
|
+
@username = 'ubuntu'
|
30
|
+
@flavor = 'm1.tiny'
|
31
|
+
@group = 'default'
|
32
|
+
@key_name = nil
|
33
|
+
@floating_ip = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def ports=(new_pors)
|
37
|
+
raise Blimpy::UnsupportedFeatureException, 'Opening arbitrary ports in OpenStack is currently not supported'
|
38
|
+
end
|
39
|
+
|
40
|
+
def wait_for_state(until_state, &block)
|
41
|
+
until @server.ready?
|
42
|
+
sleep 1
|
43
|
+
@server.reload
|
44
|
+
end
|
45
|
+
# OpenStack doesn't seem to like it if you try to associate the IP
|
46
|
+
# address too early, but will properly associate it after the machine is
|
47
|
+
# ready
|
48
|
+
associate_ip
|
49
|
+
end
|
50
|
+
|
51
|
+
def serializable_attributes
|
52
|
+
super + [:floating_ip]
|
53
|
+
end
|
54
|
+
|
55
|
+
def dns
|
56
|
+
if floating_ip.nil?
|
57
|
+
'unavailable'
|
58
|
+
else
|
59
|
+
floating_ip.address
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def internal_dns
|
64
|
+
'unavailable'
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate!
|
68
|
+
if @region.nil?
|
69
|
+
raise Blimpy::BoxValidationError, "Cannot spin up machine without a set region"
|
70
|
+
end
|
71
|
+
|
72
|
+
if flavor_id(@flavor).nil?
|
73
|
+
raise Blimpy::BoxValidationError, "'#{@flavor}' is not a valid OpenStack tenant name"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def fog
|
78
|
+
@fog ||= begin
|
79
|
+
Fog::Compute.new(:provider => 'openstack', :openstack_tenant => @region)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def flavors
|
84
|
+
@flavors ||= fog.flavors
|
85
|
+
end
|
86
|
+
|
87
|
+
def flavor_id(name)
|
88
|
+
flavors.each do |flavor|
|
89
|
+
return flavor.id if flavor.name == name
|
90
|
+
end
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def prestart
|
95
|
+
allocate_ip
|
96
|
+
end
|
97
|
+
|
98
|
+
def allocate_ip
|
99
|
+
response = fog.allocate_address
|
100
|
+
unless response.status == 200
|
101
|
+
raise Blimpy::UnknownError, "Blimpy was unable to allocate a floating IP address; #{response.inspect}"
|
102
|
+
end
|
103
|
+
|
104
|
+
details = response.body['floating_ip']
|
105
|
+
@floating_ip = FloatingIp.new(details['ip'], details['id'])
|
106
|
+
end
|
107
|
+
|
108
|
+
def associate_ip
|
109
|
+
if floating_ip.nil?
|
110
|
+
raise Blimpy::UnknownError, "Blimpy cannot associate a floating IP until it's been allocated properly!"
|
111
|
+
end
|
112
|
+
response = fog.associate_address(@server.id, floating_ip.address)
|
113
|
+
|
114
|
+
unless response.status == 202
|
115
|
+
raise Blimpy::UnknownError, "Blimpy failed to associate the IP somehow #{response.inspect}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def predestroy
|
120
|
+
disassociate_ip unless floating_ip.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
def postdestroy
|
124
|
+
deallocate_ip unless floating_ip.nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
def disassociate_ip
|
128
|
+
fog.disassociate_address(@server.id, floating_ip.address)
|
129
|
+
end
|
130
|
+
|
131
|
+
def deallocate_ip
|
132
|
+
fog.release_address(floating_ip.id)
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def import_key
|
138
|
+
material = Blimpy::Keys.public_key
|
139
|
+
begin
|
140
|
+
fog.create_key_pair(Blimpy::Keys.key_name, material)
|
141
|
+
rescue Excon::Errors::Conflict => e
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def create_host
|
146
|
+
tags = @tags.merge({:Name => @name, :CreatedBy => 'Blimpy', :BlimpyFleetId => @fleet_id})
|
147
|
+
|
148
|
+
groups = [@group]
|
149
|
+
import_key
|
150
|
+
fog.servers.create(:image_ref => image_id,
|
151
|
+
:flavor_ref => flavor_id(@flavor),
|
152
|
+
:key_name => Blimpy::Keys.key_name,
|
153
|
+
:groups => groups,
|
154
|
+
:name => @name,
|
155
|
+
:tags => tags)
|
156
|
+
end
|
11
157
|
end
|
12
158
|
end
|
data/lib/blimpy/cli.rb
CHANGED
@@ -28,7 +28,7 @@ module Blimpy
|
|
28
28
|
ship_id = nil
|
29
29
|
data = nil
|
30
30
|
engine.fleet.members.each do |instance_id, instance_data|
|
31
|
-
next unless instance_data[
|
31
|
+
next unless instance_data[:name] == name
|
32
32
|
ship_id = instance_id
|
33
33
|
data = instance_data
|
34
34
|
break
|
@@ -88,7 +88,7 @@ module Blimpy
|
|
88
88
|
blimps.each do |blimp, data|
|
89
89
|
instance_id = File.basename(blimp)
|
90
90
|
instance_id = instance_id.split('.blimp').first
|
91
|
-
puts "#{data[
|
91
|
+
puts "#{data[:name]} (#{instance_id}) is: online at #{data[:dns]}"
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
data/lib/blimpy/engine.rb
CHANGED
data/lib/blimpy/fleet.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'blimpy/helpers/state'
|
2
2
|
require 'blimpy/boxes/aws'
|
3
|
+
require 'blimpy/boxes/openstack'
|
3
4
|
|
4
5
|
module Blimpy
|
5
6
|
class Fleet
|
@@ -14,7 +15,7 @@ module Blimpy
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def valid_types
|
17
|
-
[:aws]
|
18
|
+
[:aws, :openstack]
|
18
19
|
end
|
19
20
|
|
20
21
|
def add(box_type, &block)
|
@@ -25,7 +26,13 @@ module Blimpy
|
|
25
26
|
return false
|
26
27
|
end
|
27
28
|
|
28
|
-
box =
|
29
|
+
box = nil
|
30
|
+
if box_type == :aws
|
31
|
+
box = Blimpy::Boxes::AWS.new
|
32
|
+
end
|
33
|
+
if box_type == :openstack
|
34
|
+
box = Blimpy::Boxes::OpenStack.new
|
35
|
+
end
|
29
36
|
|
30
37
|
if box.nil?
|
31
38
|
return false
|
@@ -115,7 +122,7 @@ module Blimpy
|
|
115
122
|
host.wait_for_state('running') { }
|
116
123
|
@airborn = true
|
117
124
|
print "\n"
|
118
|
-
puts ">> #{host.name} online at: #{host.
|
125
|
+
puts ">> #{host.name} online at: #{host.dns}"
|
119
126
|
host.online!
|
120
127
|
host.bootstrap
|
121
128
|
puts
|
data/lib/blimpy/keys.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
require 'fog'
|
2
1
|
require 'socket'
|
3
2
|
|
4
3
|
module Blimpy
|
5
4
|
module Keys
|
6
|
-
def self.
|
5
|
+
def self.public_key
|
7
6
|
filename = File.expand_path('~/.ssh/id_rsa.pub')
|
8
7
|
unless File.exists? filename
|
9
8
|
filename = File.expand_path('~/.ssh/id_dsa.pub')
|
@@ -12,15 +11,11 @@ module Blimpy
|
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
15
|
-
|
16
|
-
begin
|
17
|
-
fog.import_key_pair(key_name, material)
|
18
|
-
rescue Fog::Compute::AWS::Error => e
|
19
|
-
end
|
14
|
+
File.open(filename, 'r').read
|
20
15
|
end
|
21
16
|
|
22
17
|
def self.key_name
|
23
|
-
"Blimpy-#{ENV['USER']}
|
18
|
+
"Blimpy-#{ENV['USER']}-#{Socket.gethostname}"
|
24
19
|
end
|
25
20
|
end
|
26
21
|
end
|
data/lib/blimpy/version.rb
CHANGED
data/spec/blimpy/box_spec.rb
CHANGED
@@ -61,7 +61,7 @@ describe Blimpy::Box do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'should set the dns_name' do
|
64
|
-
subject.
|
64
|
+
subject.dns.should == data['dns']
|
65
65
|
end
|
66
66
|
|
67
67
|
it 'should set the region' do
|
@@ -75,6 +75,8 @@ describe Blimpy::Box do
|
|
75
75
|
let(:server) do
|
76
76
|
server = double('Fog::Compute::AWS::Server')
|
77
77
|
server.stub(:id).and_return(server_id)
|
78
|
+
server.stub(:dns_name).and_return('test')
|
79
|
+
server.stub(:private_dns_name).and_return('test')
|
78
80
|
server
|
79
81
|
end
|
80
82
|
|
@@ -126,7 +128,7 @@ describe Blimpy::Box do
|
|
126
128
|
end
|
127
129
|
|
128
130
|
it 'should fail if the "type" is not a defined Box class' do
|
129
|
-
result = Blimpy::Box.from_instance_id('someid', {
|
131
|
+
result = Blimpy::Box.from_instance_id('someid', {:type => 'MAGIC'})
|
130
132
|
result.should be_nil
|
131
133
|
end
|
132
134
|
|
@@ -137,7 +139,7 @@ describe Blimpy::Box do
|
|
137
139
|
end
|
138
140
|
|
139
141
|
it 'should create a new AWS Box instance' do
|
140
|
-
result = Blimpy::Box.from_instance_id('someid', {
|
142
|
+
result = Blimpy::Box.from_instance_id('someid', {:type => 'AWS'})
|
141
143
|
result.should be_instance_of Blimpy::Boxes::AWS
|
142
144
|
end
|
143
145
|
end
|
@@ -1,6 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'blimpy/boxes/openstack'
|
3
3
|
|
4
|
+
shared_context 'valid floating_ip' do
|
5
|
+
let(:floating) do
|
6
|
+
floating = double('FloatingIp')
|
7
|
+
floating.stub(:address).and_return('127.0.0.1')
|
8
|
+
floating.stub(:id).and_return(7)
|
9
|
+
floating
|
10
|
+
end
|
11
|
+
before :each do
|
12
|
+
subject.stub(:floating_ip).and_return(floating)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
4
16
|
describe Blimpy::Boxes::OpenStack do
|
5
17
|
describe '#image_id' do
|
6
18
|
it 'should be nil by default' do
|
@@ -8,6 +20,24 @@ describe Blimpy::Boxes::OpenStack do
|
|
8
20
|
end
|
9
21
|
end
|
10
22
|
|
23
|
+
describe '#username' do
|
24
|
+
it 'should be "ubuntu" by default' do
|
25
|
+
subject.username.should == 'ubuntu'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#flavor' do
|
30
|
+
it 'should be "m1.tiny" by default' do
|
31
|
+
subject.flavor.should == 'm1.tiny'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#group' do
|
36
|
+
it 'should be "default" by default' do
|
37
|
+
subject.group.should == 'default'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
11
41
|
describe '#allowed_regions' do
|
12
42
|
it 'should be nil by default' do
|
13
43
|
subject.allowed_regions.should be_nil
|
@@ -21,4 +51,257 @@ describe Blimpy::Boxes::OpenStack do
|
|
21
51
|
subject.region.should == :elbonia
|
22
52
|
end
|
23
53
|
end
|
54
|
+
|
55
|
+
describe '#ports=' do
|
56
|
+
it 'should be disabled currently' do
|
57
|
+
expect {
|
58
|
+
subject.ports = [22, 8080]
|
59
|
+
}.to raise_error(Blimpy::UnsupportedFeatureException)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#validate!' do
|
64
|
+
it 'should raise a validation error if there isn\'t a region' do
|
65
|
+
subject.region = nil
|
66
|
+
expect {
|
67
|
+
subject.validate!
|
68
|
+
}.to raise_error(Blimpy::BoxValidationError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with mocked flavors' do
|
73
|
+
let(:fog) { double('Fog') }
|
74
|
+
let(:flavors) do
|
75
|
+
flavor = double('Fog::Compute::OpenStack::Flavor')
|
76
|
+
flavor.stub(:id).and_return('1')
|
77
|
+
flavor.stub(:name).and_return('m1.tiny')
|
78
|
+
[flavor]
|
79
|
+
end
|
80
|
+
|
81
|
+
before :each do
|
82
|
+
fog.should_receive(:flavors).and_return(flavors)
|
83
|
+
subject.should_receive(:fog).and_return(fog)
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#flavors' do
|
87
|
+
it 'should pull the list of flavors from Fog' do
|
88
|
+
subject.flavors.should == flavors
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#flavor_id' do
|
93
|
+
it 'should filter out the right flavor' do
|
94
|
+
subject.flavor_id('m1.tiny').should == '1'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should return nil if the flavor doesn\'t exist' do
|
98
|
+
subject.flavor_id('invalid').should be_nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#validate!' do
|
103
|
+
it 'should raise a validation error with an invalid flavor' do
|
104
|
+
subject.flavor = 'invalid'
|
105
|
+
subject.region = 'test'
|
106
|
+
expect {
|
107
|
+
subject.validate!
|
108
|
+
}.to raise_error(Blimpy::BoxValidationError)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#prestart' do
|
116
|
+
it 'should allocate an IP' do
|
117
|
+
subject.should_receive(:allocate_ip)
|
118
|
+
subject.prestart
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#allocate_ip' do
|
123
|
+
let(:fog) { double('Fog') }
|
124
|
+
|
125
|
+
before :each do
|
126
|
+
subject.stub(:fog).and_return(fog)
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'with a bad response' do
|
130
|
+
let(:response) do
|
131
|
+
response = double('Excon::Response')
|
132
|
+
response.stub(:status).and_return(500)
|
133
|
+
response
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should raise an error if we cannot allocate the IP' do
|
137
|
+
fog.should_receive(:allocate_address).and_return(response)
|
138
|
+
expect {
|
139
|
+
subject.allocate_ip
|
140
|
+
}.to raise_error(Blimpy::UnknownError)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'with a good response' do
|
145
|
+
let(:response) do
|
146
|
+
response = double('Excon::Response')
|
147
|
+
response.stub(:status).and_return(200)
|
148
|
+
response.stub(:body).and_return({"floating_ip"=>
|
149
|
+
{"instance_id"=>nil,
|
150
|
+
"ip"=>"10.38.12.109",
|
151
|
+
"fixed_ip"=>nil,
|
152
|
+
"id"=>109,
|
153
|
+
"pool"=>"nova"}})
|
154
|
+
response
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should allocate and store the floating IP info' do
|
158
|
+
fog.should_receive(:allocate_address).and_return(response)
|
159
|
+
|
160
|
+
subject.allocate_ip
|
161
|
+
subject.floating_ip.address.should == '10.38.12.109'
|
162
|
+
subject.floating_ip.id.should == 109
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#associate_ip' do
|
168
|
+
let(:fog) { double('Fog') }
|
169
|
+
include_context 'valid floating_ip'
|
170
|
+
let(:server) do
|
171
|
+
server = double('Fog::Compute::Server')
|
172
|
+
server.stub(:id).and_return(server_id)
|
173
|
+
server
|
174
|
+
end
|
175
|
+
let(:server_id) { 'fake-id' }
|
176
|
+
|
177
|
+
subject { described_class.new(server) }
|
178
|
+
|
179
|
+
before :each do
|
180
|
+
subject.stub(:fog).and_return(fog)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should raise an exception if a floating IP hasn\'t been created' do
|
184
|
+
subject.stub(:floating_ip).and_return(nil)
|
185
|
+
expect {
|
186
|
+
subject.associate_ip
|
187
|
+
}.to raise_error(Blimpy::UnknownError)
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'with a good response' do
|
191
|
+
let(:response) do
|
192
|
+
response = double('Excon::Response')
|
193
|
+
response.stub(:status).and_return(202)
|
194
|
+
response
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should associate the right IP to the right instance ID' do
|
198
|
+
fog.should_receive(:associate_address).with(server_id, floating.address).and_return(response)
|
199
|
+
subject.associate_ip
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'with a bad response' do
|
204
|
+
let(:response) do
|
205
|
+
response = double('Excon::Response')
|
206
|
+
response.stub(:status).and_return(500)
|
207
|
+
response
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should raise an error' do
|
211
|
+
subject.stub(:floating_ip).and_return(floating)
|
212
|
+
fog.should_receive(:associate_address).with(server_id, floating.address).and_return(response)
|
213
|
+
expect {
|
214
|
+
subject.associate_ip
|
215
|
+
}.to raise_error(Blimpy::UnknownError)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe '#predestroy' do
|
221
|
+
context 'if the server has a floating IP' do
|
222
|
+
include_context 'valid floating_ip'
|
223
|
+
|
224
|
+
it 'should disasscoaite it' do
|
225
|
+
subject.should_receive(:disassociate_ip)
|
226
|
+
subject.predestroy
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'if the server has no floating IP' do
|
231
|
+
it 'should not try to disassociate it' do
|
232
|
+
subject.should_receive(:disassociate_ip).never
|
233
|
+
subject.predestroy
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#postdestroy' do
|
239
|
+
context 'if the server has a floating IP' do
|
240
|
+
include_context 'valid floating_ip'
|
241
|
+
|
242
|
+
it 'should deallocate the IP' do
|
243
|
+
subject.should_receive(:deallocate_ip)
|
244
|
+
subject.postdestroy
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'if the server has no floating IP' do
|
249
|
+
it 'should not try to deallocate the IP' do
|
250
|
+
subject.should_receive(:deallocate_ip).never
|
251
|
+
subject.postdestroy
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe '#disassociate_ip' do
|
257
|
+
let(:fog) { double('Fog') }
|
258
|
+
include_context 'valid floating_ip'
|
259
|
+
let(:server) do
|
260
|
+
server = double('Fog::Compute::Server')
|
261
|
+
server.stub(:id).and_return(server_id)
|
262
|
+
server
|
263
|
+
end
|
264
|
+
let(:server_id) { 'fake-id' }
|
265
|
+
|
266
|
+
subject { described_class.new(server) }
|
267
|
+
|
268
|
+
before :each do
|
269
|
+
subject.stub(:fog).and_return(fog)
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should disassociate the right IP to the right instance ID' do
|
273
|
+
fog.should_receive(:disassociate_address).with(server_id, floating.address)
|
274
|
+
subject.disassociate_ip
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe '#deallocate_ip' do
|
279
|
+
let(:fog) { double('Fog') }
|
280
|
+
include_context 'valid floating_ip'
|
281
|
+
|
282
|
+
before :each do
|
283
|
+
subject.stub(:fog).and_return(fog)
|
284
|
+
end
|
285
|
+
|
286
|
+
context 'with a good response' do
|
287
|
+
let(:response) do
|
288
|
+
response = double('Excon::Response')
|
289
|
+
response.stub(:status).and_return(202)
|
290
|
+
response
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'should release the right IP by floating_ip ID' do
|
294
|
+
fog.should_receive(:release_address).with(floating.id).and_return(response)
|
295
|
+
subject.deallocate_ip
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe Blimpy::Boxes::OpenStack::FloatingIp do
|
302
|
+
subject do
|
303
|
+
described_class.new('127.0.0.1', 1)
|
304
|
+
end
|
305
|
+
|
306
|
+
it { should respond_to(:to_yaml) }
|
24
307
|
end
|
data/spec/blimpy/keys_spec.rb
CHANGED
@@ -2,12 +2,12 @@ require 'spec_helper'
|
|
2
2
|
require 'blimpy/keys'
|
3
3
|
|
4
4
|
describe Blimpy::Keys do
|
5
|
-
describe '#
|
5
|
+
describe '#public_key' do
|
6
6
|
context 'with no SSH keys' do
|
7
7
|
it 'should raise a SSHKeyNotFoundError' do
|
8
8
|
File.stub(:exists?).and_return(false)
|
9
9
|
expect {
|
10
|
-
subject.
|
10
|
+
subject.public_key
|
11
11
|
}.to raise_error(Blimpy::SSHKeyNotFoundError)
|
12
12
|
end
|
13
13
|
end
|
@@ -21,7 +21,7 @@ describe Blimpy::Keys do
|
|
21
21
|
it do
|
22
22
|
hostname = 'rspec'
|
23
23
|
Socket.should_receive(:gethostname).and_return(hostname)
|
24
|
-
subject.key_name.should == "Blimpy-tester
|
24
|
+
subject.key_name.should == "Blimpy-tester-#{hostname}"
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blimpy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-20 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
16
|
-
requirement: &
|
16
|
+
requirement: &23292620 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *23292620
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: thor
|
27
|
-
requirement: &
|
27
|
+
requirement: &23292160 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *23292160
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: minitar
|
38
|
-
requirement: &
|
38
|
+
requirement: &23291720 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *23291720
|
47
47
|
description: Blimpy is a tool for managing a fleet of machines in the CLOUD!
|
48
48
|
email:
|
49
49
|
- tyler@monkeypox.org
|
@@ -65,6 +65,8 @@ files:
|
|
65
65
|
- features/cli/ssh.feature
|
66
66
|
- features/cli/start.feature
|
67
67
|
- features/cli/status.feature
|
68
|
+
- features/liveries.feature
|
69
|
+
- features/openstack.feature
|
68
70
|
- features/step_definitions/cli_steps.rb
|
69
71
|
- features/support/env.rb
|
70
72
|
- features/support/hooks.rb
|
@@ -108,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
110
|
version: '0'
|
109
111
|
segments:
|
110
112
|
- 0
|
111
|
-
hash: -
|
113
|
+
hash: -3073527661709686604
|
112
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
115
|
none: false
|
114
116
|
requirements:
|
@@ -117,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
119
|
version: '0'
|
118
120
|
segments:
|
119
121
|
- 0
|
120
|
-
hash: -
|
122
|
+
hash: -3073527661709686604
|
121
123
|
requirements: []
|
122
124
|
rubyforge_project:
|
123
125
|
rubygems_version: 1.8.10
|
@@ -131,6 +133,8 @@ test_files:
|
|
131
133
|
- features/cli/ssh.feature
|
132
134
|
- features/cli/start.feature
|
133
135
|
- features/cli/status.feature
|
136
|
+
- features/liveries.feature
|
137
|
+
- features/openstack.feature
|
134
138
|
- features/step_definitions/cli_steps.rb
|
135
139
|
- features/support/env.rb
|
136
140
|
- features/support/hooks.rb
|