Skip to content

Commit

Permalink
Merge pull request #216 from f2brossi/issue209
Browse files Browse the repository at this point in the history
Create bootable volume and startup
  • Loading branch information
ggiamarchi committed Apr 1, 2015
2 parents d26acad + 9d49f2d commit 5d742e4
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 25 deletions.
11 changes: 10 additions & 1 deletion source/lib/vagrant-openstack-provider/client/nova.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ def create_server(env, options)
server = {}.tap do |s|
s['name'] = options[:name]
if options[:image_ref].nil?
s['block_device_mapping'] = [{ volume_id: options[:volume_boot][:id], device_name: options[:volume_boot][:device] }]
s['block_device_mapping'] = [{ volume_id: options[:volume_boot][:id],
device_name: options[:volume_boot][:device] }] if options[:volume_boot].key?(:id)
s['block_device_mapping_v2'] = [{ boot_index: '0',
volume_size: options[:volume_boot][:size],
uuid: options[:volume_boot][:image],
device_name: options[:volume_boot][:device],
source_type: 'image',
destination_type: 'volume',
delete_on_termination: options[:volume_boot][:delete_on_destroy] }]\
if options[:volume_boot].key?(:image)
else
s['imageRef'] = options[:image_ref]
end
Expand Down
56 changes: 43 additions & 13 deletions source/lib/vagrant-openstack-provider/config_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@ def resolve_flavor(env)

def resolve_image(env)
@logger.info 'Resolving image'
config = env[:machine].provider_config
return nil if config.image.nil?
nova = env[:openstack_client].nova
env[:ui].info(I18n.t('vagrant_openstack.finding_image'))
images = nova.get_all_images(env)
@logger.info "Finding image matching name '#{config.image}'"
image = find_matching(images, config.image)
fail Errors::NoMatchingImage unless image
image
resolve_image_internal(env, env[:machine].provider_config.image)
end

def resolve_volume_boot_image(env)
@logger.info 'Resolving image to create a volume from'
resolve_image_internal(env, env[:machine].provider_config.volume_boot[:image])
end

def resolve_floating_ip(env)
Expand Down Expand Up @@ -76,7 +73,6 @@ def resolve_networks(env)
end

def resolve_volume_boot(env)
@logger.info 'Resolving image'
config = env[:machine].provider_config
return nil if config.volume_boot.nil?
return resolve_volume_without_volume_service(env, config.volume_boot, 'vda') unless env[:openstack_client].session.endpoints.key? :volume
Expand All @@ -87,9 +83,18 @@ def resolve_volume_boot(env)
@logger.debug(volume_list)

volume = resolve_volume(config.volume_boot, volume_list, volume_ids)
device = volume[:device].nil? ? 'vda' : volume[:device]

{ id: volume[:id], device: device }
device = (volume[:device].nil?) ? 'vda' : volume[:device]
size = (volume[:size].nil?) ? nil : volume[:size]
delete_on_destroy = (volume[:delete_on_destroy].nil?) ? nil : volume[:delete_on_destroy]

image = resolve_volume_boot_image(env) unless volume[:image].nil?
image_id = (image.nil?) ? nil : image.id
if image.nil?
return { id: volume[:id], device: device }
else
{ image: image_id, device: device, size: size, delete_on_destroy: delete_on_destroy }
end
end

def resolve_volumes(env)
Expand Down Expand Up @@ -135,6 +140,17 @@ def resolve_security_groups(env)

private

def resolve_image_internal(env, image_name)
return nil if image_name.nil?

nova = env[:openstack_client].nova
env[:ui].info(I18n.t('vagrant_openstack.finding_image'))
images = nova.get_all_images(env)
image = find_matching(images, image_name)
fail Errors::NoMatchingImage unless image
image
end

def search_free_ip(config, nova, env)
@logger.debug 'Retrieving all allocated floating ips on tenant'
all_floating_ips = nova.get_all_floating_ips(env)
Expand Down Expand Up @@ -258,23 +274,37 @@ def resolve_volume_from_string(volume, volume_list)
def resolve_volume_from_hash(volume, volume_list, volume_ids)
device = nil
device = volume[:device] if volume.key?(:device)
delete_on_destroy = (volume[:delete_on_destroy].nil?) ? 'true' : volume[:delete_on_destroy]

