Skip to content

Commit

Permalink
B #6206: Recreate bitmaps during cleanup in backup reset
Browse files Browse the repository at this point in the history
Libvirt does not recreate bitmaps present in qcow2 disk images. This
prevents the proper cleaning of bitmaps during a poweron cycle. This
patch redefines all backups not present in libvirt so they are cleaned
  • Loading branch information
rsmontero committed May 10, 2023
1 parent b3a5c46 commit 7471016
Showing 1 changed file with 96 additions and 53 deletions.
149 changes: 96 additions & 53 deletions src/tm_mad/lib/backup_qcow2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -422,18 +422,10 @@ def fsthaw
end

#---------------------------------------------------------------------------
# Re-define the parent_id checkpoint if not included in the checkpoint-list.
# If the checkpoint is not present in storage the methods will fail.
#
# @param[String] List of disks to include in the checkpoint
# @param[Integer] id of the checkpoint to define
# List the checkpoints defined in the domain
# @return[Array] an array of checkpint ids
#---------------------------------------------------------------------------
def define_checkpoint(disks_s)
return if @parent_id == -1

#-----------------------------------------------------------------------
# Check if the parent_id checkpoint is already defined for this domain
#-----------------------------------------------------------------------
def checkpoints
out = cmd("#{virsh} checkpoint-list", @dom, :name => '')
out.strip!

Expand All @@ -446,22 +438,38 @@ def define_checkpoint(disks_s)
check_ids << m[2].to_i
end

# Remove current checkpoint (e.g. a previous failed backup operation)
if check_ids.include? @backup_id
cpname = "one-#{@vid}-#{@backup_id}"
check_ids
end

begin
cmd("#{virsh} checkpoint-delete", @dom, :checkpointname => cpname)
rescue StandardError
cmd("#{virsh} checkpoint-delete", @dom,
:checkpointname => cpname, :metadata => '')
end
#---------------------------------------------------------------------------
# Re-define the checkpoints. This method will fail if not present in storage
#
# @param[Array] Array of checkpoint ids to re-define
# @param[String] List of disks to include in the checkpoint
#---------------------------------------------------------------------------
def redefine_checkpoints(check_ids, disks_s)
disks = disks_s.split ':'
tgts = []

return if check_ids.empty? || disks.empty?

# Create XML definition for the VM disks
@vm.elements.each 'TEMPLATE/DISK' do |d|
did = d.elements['DISK_ID'].text

next unless disks.include? did

tgts << d.elements['TARGET'].text
end

return if check_ids.include? @parent_id
return if tgts.empty?

disks = '<disks>'
tgts.each {|tgt| disks << "<disk name='#{tgt}'/>" }
disks << '</disks>'

#-----------------------------------------------------------------------
# Try to re-define checkpoint, will fail if not present in storage.
# Try to re-define checkpoints, will fail if not present in storage.
# Can be queried using qemu-monitor
#
# out = cmd("#{virsh} qemu-monitor-command", @dom,
Expand All @@ -472,54 +480,89 @@ def define_checkpoint(disks_s)
# => [{"name"=>"one-0-2", "recording"=>true, "persistent"=>true,
# "busy"=>false, "granularity"=>65536, "count"=>327680}]
#-----------------------------------------------------------------------
disks = disks_s.split ':'
tgts = []
check_ids.each do |cid|
checkpoint_xml = <<~EOS
<domaincheckpoint>
<name>one-#{@vid}-#{cid}</name>
<creationTime>#{Time.now.to_i}</creationTime>
#{disks}
</domaincheckpoint>
EOS

@vm.elements.each 'TEMPLATE/DISK' do |d|
did = d.elements['DISK_ID'].text
cpath = "#{@tmp_dir}/checkpoint.xml"

next unless disks.include? did
File.open(cpath, 'w') {|f| f.write(checkpoint_xml) }

tgts << d.elements['TARGET'].text
cmd("#{virsh} checkpoint-create", @dom, :xmlfile => cpath,
:redefine => '')
end
end

return if tgts.empty?
#---------------------------------------------------------------------------
# Re-define the parent_id checkpoint if not included in the checkpoint-list.
# If the checkpoint is not present in storage the methods will fail.
#
# @param[String] List of disks to include in the checkpoint
# @param[Integer] id of the checkpoint to define
#---------------------------------------------------------------------------
def define_parent(disks_s)
return if @parent_id == -1

disks = '<disks>'
tgts.each {|tgt| disks << "<disk name='#{tgt}'/>" }
disks << '</disks>'
check_ids = checkpoints

checkpoint_xml = <<~EOS
<domaincheckpoint>
<name>one-#{@vid}-#{@parent_id}</name>
<creationTime>#{Time.now.to_i}</creationTime>
#{disks}
</domaincheckpoint>
EOS
# Remove current checkpoint (e.g. a previous failed backup operation)
if check_ids.include? @backup_id
cpname = "one-#{@vid}-#{@backup_id}"

cpath = "#{@tmp_dir}/checkpoint.xml"
begin
cmd("#{virsh} checkpoint-delete", @dom, :checkpointname => cpname)
rescue StandardError
cmd("#{virsh} checkpoint-delete", @dom, :checkpointname => cpname,
:metadata => '')
end
end

File.open(cpath, 'w') {|f| f.write(checkpoint_xml) }
return if check_ids.include? @parent_id

cmd("#{virsh} checkpoint-create", @dom,
:xmlfile => cpath, :redefine => '')
redefine_checkpoints([@parent_id], disks_s)
end

#---------------------------------------------------------------------------
# Cleans defined checkpoints up to the last two. This way we can retry
# the backup operation in case it fails
#---------------------------------------------------------------------------
def clean_checkpoints(all = false)
def clean_checkpoints(disks_s, all = false)
return unless @checkpoint

out = cmd("#{virsh} checkpoint-list", @dom, :name => '')
out.strip!
# Get a list of dirty checkpoints
check_ids = checkpoints

out.each_line do |l|
m = l.match(/(one-[0-9]+)-([0-9]+)/)
next if !m || (!all && m[2].to_i >= @parent_id)
# Use first disk to get a list of defined bitmaps
to_define = []

disk_id = disks_s.split(':').first

idisk = QemuImg.new("#{@vm_dir}/disk.#{disk_id}")

idisk.bitmaps.each do |b|
m = b['name'].match(/(one-[0-9]+)-([0-9]+)/)
next unless m

cid = m[2].to_i

to_define << cid unless check_ids.include? cid
check_ids << cid
end unless idisk.bitmaps.nil?

# Redefine checkpoints in libvirt and remove
redefine_checkpoints(to_define, disks_s)

check_ids.uniq!

check_ids.each do |cid|
next if !all && cid >= @parent_id

cmd("#{virsh} checkpoint-delete", "#{@dom} #{m[1]}-#{m[2]}")
cmd("#{virsh} checkpoint-delete", "#{@dom} one-#{@vid}-#{cid}")
end
end

Expand Down Expand Up @@ -859,15 +902,15 @@ def stop_backup
#---------------------------------------------------------------------------
if live
if vm.parent_id == -1
vm.clean_checkpoints(true)
vm.clean_checkpoints(disk, true)

vm.backup_full_live(disk)
else
vm.define_checkpoint(disk)
vm.define_parent(disk)

vm.backup_nbd_live(disk)

vm.clean_checkpoints
vm.clean_checkpoints(disk)
end
else
if vm.parent_id == -1
Expand Down

0 comments on commit 7471016

Please sign in to comment.