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