diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 920b365..bdb114e 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -18,6 +18,7 @@ jobs:
- simple vm provision via shell
- bring up with custom default prefix
- bring up with second disk
+ - bring up with two disks
- bring up with adjusted memory settings
- bring up with adjusted cpu settings
- ip is reachable with private network
diff --git a/lib/vagrant-libvirt/action/create_domain.rb b/lib/vagrant-libvirt/action/create_domain.rb
index fa70fb6..eac27f6 100644
--- a/lib/vagrant-libvirt/action/create_domain.rb
+++ b/lib/vagrant-libvirt/action/create_domain.rb
@@ -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(' ')}")
diff --git a/lib/vagrant-libvirt/action/create_domain_volume.rb b/lib/vagrant-libvirt/action/create_domain_volume.rb
index 092a408..49831b2 100644
--- a/lib/vagrant-libvirt/action/create_domain_volume.rb
+++ b/lib/vagrant-libvirt/action/create_domain_volume.rb
@@ -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
diff --git a/lib/vagrant-libvirt/action/handle_box_image.rb b/lib/vagrant-libvirt/action/handle_box_image.rb
index 066df16..05381ca 100644
--- a/lib/vagrant-libvirt/action/handle_box_image.rb
+++ b/lib/vagrant-libvirt/action/handle_box_image.rb
@@ -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)
diff --git a/lib/vagrant-libvirt/errors.rb b/lib/vagrant-libvirt/errors.rb
index 3945c2b..3410a1d 100644
--- a/lib/vagrant-libvirt/errors.rb
+++ b/lib/vagrant-libvirt/errors.rb
@@ -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)
diff --git a/lib/vagrant-libvirt/templates/domain.xml.erb b/lib/vagrant-libvirt/templates/domain.xml.erb
index c45df81..240acd0 100644
--- a/lib/vagrant-libvirt/templates/domain.xml.erb
+++ b/lib/vagrant-libvirt/templates/domain.xml.erb
@@ -113,18 +113,18 @@
<% if @emulator_path %>
<%= @emulator_path %>
<% end %>
- <% if @domain_volume_path %>
+<% @domain_volumes.each do |volume| -%>
/>
-
+
<%# we need to ensure a unique target dev -%>
-
+
- <% end %>
+<% end -%>
<%# additional disks -%>
<% @disks.each do |d| -%>
diff --git a/locales/en.yml b/locales/en.yml
index 0bf881b..67a8805 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -92,10 +92,14 @@ en:
Error: %{stderr}
no_box_virtual_size: |-
No image virtual size specified for box.
+ no_disk_virtual_size: |-
+ No image virtual size specified for disk with index %{disk_index}.
no_box_format: |-
No image format specified for box.
wrong_box_format: |-
Wrong image format specified for box.
+ wrong_disk_format: |-
+ Wrong image format specified for disk with index %{disk_index}.
fog_libvirt_connection_error: |-
Error while connecting to Libvirt: %{error_message}
fog_create_volume_error: |-
diff --git a/spec/unit/action/create_domain_spec.rb b/spec/unit/action/create_domain_spec.rb
index d85d676..3327680 100644
--- a/spec/unit/action/create_domain_spec.rb
+++ b/spec/unit/action/create_domain_spec.rb
@@ -31,6 +31,12 @@ describe VagrantPlugins::ProviderLibvirt::Action::CreateDomain do
env[:domain_name] = "vagrant-test_default"
+ env[:box_volumes] = []
+ env[:box_volumes].push({
+ :path=>"/test/box.img",
+ :name=>"test_vagrant_box_image_1.1.1_0.img",
+ :virtual_size=>5
+ })
# should be ignored for system session and used for user session
allow(Process).to receive(:uid).and_return(9999)
allow(Process).to receive(:gid).and_return(9999)
diff --git a/spec/unit/action/create_domain_volume_spec.rb b/spec/unit/action/create_domain_volume_spec.rb
new file mode 100644
index 0000000..716eaa1
--- /dev/null
+++ b/spec/unit/action/create_domain_volume_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+require 'support/sharedcontext'
+require 'support/libvirt_context'
+
+require 'vagrant-libvirt/action/destroy_domain'
+
+describe VagrantPlugins::ProviderLibvirt::Action::CreateDomainVolume do
+ subject { described_class.new(app, env) }
+
+ include_context 'unit'
+ include_context 'libvirt'
+
+ let(:libvirt_domain) { double('libvirt_domain') }
+ let(:libvirt_client) { double('libvirt_client') }
+ let(:volumes) { double('volumes') }
+ let(:all) { double('all') }
+ let(:box_volume) { double('box_volume') }
+
+ def read_test_file(name)
+ File.read(File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), name))
+ end
+
+ describe '#call' do
+ before do
+ allow_any_instance_of(VagrantPlugins::ProviderLibvirt::Driver)
+ .to receive(:connection).and_return(connection)
+ allow(connection).to receive(:client).and_return(libvirt_client)
+ allow(connection).to receive(:volumes).and_return(volumes)
+ allow(volumes).to receive(:all).and_return(all)
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(nil)
+ env[:domain_name] = 'test'
+ end
+
+ context 'when one disk' do
+ before do
+ allow(box_volume).to receive(:path).and_return('/test/path_0.img')
+ env[:box_volumes] = [
+ {
+ :name=>"test_vagrant_box_image_1.1.1_0.img",
+ :virtual_size=>5
+ }
+ ]
+ end
+
+ it 'should create one disk in storage' do
+ expected_xml = read_test_file('one_disk_in_storage.xml')
+ expect(ui).to receive(:info).with('Creating image (snapshot of base box volume).')
+ expect(logger).to receive(:debug).with('Using pool default for base box snapshot')
+ expect(volumes).to receive(:create).with(
+ :xml => expected_xml,
+ :pool_name => "default"
+ )
+ expect(subject.call(env)).to be_nil
+ end
+ end
+
+ context 'when three disks' do
+ before do
+ allow(box_volume).to receive(:path).and_return(
+ '/test/path_0.img',
+ '/test/path_1.img',
+ '/test/path_2.img',
+ )
+ env[:box_volumes] = [
+ {
+ :name=>"test_vagrant_box_image_1.1.1_0.img",
+ :virtual_size=>5
+ },
+ {
+ :name=>"test_vagrant_box_image_1.1.1_1.img",
+ :virtual_size=>10
+ },
+ {
+ :name=>"test_vagrant_box_image_1.1.1_2.img",
+ :virtual_size=>20
+ }
+ ]
+ end
+
+ it 'should create three disks in storage' do
+ expect(ui).to receive(:info).with('Creating image (snapshot of base box volume).')
+ expect(logger).to receive(:debug).with('Using pool default for base box snapshot')
+ expect(volumes).to receive(:create).with(
+ :xml => read_test_file('three_disks_in_storage_disk_0.xml'),
+ :pool_name => "default"
+ )
+ expect(logger).to receive(:debug).with('Using pool default for base box snapshot')
+ expect(volumes).to receive(:create).with(
+ :xml => read_test_file('three_disks_in_storage_disk_1.xml'),
+ :pool_name => "default"
+ )
+ expect(logger).to receive(:debug).with('Using pool default for base box snapshot')
+ expect(volumes).to receive(:create).with(
+ :xml => read_test_file('three_disks_in_storage_disk_2.xml'),
+ :pool_name => "default"
+ )
+ expect(subject.call(env)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml b/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml
new file mode 100644
index 0000000..f66c343
--- /dev/null
+++ b/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml
@@ -0,0 +1,21 @@
+
+ test.img
+ 5
+
+
+
+ 0
+ 0
+
+
+
+
+ /test/path_0.img
+
+
+ 0
+ 0
+
+
+
+
diff --git a/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml b/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml
new file mode 100644
index 0000000..f66c343
--- /dev/null
+++ b/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml
@@ -0,0 +1,21 @@
+
+ test.img
+ 5
+
+
+
+ 0
+ 0
+
+
+
+
+ /test/path_0.img
+
+
+ 0
+ 0
+
+
+
+
diff --git a/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml b/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml
new file mode 100644
index 0000000..ecd4dc3
--- /dev/null
+++ b/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml
@@ -0,0 +1,21 @@
+
+ test_1.img
+ 10
+
+
+
+ 0
+ 0
+
+
+
+
+ /test/path_1.img
+
+
+ 0
+ 0
+
+
+
+
diff --git a/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml b/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml
new file mode 100644
index 0000000..1f39376
--- /dev/null
+++ b/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml
@@ -0,0 +1,21 @@
+
+ test_2.img
+ 20
+
+
+
+ 0
+ 0
+
+
+
+
+ /test/path_2.img
+
+
+ 0
+ 0
+
+
+
+
diff --git a/spec/unit/action/handle_box_image_spec.rb b/spec/unit/action/handle_box_image_spec.rb
new file mode 100644
index 0000000..2091934
--- /dev/null
+++ b/spec/unit/action/handle_box_image_spec.rb
@@ -0,0 +1,296 @@
+require 'spec_helper'
+require 'support/sharedcontext'
+require 'support/libvirt_context'
+
+require 'vagrant-libvirt/action/destroy_domain'
+
+describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do
+ subject { described_class.new(app, env) }
+
+ include_context 'unit'
+ include_context 'libvirt'
+
+ let(:libvirt_client) { double('libvirt_client') }
+ let(:volumes) { double('volumes') }
+ let(:all) { double('all') }
+ let(:box_volume) { double('box_volume') }
+ let(:fog_volume) { double('fog_volume') }
+
+ describe '#call' do
+ before do
+ allow_any_instance_of(VagrantPlugins::ProviderLibvirt::Driver)
+ .to receive(:connection).and_return(connection)
+ allow(connection).to receive(:client).and_return(libvirt_client)
+ allow(connection).to receive(:volumes).and_return(volumes)
+ allow(volumes).to receive(:all).and_return(all)
+ allow(env[:ui]).to receive(:clear_line)
+
+ end
+
+ context 'when one disk in metadata.json' do
+ before do
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(1)
+ allow(env[:machine]).to receive_message_chain("box.name") { 'test' }
+ allow(env[:machine]).to receive_message_chain("box.version") { '1.1.1' }
+ allow(env[:machine]).to receive_message_chain("box.metadata") { Hash[
+ 'virtual_size'=> 5,
+ 'format' => 'qcow2'
+ ]
+ }
+ allow(env[:machine]).to receive_message_chain("box.directory.join") do |arg|
+ '/test/'.concat(arg.to_s)
+ end
+ end
+
+ it 'should have one disk in machine env' do
+ expect(subject.call(env)).to be_nil
+ expect(env[:box_volume_number]).to eq(1)
+ expect(env[:box_volumes]).to eq(
+ [
+ {
+ :path=>"/test/box.img",
+ :name=>"test_vagrant_box_image_1.1.1_0.img",
+ :virtual_size=>5,
+ :format=>"qcow2"
+ }
+ ]
+ )
+ end
+
+ context 'when disk image not in storage pool' do
+ before do
+ allow(File).to receive(:exist?).and_return(true)
+ allow(File).to receive(:size).and_return(5*1024*1024*1024)
+ allow(all).to receive(:first).and_return(nil)
+ allow(subject).to receive(:upload_image).and_return(true)
+ allow(volumes).to receive(:create).and_return(fog_volume)
+ end
+
+ it 'should upload disk' do
+ expect(ui).to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(logger).to receive(:info).with('Creating volume test_vagrant_box_image_1.1.1_0.img in storage pool default.')
+ expect(volumes).to receive(:create).with(
+ hash_including(
+ :name => "test_vagrant_box_image_1.1.1_0.img",
+ :allocation => "5120M",
+ :capacity => "5G",
+ )
+ )
+ expect(subject).to receive(:upload_image)
+ expect(subject.call(env)).to be_nil
+ end
+ end
+
+ context 'when disk image already in storage pool' do
+ before do
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(1)
+ end
+
+ it 'should skip disk upload' do
+ expect(volumes).not_to receive(:create)
+ expect(subject).not_to receive(:upload_image)
+ expect(subject.call(env)).to be_nil
+ end
+ end
+ end
+
+ context 'when three disks in metadata.json' do
+ before do
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(1)
+ allow(env[:machine]).to receive_message_chain("box.name") { 'test' }
+ allow(env[:machine]).to receive_message_chain("box.version") { '1.1.1' }
+ allow(env[:machine]).to receive_message_chain("box.metadata") { Hash[
+ 'disks' => [
+ {
+ 'name'=>'send_box_name.img',
+ 'virtual_size'=> 5,
+ },
+ {
+ 'path' => 'disk.qcow2',
+ 'virtual_size'=> 10
+ },
+ {
+ 'virtual_size'=> 20,
+ },
+ ],
+ 'format' => 'qcow2'
+ ]
+ }
+ allow(env[:machine]).to receive_message_chain("box.directory.join") do |arg|
+ '/test/'.concat(arg.to_s)
+ end
+ end
+
+ it 'should have three disks in machine env' do
+ expect(subject.call(env)).to be_nil
+ expect(env[:box_volume_number]).to eq(3)
+ expect(env[:box_volumes]).to eq(
+ [
+ {
+ :path=>"/test/box.img",
+ :name=>"send_box_name.img",
+ :virtual_size=>5,
+ :format=>"qcow2"
+ },
+ {
+ :path=>"/test/disk.qcow2",
+ :name=>"test_vagrant_box_image_1.1.1_1.img",
+ :virtual_size=>10,
+ :format=>"qcow2"
+ },
+ {
+ :path=>"/test/box_2.img",
+ :name=>"test_vagrant_box_image_1.1.1_2.img",
+ :virtual_size=>20,
+ :format=>"qcow2"
+ }
+ ]
+ )
+ end
+
+ context 'when none of the disks in storage pool' do
+ before do
+ allow(File).to receive(:exist?).and_return(true)
+ allow(File).to receive(:size).and_return(5*1024*1024*1024, 10*1024*1024*1024, 20*1024*1024*1024)
+ allow(all).to receive(:first).and_return(nil)
+ allow(subject).to receive(:upload_image).and_return(true)
+ allow(volumes).to receive(:create).and_return(fog_volume)
+ end
+
+ it 'should upload all 3 disks' do
+ expect(ui).to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(logger).to receive(:info).with('Creating volume send_box_name.img in storage pool default.')
+ expect(volumes).to receive(:create).with(
+ hash_including(
+ :name => "send_box_name.img",
+ :allocation => "5120M",
+ :capacity => "5G",
+ )
+ )
+ expect(subject).to receive(:upload_image)
+ expect(ui).to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(logger).to receive(:info).with('Creating volume test_vagrant_box_image_1.1.1_1.img in storage pool default.')
+ expect(volumes).to receive(:create).with(
+ hash_including(
+ :name => "test_vagrant_box_image_1.1.1_1.img",
+ :allocation => "10240M",
+ :capacity => "10G",
+ )
+ )
+ expect(subject).to receive(:upload_image)
+ expect(ui).to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(logger).to receive(:info).with('Creating volume test_vagrant_box_image_1.1.1_2.img in storage pool default.')
+ expect(volumes).to receive(:create).with(
+ hash_including(
+ :name => "test_vagrant_box_image_1.1.1_2.img",
+ :allocation => "20480M",
+ :capacity => "20G",
+ )
+ )
+ expect(subject).to receive(:upload_image)
+
+ expect(subject.call(env)).to be_nil
+ end
+ end
+
+ context 'when only disk 0 in storage pool' do
+ before do
+ allow(File).to receive(:exist?).and_return(true)
+ allow(File).to receive(:size).and_return(10*1024*1024*1024, 20*1024*1024*1024)
+ allow(all).to receive(:first).and_return(box_volume, nil, nil)
+ allow(box_volume).to receive(:id).and_return(1)
+ allow(subject).to receive(:upload_image).and_return(true)
+ allow(volumes).to receive(:create).and_return(fog_volume)
+ end
+
+ it 'upload disks 1 and 2 only' do
+ expect(ui).to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(logger).to receive(:info).with('Creating volume test_vagrant_box_image_1.1.1_1.img in storage pool default.')
+ expect(volumes).to receive(:create).with(hash_including(:name => "test_vagrant_box_image_1.1.1_1.img"))
+ expect(subject).to receive(:upload_image)
+ expect(ui).to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(logger).to receive(:info).with('Creating volume test_vagrant_box_image_1.1.1_2.img in storage pool default.')
+ expect(volumes).to receive(:create).with(hash_including(:name => "test_vagrant_box_image_1.1.1_2.img"))
+ expect(subject).to receive(:upload_image)
+
+ expect(subject.call(env)).to be_nil
+ end
+ end
+
+ context 'when has all disks on storage pool' do
+ before do
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(1)
+ end
+
+ it 'should skip disk upload' do
+ expect(ui).not_to receive(:info).with('Uploading base box image as volume into Libvirt storage...')
+ expect(volumes).not_to receive(:create)
+ expect(subject).not_to receive(:upload_image)
+ expect(subject.call(env)).to be_nil
+ end
+ end
+ end
+
+ context 'when wrong box format in metadata.json' do
+ before do
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(1)
+ allow(env[:machine]).to receive_message_chain("box.name") { 'test' }
+ allow(env[:machine]).to receive_message_chain("box.version") { '1.1.1' }
+ allow(env[:machine]).to receive_message_chain("box.metadata") { Hash[
+ 'virtual_size'=> 5,
+ 'format' => 'wrongFormat'
+ ]
+ }
+ allow(env[:machine]).to receive_message_chain("box.directory.join") do |arg|
+ '/test/'.concat(arg.to_s)
+ end
+ end
+
+ it 'should raise WrongBoxFormatSet exception' do
+ expect{ subject.call(env) }.to raise_error(VagrantPlugins::ProviderLibvirt::Errors::WrongBoxFormatSet)
+ end
+
+ end
+
+ context 'when one of a multi disk definition has wrong disk format in metadata.json' do
+ before do
+ allow(all).to receive(:first).and_return(box_volume)
+ allow(box_volume).to receive(:id).and_return(1)
+ allow(env[:machine]).to receive_message_chain("box.name") { 'test' }
+ allow(env[:machine]).to receive_message_chain("box.version") { '1.1.1' }
+ allow(env[:machine]).to receive_message_chain("box.metadata") {
+ Hash[
+ 'disks' => [
+ {
+ 'name'=>'send_box_name.img',
+ 'virtual_size'=> 5,
+ 'format'=> 'wrongFormat'
+ },
+ {
+ 'path' => 'disk.qcow2',
+ 'virtual_size'=> 10
+ },
+ {
+ 'virtual_size'=> 20,
+ },
+ ],
+ 'format' => 'qcow2',
+ ]
+ }
+ allow(env[:machine]).to receive_message_chain("box.directory.join") do |arg|
+ '/test/'.concat(arg.to_s)
+ end
+ end
+
+ it 'should raise WrongDiskFormatSet exception' do
+ expect { subject.call(env) }.to raise_error(VagrantPlugins::ProviderLibvirt::Errors::WrongDiskFormatSet)
+ end
+ end
+
+ end
+end
diff --git a/spec/unit/templates/domain_all_settings.xml b/spec/unit/templates/domain_all_settings.xml
index fd52809..69eb0a6 100644
--- a/spec/unit/templates/domain_all_settings.xml
+++ b/spec/unit/templates/domain_all_settings.xml
@@ -47,6 +47,11 @@
+
+
+
+
+
diff --git a/spec/unit/templates/domain_spec.rb b/spec/unit/templates/domain_spec.rb
index 87b4578..51aaf61 100644
--- a/spec/unit/templates/domain_spec.rb
+++ b/spec/unit/templates/domain_spec.rb
@@ -9,6 +9,13 @@ describe 'templates/domain' do
class DomainTemplateHelper < VagrantPlugins::ProviderLibvirt::Config
include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
+ attr_accessor :domain_volumes
+
+ def initialize
+ super
+ @domain_volumes = []
+ end
+
def finalize!
super
end
@@ -44,11 +51,22 @@ describe 'templates/domain' do
domain.boot('cdrom')
domain.boot('hd')
domain.emulator_path = '/usr/bin/kvm-spice'
- domain.instance_variable_set('@domain_volume_path', '/var/lib/libvirt/images/test.qcow2')
domain.instance_variable_set('@domain_volume_cache', 'deprecated')
domain.disk_bus = 'ide'
domain.disk_device = 'vda'
domain.disk_driver(:cache => 'unsafe', :io => 'threads', :copy_on_read => 'on', :discard => 'unmap', :detect_zeroes => 'on')
+ domain.domain_volumes.push({
+ :dev => 1.vdev.to_s,
+ :cache => 'unsafe',
+ :bus => domain.disk_bus,
+ :path => '/var/lib/libvirt/images/test.qcow2'
+ })
+ domain.domain_volumes.push({
+ :dev => 2.vdev.to_s,
+ :cache => 'unsafe',
+ :bus => domain.disk_bus,
+ :path => '/var/lib/libvirt/images/test2.qcow2'
+ })
domain.storage(:file, path: 'test-disk1.qcow2')
domain.storage(:file, path: 'test-disk2.qcow2', io: 'threads', copy_on_read: 'on', discard: 'unmap', detect_zeroes: 'on')
domain.disks.each do |disk|
diff --git a/tests/runtests.bats b/tests/runtests.bats
index 0532b2e..fd355e5 100644
--- a/tests/runtests.bats
+++ b/tests/runtests.bats
@@ -6,6 +6,7 @@ VAGRANT_OPT="--provider=libvirt"
TEMPDIR=
+
setup_file() {
# set VAGRANT_HOME to something else to reuse for tests to avoid clashes with
# user installed plugins when running tests locally.
@@ -77,6 +78,19 @@ cleanup() {
cleanup
}
+@test "bring up with two disks" {
+ export VAGRANT_CWD=tests/two_disks
+ cleanup
+ run ${VAGRANT_CMD} up ${VAGRANT_OPT}
+ echo "${output}"
+ echo "status = ${status}"
+ [ "$status" -eq 0 ]
+ echo "${output}"
+ [ $(expr "$output" : ".*Image.*2G") -ne 0 ]
+ [ $(expr "$output" : ".*Image.*10G") -ne 0 ]
+ cleanup
+}
+
@test "bring up with adjusted memory settings" {
export VAGRANT_CWD=tests/memory
cleanup
diff --git a/tests/two_disks/Vagrantfile b/tests/two_disks/Vagrantfile
new file mode 100644
index 0000000..8b2a36a
--- /dev/null
+++ b/tests/two_disks/Vagrantfile
@@ -0,0 +1,10 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure("2") do |config|
+ config.vm.box = "yamatoRT/tinycore-two-disks"
+ config.vm.box_version = "0.0.2"
+ config.ssh.shell = "/bin/sh"
+ config.vm.synced_folder ".", "/vagrant", disabled: true
+
+end
diff --git a/tools/create_box_with_two_disks.sh b/tools/create_box_with_two_disks.sh
new file mode 100755
index 0000000..149a550
--- /dev/null
+++ b/tools/create_box_with_two_disks.sh
@@ -0,0 +1,32 @@
+
+VAGRANT_HOME=${1:-$HOME/.vagrant.d/}
+VAGRANT_CMD=${2:-vagrant}
+
+echo 'Create box with two disks'
+${VAGRANT_CMD} box list
+if [ "$(${VAGRANT_CMD} box list | grep -c -E '^infernix/tinycore-two-disks\s')" -eq 0 ]
+then
+ if [ "$(${VAGRANT_CMD} box list | grep -c -E '^infernix/tinycore\s')" -eq 0 ]
+ then
+ ${VAGRANT_CMD} box add infernix/tinycore
+ fi
+ NEW_PATH="${VAGRANT_HOME}/boxes/infernix-VAGRANTSLASH-tinycore-two-disks"
+ cp -r "${VAGRANT_HOME}/boxes/infernix-VAGRANTSLASH-tinycore" "${NEW_PATH}"
+ BOX_VERSION="$(${VAGRANT_CMD} box list --machine-readable | grep -A 10 infernix/tinycore-two-disks | grep box-version | cut -d, -f4)"
+ qemu-img create -f qcow2 "${NEW_PATH}/${BOX_VERSION}/libvirt/disk2.qcow2" 10G
+ cat > "${NEW_PATH}/${BOX_VERSION}/libvirt/metadata.json" <