From 00cd79aa35c16e7e045dd04d257aac118df713ff Mon Sep 17 00:00:00 2001 From: Richard Turc <> Date: Wed, 9 Jun 2021 22:16:15 +0200 Subject: [PATCH] Use qemu-img json output and compute virtual size #1308 --- lib/vagrant-libvirt/action/create_domain.rb | 2 +- .../action/create_domain_volume.rb | 4 +- .../action/handle_box_image.rb | 26 ++-- lib/vagrant-libvirt/util/byte_number.rb | 71 +++++++++++ spec/unit/action/create_domain_volume_spec.rb | 10 +- .../one_disk_in_storage.xml | 2 +- .../three_disks_in_storage_disk_0.xml | 2 +- .../three_disks_in_storage_disk_1.xml | 2 +- .../three_disks_in_storage_disk_2.xml | 2 +- spec/unit/action/handle_box_image_spec.rb | 118 +++++++++++++++--- spec/unit/util/byte_number_spec.rb | 26 ++++ tests/runtests.bats | 2 +- 12 files changed, 224 insertions(+), 43 deletions(-) create mode 100644 lib/vagrant-libvirt/util/byte_number.rb create mode 100644 spec/unit/util/byte_number_spec.rb diff --git a/lib/vagrant-libvirt/action/create_domain.rb b/lib/vagrant-libvirt/action/create_domain.rb index 27f4e6d..0e68f6b 100644 --- a/lib/vagrant-libvirt/action/create_domain.rb +++ b/lib/vagrant-libvirt/action/create_domain.rb @@ -266,7 +266,7 @@ module VagrantPlugins end env[:ui].info(" -- Storage pool: #{@storage_pool_name}") @domain_volumes.each do |volume| - env[:ui].info(" -- Image(#{volume[:device]}): #{volume[:path]}, #{volume[:virtual_size]}G") + env[:ui].info(" -- Image(#{volume[:device]}): #{volume[:path]}, #{volume[:virtual_size].to_GB}G") end if not @disk_driver_opts.empty? diff --git a/lib/vagrant-libvirt/action/create_domain_volume.rb b/lib/vagrant-libvirt/action/create_domain_volume.rb index 49831b2..d4b8692 100644 --- a/lib/vagrant-libvirt/action/create_domain_volume.rb +++ b/lib/vagrant-libvirt/action/create_domain_volume.rb @@ -39,7 +39,7 @@ module VagrantPlugins @backing_file = box_volume.path # Virtual size of image. Take value worked out by HandleBoxImage - @capacity = env[:box_volumes][index][:virtual_size] # G + @capacity = env[:box_volumes][index][:virtual_size].to_B # Byte # Create new volume from xml template. Fog currently doesn't support # volume snapshots directly. @@ -47,7 +47,7 @@ module VagrantPlugins xml = Nokogiri::XML::Builder.new do |xml| xml.volume do xml.name(@name) - xml.capacity(@capacity, unit: 'G') + xml.capacity(@capacity, unit: 'B') xml.target do xml.format(type: 'qcow2') xml.permissions do diff --git a/lib/vagrant-libvirt/action/handle_box_image.rb b/lib/vagrant-libvirt/action/handle_box_image.rb index 4b6bc41..cc5b57a 100644 --- a/lib/vagrant-libvirt/action/handle_box_image.rb +++ b/lib/vagrant-libvirt/action/handle_box_image.rb @@ -1,5 +1,8 @@ require 'log4r' require 'open3' +require 'json' + +require 'vagrant-libvirt/util/byte_number' module VagrantPlugins module ProviderLibvirt @@ -65,7 +68,7 @@ module VagrantPlugins { :path => image_path, :name => volume_name, - :virtual_size => virtual_size.to_i, + :virtual_size => virtual_size, :format => HandleBoxImage.verify_box_format(format) } } @@ -81,16 +84,18 @@ module VagrantPlugins # 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 + config_machine_virtual_size = ByteNumber.from_GB(config.machine_virtual_size) + puts config_machine_virtual_size < box_virtual_size + if config_machine_virtual_size < box_virtual_size # Warn that a virtual size less than the box metadata size # is not supported and will be ignored env[:ui].warn I18n.t( 'vagrant_libvirt.warnings.ignoring_virtual_size_too_small', - requested: config.machine_virtual_size, minimum: box_virtual_size + requested: config_machine_virtual_size.to_GB, minimum: box_virtual_size.to_GB ) else env[:ui].info I18n.t('vagrant_libvirt.manual_resize_required') - box_virtual_size = config.machine_virtual_size + box_virtual_size = config_machine_virtual_size end end # save for use by later actions @@ -131,7 +136,7 @@ module VagrantPlugins # 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 + return ByteNumber.from_GB(box_virtual_size) end def self.get_box_image_path(box, box_name) @@ -153,14 +158,14 @@ module VagrantPlugins end def self.get_box_disk_settings(image_path) - stdout, stderr, status = Open3.capture3('qemu-img', 'info', image_path) + stdout, stderr, status = Open3.capture3('qemu-img', 'info', '--output=json', image_path) if !status.success? raise Errors::BadBoxImage, image: image_path, out: stdout, err: stderr end - image_info_lines = stdout.split("\n") - format = image_info_lines.find { |l| l.start_with?('file format:') }.split(' ')[2] - virtual_size = image_info_lines.find { |l| l.start_with?('virtual size:') }.split(' ')[2] + image_info = JSON.parse(stdout) + format = image_info['format'] + virtual_size = ByteNumber.new(image_info['virtual-size']) return format, virtual_size end @@ -178,12 +183,11 @@ module VagrantPlugins 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", + capacity: "#{box_volume[:virtual_size].to_B}B", format_type: box_volume[:format], owner: storage_uid(env), group: storage_gid(env), diff --git a/lib/vagrant-libvirt/util/byte_number.rb b/lib/vagrant-libvirt/util/byte_number.rb new file mode 100644 index 0000000..0612ddc --- /dev/null +++ b/lib/vagrant-libvirt/util/byte_number.rb @@ -0,0 +1,71 @@ +class ByteNumber < Numeric + def initialize(int) + @int = int + end + + def to_s + @int.to_s + end + + def to_i + @int + end + + def to_f + @int.to_f + end + + def to_B + to_i + end + + def to_KB + _compute_unit_to_n_kilo(1) + end + + def to_MB + _compute_unit_to_n_kilo(2) + end + + def to_GB + _compute_unit_to_n_kilo(3) + end + + def coerce(other) + to_i.coerce(other) + end + + def <=>(other) + to_i <=> other + end + + def +(other) + to_i + other + end + + def -(other) + to_i - other + end + + def *(other) + to_i * other + end + + def /(other) + to_i / other + end + + def pow(n) + self.class.new(to_i ** n) + end + + def self.from_GB(value) + self.new(value*(1024**3)) + end + + private + def _compute_unit_to_n_kilo(n=0) + (to_f/(1024 ** n)).ceil + end +end + \ No newline at end of file diff --git a/spec/unit/action/create_domain_volume_spec.rb b/spec/unit/action/create_domain_volume_spec.rb index 716eaa1..bc45c21 100644 --- a/spec/unit/action/create_domain_volume_spec.rb +++ b/spec/unit/action/create_domain_volume_spec.rb @@ -3,6 +3,8 @@ require 'support/sharedcontext' require 'support/libvirt_context' require 'vagrant-libvirt/action/destroy_domain' +require 'vagrant-libvirt/util/byte_number' + describe VagrantPlugins::ProviderLibvirt::Action::CreateDomainVolume do subject { described_class.new(app, env) } @@ -38,7 +40,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::CreateDomainVolume do env[:box_volumes] = [ { :name=>"test_vagrant_box_image_1.1.1_0.img", - :virtual_size=>5 + :virtual_size=>ByteNumber.new(5368709120) } ] end @@ -65,15 +67,15 @@ describe VagrantPlugins::ProviderLibvirt::Action::CreateDomainVolume do env[:box_volumes] = [ { :name=>"test_vagrant_box_image_1.1.1_0.img", - :virtual_size=>5 + :virtual_size=>ByteNumber.new(5368709120) }, { :name=>"test_vagrant_box_image_1.1.1_1.img", - :virtual_size=>10 + :virtual_size=>ByteNumber.new(10737423360) }, { :name=>"test_vagrant_box_image_1.1.1_2.img", - :virtual_size=>20 + :virtual_size=>ByteNumber.new(21474836480) } ] 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 index f66c343..f4166ee 100644 --- 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 @@ -1,6 +1,6 @@ test.img - 5 + 5368709120 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 index f66c343..f4166ee 100644 --- 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 @@ -1,6 +1,6 @@ test.img - 5 + 5368709120 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 index ecd4dc3..af2e41c 100644 --- 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 @@ -1,6 +1,6 @@ test_1.img - 10 + 10737423360 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 index 1f39376..0627f21 100644 --- 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 @@ -1,6 +1,6 @@ test_2.img - 20 + 21474836480 diff --git a/spec/unit/action/handle_box_image_spec.rb b/spec/unit/action/handle_box_image_spec.rb index 6a8b2da..61d4f4f 100644 --- a/spec/unit/action/handle_box_image_spec.rb +++ b/spec/unit/action/handle_box_image_spec.rb @@ -1,8 +1,11 @@ require 'spec_helper' +require 'json' require 'support/sharedcontext' require 'support/libvirt_context' require 'vagrant-libvirt/action/destroy_domain' +require 'vagrant-libvirt/util/byte_number' + describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do subject { described_class.new(app, env) } @@ -15,6 +18,41 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do let(:all) { double('all') } let(:box_volume) { double('box_volume') } let(:fog_volume) { double('fog_volume') } + let(:config) { double('config') } + + qemu_json_return_5G = JSON.dump({ + "virtual-size": 5368709120, + "filename": "/test/box.img", + "cluster-size": 65536, + "format": "qcow2", + "actual-size": 655360, + "dirty-flag": false + }) + byte_number_5G = ByteNumber.new(5368709120) + + + qemu_json_return_10G = JSON.dump({ + "virtual-size": 10737423360, + "filename": "/test/disk.qcow2", + "cluster-size": 65536, + "format": "qcow2", + "actual-size": 655360, + "dirty-flag": false + }) + byte_number_10G = ByteNumber.new(10737423360) + + qemu_json_return_20G = JSON.dump({ + "virtual-size": 21474836480, + "filename": "/test/box_2.img", + "cluster-size": 65536, + "format": "qcow2", + "actual-size": 1508708352, + "dirty-flag": false + }) + byte_number_20G = ByteNumber.new(21474836480) + + + describe '#call' do before do @@ -51,13 +89,53 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do { :path=>"/test/box.img", :name=>"test_vagrant_box_image_1.1.1_box.img", - :virtual_size=>5, + :virtual_size=>byte_number_5G, :format=>"qcow2" } ] ) end + context 'When config.machine_virtual_size is set and smaller than box_virtual_size' do + before do + allow(env[:machine]).to receive_message_chain("provider_config.machine_virtual_size").and_return(1) + end + it 'should warning must be raise' do + expect(ui).to receive(:warn).with("Ignoring requested virtual disk size of '1' as it is below\nthe minimum box image size of '5'.") + expect(subject.call(env)).to be_nil + expect(env[:box_volumes]).to eq( + [ + { + :path=>"/test/box.img", + :name=>"test_vagrant_box_image_1.1.1_box.img", + :virtual_size=>byte_number_5G, + :format=>"qcow2" + } + ] + ) + end + end + + context 'When config.machine_virtual_size is set and higher than box_virtual_size' do + before do + allow(env[:machine]).to receive_message_chain("provider_config.machine_virtual_size").and_return(20) + end + it 'should be use' do + expect(ui).to receive(:info).with("Created volume larger than box defaults, will require manual resizing of\nfilesystems to utilize.") + expect(subject.call(env)).to be_nil + expect(env[:box_volumes]).to eq( + [ + { + :path=>"/test/box.img", + :name=>"test_vagrant_box_image_1.1.1_box.img", + :virtual_size=>byte_number_20G, + :format=>"qcow2" + } + ] + ) + end + end + context 'when disk image not in storage pool' do before do allow(File).to receive(:exist?).and_return(true) @@ -74,7 +152,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do hash_including( :name => "test_vagrant_box_image_1.1.1_box.img", :allocation => "5120M", - :capacity => "5G", + :capacity => "5368709120B", ) ) expect(subject).to receive(:upload_image) @@ -122,14 +200,14 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do '/test/'.concat(arg.to_s) end allow(status).to receive(:success?).and_return(true) - allow(Open3).to receive(:capture3).with('qemu-img', 'info', '/test/box.img').and_return([ - "image: /test/box.img\nfile format: qcow2\nvirtual size: 5 GiB (5368709120 bytes)\ndisk size: 1.45 GiB\n", "", status + allow(Open3).to receive(:capture3).with('qemu-img', 'info', '--output=json', '/test/box.img').and_return([ + qemu_json_return_5G, "", status ]) - allow(Open3).to receive(:capture3).with('qemu-img', 'info', '/test/disk.qcow2').and_return([ - "image: /test/disk.qcow2\nfile format: qcow2\nvirtual size: 10 GiB (10737418240 bytes)\ndisk size: 1.45 GiB\n", "", status + allow(Open3).to receive(:capture3).with('qemu-img', 'info', '--output=json', '/test/disk.qcow2').and_return([ + qemu_json_return_10G, "", status ]) - allow(Open3).to receive(:capture3).with('qemu-img', 'info', '/test/box_2.img').and_return([ - "image: /test/box_2.img\nfile format: qcow2\nvirtual size: 20 GiB (21474836480 bytes)\ndisk size: 1.45 GiB\n", "", status + allow(Open3).to receive(:capture3).with('qemu-img', 'info', '--output=json', '/test/box_2.img').and_return([ + qemu_json_return_20G, "", status ]) end @@ -141,19 +219,19 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do { :path=>"/test/box.img", :name=>"test_vagrant_box_image_1.1.1_send_box_name.img", - :virtual_size=>5, + :virtual_size=>byte_number_5G, :format=>"qcow2" }, { :path=>"/test/disk.qcow2", :name=>"test_vagrant_box_image_1.1.1_disk.img", - :virtual_size=>10, + :virtual_size=>byte_number_10G, :format=>"qcow2" }, { :path=>"/test/box_2.img", :name=>"test_vagrant_box_image_1.1.1_box_2.img", - :virtual_size=>20, + :virtual_size=>byte_number_20G, :format=>"qcow2" } ] @@ -176,7 +254,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do hash_including( :name => "test_vagrant_box_image_1.1.1_send_box_name.img", :allocation => "5120M", - :capacity => "5G", + :capacity => "5368709120B", ) ) expect(subject).to receive(:upload_image) @@ -186,7 +264,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do hash_including( :name => "test_vagrant_box_image_1.1.1_disk.img", :allocation => "10240M", - :capacity => "10G", + :capacity => "10737423360B", ) ) expect(subject).to receive(:upload_image) @@ -196,7 +274,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do hash_including( :name => "test_vagrant_box_image_1.1.1_box_2.img", :allocation => "20480M", - :capacity => "20G", + :capacity => "21474836480B", ) ) expect(subject).to receive(:upload_image) @@ -279,14 +357,14 @@ describe VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage do '/test/'.concat(arg.to_s) end allow(status).to receive(:success?).and_return(true) - allow(Open3).to receive(:capture3).with('qemu-img', 'info', '/test/box.img').and_return([ - "image: /test/box.img\nfile format: qcow2\nvirtual size: 5 GiB (5368709120 bytes)\ndisk size: 1.45 GiB\n", "", status + allow(Open3).to receive(:capture3).with('qemu-img', 'info', "--output=json", '/test/box.img').and_return([ + qemu_json_return_5G, "", status ]) - allow(Open3).to receive(:capture3).with('qemu-img', 'info', '/test/disk.qcow2').and_return([ - "image: /test/disk.qcow2\nfile format: qcow2\nvirtual size: 10 GiB (10737418240 bytes)\ndisk size: 1.45 GiB\n", "", status + allow(Open3).to receive(:capture3).with('qemu-img', 'info', "--output=json", '/test/disk.qcow2').and_return([ + qemu_json_return_10G, "", status ]) - allow(Open3).to receive(:capture3).with('qemu-img', 'info', '/test/box_2.img').and_return([ - "image: /test/box_2.img\nfile format: qcow2\nvirtual size: 20 GiB (21474836480 bytes)\ndisk size: 1.45 GiB\n", "", status + allow(Open3).to receive(:capture3).with('qemu-img', 'info', "--output=json", '/test/box_2.img').and_return([ + qemu_json_return_20G, "", status ]) end diff --git a/spec/unit/util/byte_number_spec.rb b/spec/unit/util/byte_number_spec.rb new file mode 100644 index 0000000..7f016ab --- /dev/null +++ b/spec/unit/util/byte_number_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +require 'vagrant-libvirt/util/byte_number' + + +describe ByteNumber do + describe '#ByteNumber to Gigrabyte' do + it 'should return bigger size' do + expect( ByteNumber.new("10737423360").to_GB).to eq(11) + expect( ByteNumber.new("737423360").to_GB).to eq(1) + expect( ByteNumber.new("110737423360").to_GB).to eq(104) + end + end + + describe '#ByteNumber from Gigrabyte' do + it 'should convert' do + expect( ByteNumber.from_GB(5).to_i).to eq(5368709120) + end + end + + describe '#ByteNumber pow' do + it 'should be work like interger' do + expect( ByteNumber.new(5).pow(5).to_i).to eq(5**5) + end + end +end \ No newline at end of file diff --git a/tests/runtests.bats b/tests/runtests.bats index 13b84d7..d633215 100644 --- a/tests/runtests.bats +++ b/tests/runtests.bats @@ -1,7 +1,7 @@ SCRIPT_DIR="$( cd "$BATS_TEST_DIRNAME" &> /dev/null && pwd )" export PATH=$(dirname ${SCRIPT_DIR})/bin:${PATH} -VAGRANT_CMD=vagrant +VAGRANT_CMD="vagrant" VAGRANT_OPT="--provider=libvirt" TEMPDIR=