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 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