volume_id = nil
if volume.key?(:id)
fail Errors::ConflictVolumeNameId, volume: volume if volume.key?(:name)
check_boot_volume_conflict(volume)
volume_id = volume[:id]
fail Errors::UnresolvedVolumeId, id: volume_id unless volume_ids.include? volume_id
elsif volume.key?(:name)
volume_list.each do |v|
next unless v.name.eql? volume[:name]
fail Errors::MultipleVolumeName, name: volume[:name] unless volume_id.nil?
check_boot_volume_conflict(volume)
volume_id = v.id
end
fail Errors::UnresolvedVolumeName, name: volume[:name] unless volume_ids.include? volume_id
elsif volume.key?(:image)
fail Errors::UnresolvedVolume, volume: volume unless volume.key?(:size)
fail Errors::ConflictBootVolume, volume: volume if volume.key?(:id)
fail Errors::ConflictBootVolume, volume: volume if volume.key?(:name)
return { image: volume[:image], device: device, size: volume[:size], delete_on_destroy: delete_on_destroy }
else
fail Errors::ConflictVolumeNameId, volume: volume
fail Errors::ConflictBootVolume, volume: volume
end
{ id: volume_id, device: device }
end

def check_boot_volume_conflict(volume)
fail Errors::ConflictBootVolume, volume: volume if volume.key?(:image) || volume.key?(:size) || volume.key?(:delete_on_destroy)
end

# This method finds any matching _thing_ from a list of names
# in a collection of _things_. The first to match is the returned
# one. Names in list can be a regexp, a partial match is chosen
Expand Down
4 changes: 4 additions & 0 deletions source/lib/vagrant-openstack-provider/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class NoMatchingImage < VagrantOpenstackError
error_key(:no_matching_image)
end

class ConflictBootVolume < VagrantOpenstackError
error_key(:conflict_boot_volume)
end

class SyncMethodError < VagrantOpenstackError
error_key(:sync_method_error)
end
Expand Down
3 changes: 3 additions & 0 deletions source/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ en:
no_matching_image: |-
No matching image was found! Please check your image setting to
make sure you have a valid image chosen.
conflict_boot_volume: |-
When booting from an existing volume it is not authorized to specify in your Vagrantfile either 'image' or 'size' or 'delete_on_destroy'.
When booting from a newly creating volume it is not authorized to specify in your Vagrantfile either 'id' or 'name'.
sync_method_error: |-
Value '%{sync_method_value}' is not allowed for 'sync_method' configuration parameter. Valid values are 'rsync' and 'none'
rsync_error: |-
Expand Down
48 changes: 39 additions & 9 deletions source/spec/vagrant-openstack-provider/client/nova_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,21 +212,51 @@
end
end

context 'with volume_boot' do
it 'returns new instance id' do
context 'with volume_boot creating volume' do
it 'create bootable volume and returns new instance id' do
stub_request(:post, 'http://nova/a1b2c3/servers')
.with(
body: '{"server":{"name":"inst","block_device_mapping":[{"volume_id":"vol","device_name":"vda"}],"flavorRef":"flav","key_name":"key"}}',
body: '{"server":{"name":"inst","block_device_mapping_v2":[{"boot_index":"0","volume_size":"10","uuid":"image_id",'\
'"device_name":"vda","source_type":"image","destination_type":"volume","delete_on_termination":"false"}],'\
'"flavorRef":"flav","key_name":"key"}}',
headers:
{
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Auth-Token' => '123456'
})
{
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Auth-Token' => '123456'
})
.to_return(status: 202, body: '{ "server": { "id": "o1o2o3" } }')

instance_id = @nova_client.create_server(env, name: 'inst', volume_boot: { id: 'vol', device: 'vda' }, flavor_ref: 'flav', keypair: 'key')
instance_id = @nova_client.create_server(env,
name: 'inst',
image_ref: nil,
volume_boot: { image: 'image_id', device: 'vda', size: '10',
delete_on_destroy: 'false' },
flavor_ref: 'flav',
keypair: 'key')
expect(instance_id).to eq('o1o2o3')
end
end

context 'with volume_boot id' do
it 'returns new instance id' do
stub_request(:post, 'http://nova/a1b2c3/servers')
.with(
body: '{"server":{"name":"inst","block_device_mapping":[{"volume_id":"vol","device_name":"vda"}],'\
'"flavorRef":"flav","key_name":"key"}}',
headers:
{
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Auth-Token' => '123456'
})
.to_return(status: 202, body: '{ "server": { "id": "o1o2o3" } }')

