mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
Allow to use many disks in vagrant box for libvirt provider
Adds support for a new multi disk box format and handling to upload the
multiple disks to the storage pool.
New format is:
{
'disks': [
{
'name': 'disk1.img',
'virtual_size': 10,
'format': 'qcow2'
},
{
'name': 'disk2.img',
'virtual_size': 15,
'format': 'qcow2'
},
{
'name': 'disk3.img',
}
],
'provider': 'libvirt',
'format': 'qcow2'
}
It is expected to remove format from being set at the top level when
using the new format, with the assuming that qcow2 should be the default
format, and other formats should be permitted to be specified as needed.
Includes tests for handling the box images and creation of domain
volumes. Additionally includes an integration test to ensure a box with
2 disks will work as expected.
Partially fixes: #602
This commit is contained in:
committed by
Darragh Bailey
parent
6f608c54bf
commit
225237b125
@@ -92,6 +92,7 @@ module VagrantPlugins
|
||||
# Storage
|
||||
@storage_pool_name = config.storage_pool_name
|
||||
@snapshot_pool_name = config.snapshot_pool_name
|
||||
@domain_volumes = []
|
||||
@disks = config.disks
|
||||
@cdroms = config.cdroms
|
||||
|
||||
@@ -141,19 +142,28 @@ module VagrantPlugins
|
||||
else
|
||||
pool_name = @storage_pool_name
|
||||
end
|
||||
@logger.debug "Search for volume in pool: #{pool_name}"
|
||||
domain_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: "#{@name}.img"
|
||||
).find { |x| x.pool_name == pool_name }
|
||||
raise Errors::DomainVolumeExists if domain_volume.nil?
|
||||
@domain_volume_path = domain_volume.path
|
||||
@logger.debug "Search for volumes in pool: #{pool_name}"
|
||||
env[:box_volumes].each_index do |index|
|
||||
suffix_index = index > 0 ? "_#{index}" : ''
|
||||
domain_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: "#{@name}#{suffix_index}.img"
|
||||
).find { |x| x.pool_name == pool_name }
|
||||
raise Errors::DomainVolumeExists if domain_volume.nil?
|
||||
@domain_volumes.push({
|
||||
:dev => (index+1).vdev.to_s,
|
||||
:cache => @domain_volume_cache,
|
||||
:bus => @disk_bus,
|
||||
:path => domain_volume.path,
|
||||
:virtual_size => env[:box_volumes][index][:virtual_size]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# If we have a box, take the path from the domain volume and set our storage_prefix.
|
||||
# If not, we dump the storage pool xml to get its defined path.
|
||||
# the default storage prefix is typically: /var/lib/libvirt/images/
|
||||
if env[:machine].config.vm.box
|
||||
storage_prefix = File.dirname(@domain_volume_path) + '/' # steal
|
||||
storage_prefix = File.dirname(@domain_volumes[0][:path]) + '/' # steal
|
||||
else
|
||||
storage_prefix = get_disk_storage_prefix(env, @storage_pool_name)
|
||||
end
|
||||
@@ -250,7 +260,9 @@ module VagrantPlugins
|
||||
env[:ui].info(" -- Base box: #{env[:machine].box.name}")
|
||||
end
|
||||
env[:ui].info(" -- Storage pool: #{@storage_pool_name}")
|
||||
env[:ui].info(" -- Image: #{@domain_volume_path} (#{env[:box_virtual_size]}G)")
|
||||
@domain_volumes.each do |volume|
|
||||
env[:ui].info(" -- Image(#{volume[:device]}): #{volume[:path]}, #{volume[:virtual_size]}G")
|
||||
end
|
||||
|
||||
if not @disk_driver_opts.empty?
|
||||
env[:ui].info(" -- Disk driver opts: #{@disk_driver_opts.reject { |k,v| v.nil? }.map { |k,v| "#{k}='#{v}'"}.join(' ')}")
|
||||
|
||||
@@ -18,72 +18,74 @@ module VagrantPlugins
|
||||
def call(env)
|
||||
env[:ui].info(I18n.t('vagrant_libvirt.creating_domain_volume'))
|
||||
|
||||
# Get config options.
|
||||
config = env[:machine].provider_config
|
||||
env[:box_volumes].each_index do |index|
|
||||
suffix_index = index > 0 ? "_#{index}" : ''
|
||||
# Get config options.
|
||||
config = env[:machine].provider_config
|
||||
|
||||
# This is name of newly created image for vm.
|
||||
@name = "#{env[:domain_name]}.img"
|
||||
# This is name of newly created image for vm.
|
||||
@name = "#{env[:domain_name]}#{suffix_index}.img"
|
||||
|
||||
# Verify the volume doesn't exist already.
|
||||
domain_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: @name
|
||||
).first
|
||||
raise Errors::DomainVolumeExists if domain_volume && domain_volume.id
|
||||
# Verify the volume doesn't exist already.
|
||||
domain_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: @name
|
||||
).first
|
||||
raise Errors::DomainVolumeExists if domain_volume && domain_volume.id
|
||||
|
||||
# Get path to backing image - box volume.
|
||||
box_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: env[:box_volume_name]
|
||||
).first
|
||||
@backing_file = box_volume.path
|
||||
# Get path to backing image - box volume.
|
||||
box_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: env[:box_volumes][index][:name]
|
||||
).first
|
||||
@backing_file = box_volume.path
|
||||
|
||||
# Virtual size of image. Take value worked out by HandleBoxImage
|
||||
@capacity = env[:box_virtual_size] # G
|
||||
# Virtual size of image. Take value worked out by HandleBoxImage
|
||||
@capacity = env[:box_volumes][index][:virtual_size] # G
|
||||
|
||||
# Create new volume from xml template. Fog currently doesn't support
|
||||
# volume snapshots directly.
|
||||
begin
|
||||
xml = Nokogiri::XML::Builder.new do |xml|
|
||||
xml.volume do
|
||||
xml.name(@name)
|
||||
xml.capacity(@capacity, unit: 'G')
|
||||
xml.target do
|
||||
xml.format(type: 'qcow2')
|
||||
xml.permissions do
|
||||
xml.owner storage_uid(env)
|
||||
xml.group storage_gid(env)
|
||||
xml.label 'virt_image_t'
|
||||
end
|
||||
end
|
||||
xml.backingStore do
|
||||
xml.path(@backing_file)
|
||||
xml.format(type: 'qcow2')
|
||||
xml.permissions do
|
||||
xml.owner storage_uid(env)
|
||||
xml.group storage_gid(env)
|
||||
xml.label 'virt_image_t'
|
||||
# Create new volume from xml template. Fog currently doesn't support
|
||||
# volume snapshots directly.
|
||||
begin
|
||||
xml = Nokogiri::XML::Builder.new do |xml|
|
||||
xml.volume do
|
||||
xml.name(@name)
|
||||
xml.capacity(@capacity, unit: 'G')
|
||||
xml.target do
|
||||
xml.format(type: 'qcow2')
|
||||
xml.permissions do
|
||||
xml.owner storage_uid(env)
|
||||
xml.group storage_gid(env)
|
||||
xml.label 'virt_image_t'
|
||||
end
|
||||
end
|
||||
xml.backingStore do
|
||||
xml.path(@backing_file)
|
||||
xml.format(type: 'qcow2')
|
||||
xml.permissions do
|
||||
xml.owner storage_uid(env)
|
||||
xml.group storage_gid(env)
|
||||
xml.label 'virt_image_t'
|
||||
end
|
||||
end
|
||||
end
|
||||
end.to_xml(
|
||||
save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
|
||||
Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
|
||||
Nokogiri::XML::Node::SaveOptions::FORMAT
|
||||
)
|
||||
if config.snapshot_pool_name != config.storage_pool_name
|
||||
pool_name = config.snapshot_pool_name
|
||||
else
|
||||
pool_name = config.storage_pool_name
|
||||
end
|
||||
end.to_xml(
|
||||
save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
|
||||
Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
|
||||
Nokogiri::XML::Node::SaveOptions::FORMAT
|
||||
)
|
||||
if config.snapshot_pool_name != config.storage_pool_name
|
||||
pool_name = config.snapshot_pool_name
|
||||
else
|
||||
pool_name = config.storage_pool_name
|
||||
@logger.debug "Using pool #{pool_name} for base box snapshot"
|
||||
domain_volume = env[:machine].provider.driver.connection.volumes.create(
|
||||
xml: xml,
|
||||
pool_name: pool_name
|
||||
)
|
||||
rescue Fog::Errors::Error => e
|
||||
raise Errors::FogDomainVolumeCreateError,
|
||||
error_message: e.message
|
||||
end
|
||||
@logger.debug "Using pool #{pool_name} for base box snapshot"
|
||||
domain_volume = env[:machine].provider.driver.connection.volumes.create(
|
||||
xml: xml,
|
||||
pool_name: pool_name
|
||||
)
|
||||
rescue Fog::Errors::Error => e
|
||||
raise Errors::FogDomainVolumeCreateError,
|
||||
error_message: e.message
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,31 +18,46 @@ module VagrantPlugins
|
||||
def call(env)
|
||||
# Verify box metadata for mandatory values.
|
||||
#
|
||||
# Virtual size has to be set for allocating space in storage pool.
|
||||
box_virtual_size = env[:machine].box.metadata['virtual_size']
|
||||
raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
|
||||
# Verify disk number
|
||||
disks = env[:machine].box.metadata.fetch('disks', [])
|
||||
if disks.empty?
|
||||
disks.push({
|
||||
'path' => HandleBoxImage.get_default_box_image_path(0),
|
||||
'name' => HandleBoxImage.get_volume_name(env, 0),
|
||||
'virtual_size' => HandleBoxImage.get_virtual_size(env),
|
||||
})
|
||||
end
|
||||
HandleBoxImage.verify_virtual_size_in_disks(disks)
|
||||
|
||||
# Support qcow2 format only for now, but other formats with backing
|
||||
# store capability should be usable.
|
||||
box_format = env[:machine].box.metadata['format']
|
||||
if box_format.nil?
|
||||
raise Errors::NoBoxFormatSet
|
||||
elsif box_format != 'qcow2'
|
||||
raise Errors::WrongBoxFormatSet
|
||||
end
|
||||
HandleBoxImage.verify_box_format(box_format)
|
||||
|
||||
env[:box_volume_number] = disks.length()
|
||||
env[:box_volumes] = Array.new(env[:box_volume_number]) {|i| {
|
||||
:path => HandleBoxImage.get_box_image_path(
|
||||
env,
|
||||
disks[i].fetch('path', HandleBoxImage.get_default_box_image_path(i))
|
||||
),
|
||||
:name => disks[i].fetch('name', HandleBoxImage.get_volume_name(env, i)),
|
||||
:virtual_size => disks[i]['virtual_size'],
|
||||
:format => HandleBoxImage.verify_box_format(
|
||||
disks[i].fetch('format', box_format),
|
||||
i
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# Get config options
|
||||
config = env[:machine].provider_config
|
||||
box_image_file = env[:machine].box.directory.join('box.img').to_s
|
||||
env[:box_volume_name] = env[:machine].box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
|
||||
env[:box_volume_name] << "_vagrant_box_image_#{
|
||||
begin
|
||||
env[:machine].box.version.to_s
|
||||
rescue
|
||||
''
|
||||
end}.img"
|
||||
box_image_files = []
|
||||
env[:box_volumes].each do |d|
|
||||
box_image_files.push(d[:path])
|
||||
end
|
||||
|
||||
# Override box_virtual_size
|
||||
box_virtual_size = env[:box_volumes][0][:virtual_size]
|
||||
if config.machine_virtual_size
|
||||
if config.machine_virtual_size < box_virtual_size
|
||||
# Warn that a virtual size less than the box metadata size
|
||||
@@ -57,70 +72,20 @@ module VagrantPlugins
|
||||
end
|
||||
end
|
||||
# save for use by later actions
|
||||
env[:box_virtual_size] = box_virtual_size
|
||||
env[:box_volumes][0][:virtual_size] = box_virtual_size
|
||||
|
||||
# while inside the synchronize block take care not to call the next
|
||||
# action in the chain, as must exit this block first to prevent
|
||||
# locking all subsequent actions as well.
|
||||
@@lock.synchronize do
|
||||
# Don't continue if image already exists in storage pool.
|
||||
box_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: env[:box_volume_name]
|
||||
).first
|
||||
break if box_volume && box_volume.id
|
||||
env[:box_volumes].each_index do |i|
|
||||
# Don't continue if image already exists in storage pool.
|
||||
box_volume = env[:machine].provider.driver.connection.volumes.all(
|
||||
name: env[:box_volumes][i][:name]
|
||||
).first
|
||||
next if box_volume && box_volume.id
|
||||
|
||||
# Box is not available as a storage pool volume. Create and upload
|
||||
# it as a copy of local box image.
|
||||
env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
|
||||
|
||||
# Create new volume in storage pool
|
||||
unless File.exist?(box_image_file)
|
||||
raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
|
||||
end
|
||||
box_image_size = File.size(box_image_file) # B
|
||||
message = "Creating volume #{env[:box_volume_name]}"
|
||||
message << " in storage pool #{config.storage_pool_name}."
|
||||
@logger.info(message)
|
||||
|
||||
@storage_volume_uid = storage_uid env
|
||||
@storage_volume_gid = storage_gid env
|
||||
|
||||
begin
|
||||
fog_volume = env[:machine].provider.driver.connection.volumes.create(
|
||||
name: env[:box_volume_name],
|
||||
allocation: "#{box_image_size / 1024 / 1024}M",
|
||||
capacity: "#{box_virtual_size}G",
|
||||
format_type: box_format,
|
||||
owner: @storage_volume_uid,
|
||||
group: @storage_volume_gid,
|
||||
pool_name: config.storage_pool_name
|
||||
)
|
||||
rescue Fog::Errors::Error => e
|
||||
raise Errors::FogCreateVolumeError,
|
||||
error_message: e.message
|
||||
end
|
||||
|
||||
# Upload box image to storage pool
|
||||
ret = upload_image(box_image_file, config.storage_pool_name,
|
||||
env[:box_volume_name], env) do |progress|
|
||||
rewriting(env[:ui]) do |ui|
|
||||
ui.clear_line
|
||||
ui.report_progress(progress, box_image_size, false)
|
||||
end
|
||||
end
|
||||
|
||||
# Clear the line one last time since the progress meter doesn't
|
||||
# disappear immediately.
|
||||
rewriting(env[:ui]) {|ui| ui.clear_line}
|
||||
|
||||
# If upload failed or was interrupted, remove created volume from
|
||||
# storage pool.
|
||||
if env[:interrupted] || !ret
|
||||
begin
|
||||
fog_volume.destroy
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
send_box_image(env, config, box_image_files[i], env[:box_volumes][i])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,6 +94,105 @@ module VagrantPlugins
|
||||
|
||||
protected
|
||||
|
||||
def self.get_volume_name(env, index)
|
||||
name = env[:machine].box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
|
||||
name << "_vagrant_box_image_#{
|
||||
begin
|
||||
env[:machine].box.version.to_s
|
||||
rescue
|
||||
''
|
||||
end}_#{index}.img"
|
||||
return name
|
||||
end
|
||||
|
||||
def self.get_virtual_size(env)
|
||||
# Virtual size has to be set for allocating space in storage pool.
|
||||
box_virtual_size = env[:machine].box.metadata['virtual_size']
|
||||
raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
|
||||
return box_virtual_size
|
||||
end
|
||||
|
||||
def self.get_default_box_image_path(index)
|
||||
return index <= 0 ? 'box.img' : "box_#{index}.img"
|
||||
end
|
||||
|
||||
def self.get_box_image_path(env, box_name)
|
||||
return env[:machine].box.directory.join(box_name).to_s
|
||||
end
|
||||
|
||||
def self.verify_box_format(box_format, disk_index=nil)
|
||||
if box_format.nil?
|
||||
raise Errors::NoBoxFormatSet
|
||||
elsif box_format != 'qcow2'
|
||||
if disk_index.nil?
|
||||
raise Errors::WrongBoxFormatSet
|
||||
else
|
||||
raise Errors::WrongDiskFormatSet,
|
||||
disk_index: disk_index
|
||||
end
|
||||
end
|
||||
return box_format
|
||||
end
|
||||
|
||||
def self.verify_virtual_size_in_disks(disks)
|
||||
disks.each_with_index do |disk, index|
|
||||
raise Errors::NoDiskVirtualSizeSet, disk_index:index if disk['virtual_size'].nil?
|
||||
end
|
||||
end
|
||||
|
||||
def send_box_image(env, config, box_image_file, box_volume)
|
||||
# Box is not available as a storage pool volume. Create and upload
|
||||
# it as a copy of local box image.
|
||||
env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
|
||||
|
||||
# Create new volume in storage pool
|
||||
unless File.exist?(box_image_file)
|
||||
raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
|
||||
end
|
||||
box_image_size = File.size(box_image_file) # B
|
||||
message = "Creating volume #{box_volume[:name]}"
|
||||
message << " in storage pool #{config.storage_pool_name}."
|
||||
@logger.info(message)
|
||||
|
||||
begin
|
||||
fog_volume = env[:machine].provider.driver.connection.volumes.create(
|
||||
name: box_volume[:name],
|
||||
allocation: "#{box_image_size / 1024 / 1024}M",
|
||||
capacity: "#{box_volume[:virtual_size]}G",
|
||||
format_type: box_volume[:format],
|
||||
owner: storage_uid(env),
|
||||
group: storage_gid(env),
|
||||
pool_name: config.storage_pool_name
|
||||
)
|
||||
rescue Fog::Errors::Error => e
|
||||
raise Errors::FogCreateVolumeError,
|
||||
error_message: e.message
|
||||
end
|
||||
|
||||
# Upload box image to storage pool
|
||||
ret = upload_image(box_image_file, config.storage_pool_name,
|
||||
box_volume[:name], env) do |progress|
|
||||
rewriting(env[:ui]) do |ui|
|
||||
ui.clear_line
|
||||
ui.report_progress(progress, box_image_size, false)
|
||||
end
|
||||
end
|
||||
|
||||
# Clear the line one last time since the progress meter doesn't
|
||||
# disappear immediately.
|
||||
rewriting(env[:ui]) {|ui| ui.clear_line}
|
||||
|
||||
# If upload failed or was interrupted, remove created volume from
|
||||
# storage pool.
|
||||
if env[:interrupted] || !ret
|
||||
begin
|
||||
fog_volume.destroy
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Fog Libvirt currently doesn't support uploading images to storage
|
||||
# pool volumes. Use ruby-libvirt client instead.
|
||||
def upload_image(image_file, pool_name, volume_name, env)
|
||||
|
||||
@@ -46,6 +46,10 @@ module VagrantPlugins
|
||||
error_key(:no_box_virtual_size)
|
||||
end
|
||||
|
||||
class NoDiskVirtualSizeSet < VagrantLibvirtError
|
||||
error_key(:no_disk_virtual_size)
|
||||
end
|
||||
|
||||
class NoBoxFormatSet < VagrantLibvirtError
|
||||
error_key(:no_box_format)
|
||||
end
|
||||
@@ -54,6 +58,10 @@ module VagrantPlugins
|
||||
error_key(:wrong_box_format)
|
||||
end
|
||||
|
||||
class WrongDiskFormatSet < VagrantLibvirtError
|
||||
error_key(:wrong_disk_format)
|
||||
end
|
||||
|
||||
# Fog Libvirt exceptions
|
||||
class FogError < VagrantLibvirtError
|
||||
error_key(:fog_error)
|
||||
|
||||
@@ -113,18 +113,18 @@
|
||||
<% if @emulator_path %>
|
||||
<emulator><%= @emulator_path %></emulator>
|
||||
<% end %>
|
||||
<% if @domain_volume_path %>
|
||||
<% @domain_volumes.each do |volume| -%>
|
||||
<disk type='file' device='disk'>
|
||||
<driver name='qemu' type='qcow2' <%=
|
||||
@disk_driver_opts.empty? ? "cache='#{@domain_volume_cache}'" :
|
||||
@disk_driver_opts.empty? ? "cache='#{volume[:cache]}'" :
|
||||
@disk_driver_opts.reject { |k,v| v.nil? }
|
||||
.map { |k,v| "#{k}='#{v}'"}
|
||||
.join(' ') -%>/>
|
||||
<source file='<%= @domain_volume_path %>'/>
|
||||
<source file='<%= volume[:path] %>'/>
|
||||
<%# we need to ensure a unique target dev -%>
|
||||
<target dev='<%= @disk_device %>' bus='<%= @disk_bus %>'/>
|
||||
<target dev='<%= volume[:dev] %>' bus='<%= volume[:bus] %>'/>
|
||||
</disk>
|
||||
<% end %>
|
||||
<% end -%>
|
||||
<%# additional disks -%>
|
||||
<% @disks.each do |d| -%>
|
||||
<disk type='file' device='disk'>
|
||||
|
||||
Reference in New Issue
Block a user