If the record we want to delete exists in both obsoleted_records list and seen_tombstoned_records list, we try to delete them twice. Second deletion fails with the error message "Attempt to delete object did not result in a single row modification". To prevent double deletion, we deduplicate the records.
88 lines
2.9 KiB
Ruby
88 lines
2.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "../../lib/util"
|
|
|
|
class Prog::DnsZone::DnsZoneNexus < Prog::Base
|
|
subject_is :dns_zone
|
|
|
|
semaphore :refresh_dns_servers
|
|
|
|
label def wait
|
|
if dns_zone.last_purged_at < Time.now - 60 * 60 * 1 # ~1 hour
|
|
register_deadline(:wait, 5 * 60)
|
|
hop_purge_dns_records
|
|
end
|
|
|
|
when_refresh_dns_servers_set? do
|
|
register_deadline(:wait, 5 * 60)
|
|
hop_refresh_dns_servers
|
|
end
|
|
|
|
nap 10
|
|
end
|
|
|
|
label def refresh_dns_servers
|
|
decr_refresh_dns_servers
|
|
|
|
dns_zone.dns_servers.each do |dns_server|
|
|
records_to_rectify = dns_zone.records_dataset
|
|
.left_join(:seen_dns_records_by_dns_servers, dns_record_id: :id, dns_server_id: dns_server.id)
|
|
.where(Sequel[:seen_dns_records_by_dns_servers][:dns_record_id] => nil)
|
|
.order(Sequel.asc(:created_at)).all
|
|
|
|
next if records_to_rectify.empty?
|
|
|
|
commands = ["zone-abort #{dns_zone.name}", "zone-begin #{dns_zone.name}"]
|
|
records_to_rectify.each do |r|
|
|
commands << if r.tombstoned
|
|
"zone-unset #{dns_zone.name} #{r.name} #{r.ttl} #{r.type} #{r.data}"
|
|
else
|
|
"zone-set #{dns_zone.name} #{r.name} #{r.ttl} #{r.type} #{r.data}"
|
|
end
|
|
end
|
|
commands << "zone-commit #{dns_zone.name}"
|
|
|
|
dns_server.run_commands_on_all_vms(commands)
|
|
|
|
DB[:seen_dns_records_by_dns_servers].multi_insert(records_to_rectify.map { {dns_record_id: _1.id, dns_server_id: dns_server.id} })
|
|
end
|
|
|
|
hop_wait
|
|
end
|
|
|
|
label def purge_dns_records
|
|
# These are the records that are obsoleted by a another record with the
|
|
# same fields but newer date. We can delete them even if they are not
|
|
# seen yet.
|
|
obsoleted_records = dns_zone.records_dataset
|
|
.join(
|
|
dns_zone.records_dataset
|
|
.select_group(:dns_zone_id, :name, :type, :data)
|
|
.select_append { max(created_at).as(:latest_created_at) }
|
|
.as(:latest_dns_record),
|
|
[:dns_zone_id, :name, :type, :data]
|
|
)
|
|
.where { dns_record[:created_at] < latest_dns_record[:latest_created_at] }.all
|
|
|
|
# These are the tombstoned records, we can only delete them if they are
|
|
# seen by all DNS servers. We join with seen_dns_records_by_dns_servers
|
|
# and count the number of DNS servers to ensure that they are seen by all
|
|
# DNS servers.
|
|
dns_server_ids = dns_zone.dns_servers.map(&:id)
|
|
seen_tombstoned_records = dns_zone.records_dataset
|
|
.select_group(:id)
|
|
.join(:seen_dns_records_by_dns_servers, dns_record_id: :id, dns_server_id: dns_server_ids)
|
|
.where(tombstoned: true)
|
|
.having { count.function.* =~ dns_server_ids.count }.all
|
|
|
|
records_to_purge = obsoleted_records + seen_tombstoned_records
|
|
DB[:seen_dns_records_by_dns_servers].where(dns_record_id: records_to_purge.map(&:id).uniq).delete(force: true)
|
|
records_to_purge.uniq(&:id).map(&:destroy)
|
|
|
|
dns_zone.last_purged_at = Time.now
|
|
dns_zone.save_changes
|
|
|
|
hop_wait
|
|
end
|
|
end
|