instance_id = @nova_client.create_server(env,
name: 'inst',
volume_boot: { id: 'vol', device: 'vda' },
flavor_ref: 'flav',
keypair: 'key')
expect(instance_id).to eq('o1o2o3')
end
end
Expand Down
101 changes: 99 additions & 2 deletions source/spec/vagrant-openstack-provider/config_resolver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
context 'with id' do
it 'returns the specified flavor' do
config.stub(:image) { 'img-001' }
config.stub(:volume_boot) { nil }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
Expand All @@ -195,6 +196,7 @@
context 'with name' do
it 'returns the specified flavor' do
config.stub(:image) { 'image-02' }
config.stub(:volume_boot) { nil }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
Expand All @@ -205,13 +207,25 @@
context 'with invalid identifier' do
it 'raise an error' do
config.stub(:image) { 'not-existing' }
config.stub(:volume_boot) { nil }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
expect { @action.resolve_image(env) }.to raise_error(Errors::NoMatchingImage)
end
end
context 'with no images in config' do
it 'return nil' do
config.stub(:image) { nil }
config.stub(:volume_boot) { nil }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
@action.resolve_image(env).should eq(nil)
end
end
end

describe 'resolve_floating_ip' do
Expand Down Expand Up @@ -513,6 +527,10 @@
context 'with string volume name' do
it 'returns normalized volume' do
config.stub(:volume_boot) { 'vol-01' }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vda'
end
end
Expand All @@ -527,6 +545,10 @@
context 'with hash volume name' do
it 'returns normalized volume' do
config.stub(:volume_boot) { { name: 'vol-01' } }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vda'
end
end
Expand All @@ -541,14 +563,18 @@
context 'with hash volume name and device' do
it 'returns normalized volume' do
config.stub(:volume_boot) { { name: 'vol-01', device: 'vdb' } }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
expect(@action.resolve_volume_boot(env)).to eq id: '001', device: 'vdb'
end
end

context 'with empty hash' do
it 'raises an error' do
config.stub(:volume_boot) { {} }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictVolumeNameId)
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

Expand Down Expand Up @@ -586,6 +612,77 @@
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::MultipleVolumeName)
end
end

context 'with hash containing a name and an image_name' do
it 'raises an error' do
config.stub(:volume_boot) { { name: 'vol-01', image: 'img_001' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

context 'with hash containing a name and a size' do
it 'raises an error' do
config.stub(:volume_boot) { { name: 'vol-01', size: '10' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

context 'with hash containing a name and a delete_on_destroy indication' do
it 'raises an error' do
config.stub(:volume_boot) { { name: 'vol-01', delete_on_destroy: 'true' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

context 'with hash containing a volume_id and an image_name' do
it 'raises an error' do
config.stub(:volume_boot) { { id: 'id', image: 'img_001' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

context 'with hash containing a volume_id and a size' do
it 'raises an error' do
config.stub(:volume_boot) { { id: 'id', size: '10' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

context 'with hash containing a volume_id and a delete_on_destroy indication' do
it 'raises an error' do
config.stub(:volume_boot) { { id: 'id', delete_on_destroy: 'true' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

context 'with hash containing an image_name without size' do
it 'raises an error' do
config.stub(:volume_boot) { { image: 'img-001' } }
expect { @action.resolve_volume_boot(env) }.to raise_error(Errors::UnresolvedVolume)
end
end

context 'with hash containing an image_name with a size' do
it 'return normalized volume' do
config.stub(:volume_boot) { { image: 'image-01', size: '10' } }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
expect(@action.resolve_volume_boot(env)).to eq image: 'img-001', device: 'vda', size: '10', delete_on_destroy: 'true'
end
end

context 'with hash containing an image_name, size, device and delete_on_destroy' do
it 'return normalized volume' do
config.stub(:volume_boot) { { image: 'image-01', size: '10', device: 'vdb', delete_on_destroy: 'false' } }
nova.stub(:get_all_images).with(anything) do
[Item.new('img-001', 'image-01'),
Item.new('img-002', 'image-02')]
end
expect(@action.resolve_volume_boot(env)).to eq image: 'img-001', device: 'vdb', size: '10', delete_on_destroy: 'false'
end
end
end

context 'cinder service is not available' do
Expand Down Expand Up @@ -700,7 +797,7 @@
context 'with empty hash' do
it 'raises an error' do
config.stub(:volumes) { [{}] }
expect { @action.resolve_volumes(env) }.to raise_error(Errors::ConflictVolumeNameId)
expect { @action.resolve_volumes(env) }.to raise_error(Errors::ConflictBootVolume)
end
end

Expand Down

0 comments on commit 5d742e4

Please sign in to comment.