ManageIQ/manageiq

View on GitHub
tools/cleanup_duplicate_host_guest_devices.rb

Summary

Maintainability
A
0 mins
Test Coverage
#!/usr/bin/env ruby
require File.expand_path('../config/environment', __dir__)
require "optimist"

opts = Optimist.options do
  opt :ems_id, "The ID of the ExtManagementSystem to reconnect VMs for", :type => :integer
  opt :ems_name, "The name of the ExtManagementSystem to reconnect VMs for", :type => :string
  opt :dry_run, "Just print out what would be done without modifying anything", :type => :boolean, :default => true
  opt :page_size, "How many records to delete at a time", :type => :integer, :default => 100
end

if opts[:ems_id].nil? && opts[:ems_name].nil?
  Optimist.die :ems_id, "Must pass either --ems-id or --ems-name"
end

ems = if opts[:ems_id].present?
        ExtManagementSystem.find_by(:id => opts[:ems_id])
      else
        ExtManagementSystem.find_by(:name => opts[:ems_name])
      end

if ems.nil?
  print "Failed to find EMS [#{opts.fetch(:ems_id) { opts[:ems_name] }}]"
  exit
end

# Find all duplicate guest devices (having the same hardware_id + uid_ems)
duplicate_guest_devices = GuestDevice.where(:hardware => ems.host_hardwares)
                                     .group_by { |guest_device| [guest_device.hardware_id, guest_device.uid_ems] }
                                     .select   { |(_hardware_id, _uid_ems), guest_devices| guest_devices.count > 1 }

guest_devices_to_delete = duplicate_guest_devices.flat_map do |(_hardware_id, _uid_ems), guest_devices|
  # Pick the oldest guest_device to keep
  guest_devices.sort_by(&:id)[1..-1].map(&:id)
end

puts "Found #{guest_devices_to_delete.count} duplicate Guest Devices..."

if opts[:dry_run]
  puts "**** This is a dry-run, nothing will be updated! ****"
else
  puts "**** THIS WILL MODIFY YOUR DATABASE ****"
  puts "     Press Enter to Continue: "
  STDIN.getc
end

return if opts[:dry_run]

associations = %i[has_one has_many].flat_map { |assoc| GuestDevice.reflect_on_all_associations(assoc) }
dependents   = associations.select { |assoc| assoc.options[:dependent].present? }

total_slices = (guest_devices_to_delete.count / opts[:page_size].to_f).ceil
guest_devices_to_delete.each_slice(opts[:page_size]).with_index do |slice, index|
  puts "Destroying slice #{index + 1} of #{total_slices}..."
  GuestDevice.delete(slice)

  dependents.each do |assoc|
    delete_meth = assoc.options[:dependent]
    foreign_key = assoc.join_primary_key

    if %i[delete destroy].include?(delete_meth)
      assoc.klass.where(foreign_key => slice).send(:"#{delete_meth}_all")
    elsif delete_meth == :nullify
      assoc.klass.where(foreign_key => slice).update_all(foreign_key => nil)
    else
      assoc.klass.where(foreign_key => slice).each(&delete_meth)
    end
  end

  puts "Destroying slice #{index + 1} of #{total_slices}...Complete"
end

puts "Destroyed #{guest_devices_to_delete.count} duplicate Guest Devices"