blimpy 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -10,4 +10,9 @@ group :development do
10
10
  gem 'aruba'
11
11
  gem 'tempdir'
12
12
  gem 'pry'
13
+ if RUBY_VERSION > '1.9'
14
+ gem 'ruby-debug19', :require => 'ruby-debug'
15
+ else
16
+ gem 'ruby-debug'
17
+ end
13
18
  end
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
 
@@ -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['type'].nil?
20
+ return if data[:type].nil?
18
21
 
19
- name = data['type'].upcase.to_sym
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.name = data['name']
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
- File.open(state_file, 'a') do |f|
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
- # We only really care about the class name as part of the Blimpy::Boxes
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
- @dns = data['dns']
126
- @region = data['region']
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 dns_name
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 internal_dns_name
140
- return @server.private_dns_name unless @server.nil?
141
- 'no name'
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, dns_name, *args)
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}@#{dns_name}:#{directory}", *ARGV[3..-1])
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}@#{dns_name}:#{dir_name}/")
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)
@@ -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
- Blimpy::Keys.import_key(fog)
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['region']
8
- fog = Fog::Compute.new(:provider => 'OpenStack', :region => region)
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['name'] == name
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['name']} (#{instance_id}) is: online at #{data['dns']}"
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
@@ -1,6 +1,7 @@
1
1
 
2
2
  require 'rubygems'
3
- require 'fog'
3
+ require 'fog/core'
4
+ require 'fog/compute'
4
5
 
5
6
  module Blimpy
6
7
  class Engine
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 = Blimpy::Boxes::AWS.new
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.dns_name}"
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.import_key(fog)
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
- material = File.open(filename, 'r').read
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']}@#{Socket.gethostname}"
18
+ "Blimpy-#{ENV['USER']}-#{Socket.gethostname}"
24
19
  end
25
20
  end
26
21
  end
@@ -1,3 +1,3 @@
1
1
  module Blimpy
2
- VERSION = "0.3.8"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -61,7 +61,7 @@ describe Blimpy::Box do
61
61
  end
62
62
 
63
63
  it 'should set the dns_name' do
64
- subject.dns_name.should == data['dns']
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', {'type' => 'MAGIC'})
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', {'type' => 'AWS'})
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
@@ -2,12 +2,12 @@ require 'spec_helper'
2
2
  require 'blimpy/keys'
3
3
 
4
4
  describe Blimpy::Keys do
5
- describe '#import_key' do
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.import_key(nil)
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@#{hostname}"
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.3.8
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-09 00:00:00.000000000Z
12
+ date: 2012-07-20 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
16
- requirement: &13551560 !ruby/object:Gem::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: *13551560
24
+ version_requirements: *23292620
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thor
27
- requirement: &13550720 !ruby/object:Gem::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: *13550720
35
+ version_requirements: *23292160
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: minitar
38
- requirement: &13549340 !ruby/object:Gem::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: *13549340
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: -3611461695599472567
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: -3611461695599472567
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