dnsync 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +180 -0
- data/Rakefile +150 -0
- data/bin/dnsync +6 -0
- data/dnsync.gemspec +88 -0
- data/lib/dnsync/answer.rb +28 -0
- data/lib/dnsync/cli.rb +207 -0
- data/lib/dnsync/dnsimple.rb +65 -0
- data/lib/dnsync/http_status.rb +56 -0
- data/lib/dnsync/nsone.rb +106 -0
- data/lib/dnsync/record.rb +46 -0
- data/lib/dnsync/record_identifier.rb +34 -0
- data/lib/dnsync/recurring_zone_updater.rb +152 -0
- data/lib/dnsync/zone.rb +36 -0
- data/lib/dnsync/zone_difference.rb +52 -0
- data/lib/dnsync/zone_updater.rb +22 -0
- data/lib/dnsync.rb +4 -0
- data/test/record_identifier_test.rb +35 -0
- data/test/record_test.rb +28 -0
- data/test/test_helper.rb +5 -0
- metadata +184 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
|
5
|
+
require 'dnsync/zone'
|
6
|
+
|
7
|
+
module Dnsync
|
8
|
+
class Dnsimple
|
9
|
+
attr_reader :domain
|
10
|
+
|
11
|
+
def initialize(email, token, domain)
|
12
|
+
unless email.present?
|
13
|
+
raise ArgumentError, "email must be specified"
|
14
|
+
end
|
15
|
+
|
16
|
+
unless token.present?
|
17
|
+
raise ArgumentError, "token must be specified"
|
18
|
+
end
|
19
|
+
|
20
|
+
unless domain.present?
|
21
|
+
raise ArgumentError, "domain must be specified"
|
22
|
+
end
|
23
|
+
|
24
|
+
@email = email
|
25
|
+
@token = token
|
26
|
+
@domain = domain
|
27
|
+
end
|
28
|
+
|
29
|
+
def connection
|
30
|
+
@connection ||= Faraday.new('https://api.dnsimple.com/v1/') do |conn|
|
31
|
+
conn.request :url_encoded # form-encode POST params
|
32
|
+
|
33
|
+
# conn.response :logger
|
34
|
+
conn.response :raise_error
|
35
|
+
conn.response :json, :content_type => /\bjson$/
|
36
|
+
|
37
|
+
conn.adapter Faraday.default_adapter
|
38
|
+
|
39
|
+
conn.headers['X-DNSimple-Token'] = "#{@email}:#{@token}"
|
40
|
+
|
41
|
+
conn.options.timeout = 5
|
42
|
+
conn.options.open_timeout = 5
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def zone
|
47
|
+
records_by_record = connection.get("domains/#{@domain}/records").body.group_by do |dnsimple_record|
|
48
|
+
[ dnsimple_record['record']['name'], dnsimple_record['record']['record_type'] ]
|
49
|
+
end
|
50
|
+
|
51
|
+
records = records_by_record.map do |(name, record_type), records|
|
52
|
+
fqdn = name.present? ? "#{name}.#{@domain}" : @domain
|
53
|
+
ttl = records.first['record']['ttl']
|
54
|
+
|
55
|
+
answers = records.map do |record|
|
56
|
+
Answer.new(record['record']['content'], record['record']['prio'])
|
57
|
+
end
|
58
|
+
|
59
|
+
Record.new(fqdn, record_type, ttl, answers)
|
60
|
+
end
|
61
|
+
|
62
|
+
Zone.new(@domain, records)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
|
3
|
+
module Dnsync
|
4
|
+
class HttpStatus
|
5
|
+
def initialize(port, updater)
|
6
|
+
@port = port
|
7
|
+
@updater = updater
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
return if @server || @thread
|
12
|
+
|
13
|
+
logger = WEBrick::Log.new
|
14
|
+
logger.level = WEBrick::Log::WARN
|
15
|
+
|
16
|
+
@server = WEBrick::HTTPServer.new(:Port => @port,
|
17
|
+
:Logger => logger, :AccessLog => [])
|
18
|
+
@server.mount_proc("/status", &method(:handler))
|
19
|
+
|
20
|
+
@thread = Thread.new do
|
21
|
+
Thread.current.abort_on_exception = true
|
22
|
+
@server.start
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop
|
29
|
+
if @server
|
30
|
+
@server.stop
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def join
|
37
|
+
if @thread
|
38
|
+
@thread.join
|
39
|
+
end
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def handler(request, response)
|
45
|
+
health_problems = @updater.health_problems
|
46
|
+
|
47
|
+
if health_problems.blank?
|
48
|
+
response.status = 200
|
49
|
+
response.body = "OK\n"
|
50
|
+
else
|
51
|
+
response.status = 500
|
52
|
+
response.body = health_problems + "\n"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/dnsync/nsone.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
module Dnsync
|
2
|
+
class Nsone
|
3
|
+
def initialize(api_key, domain)
|
4
|
+
unless api_key.present?
|
5
|
+
raise ArgumentError, "api_key must be specified"
|
6
|
+
end
|
7
|
+
|
8
|
+
unless domain.present?
|
9
|
+
raise ArgumentError, "domain must be specified"
|
10
|
+
end
|
11
|
+
|
12
|
+
@api_key = api_key
|
13
|
+
@domain = domain
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection
|
17
|
+
@connection ||= Faraday.new('https://api.nsone.net/v1/') do |conn|
|
18
|
+
conn.request :json
|
19
|
+
|
20
|
+
# conn.response :logger
|
21
|
+
conn.response :raise_error
|
22
|
+
conn.response :json, :content_type => /\bjson$/
|
23
|
+
|
24
|
+
conn.adapter Faraday.default_adapter
|
25
|
+
|
26
|
+
conn.headers['X-NSONE-Key'] = @api_key
|
27
|
+
|
28
|
+
conn.options.timeout = 5
|
29
|
+
conn.options.open_timeout = 5
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def zone
|
34
|
+
zone = connection.get("zones/#{@domain}").body
|
35
|
+
|
36
|
+
records = zone['records'].map do |record|
|
37
|
+
record_for(record['domain'], record['type'])
|
38
|
+
end
|
39
|
+
|
40
|
+
Zone.new(@domain, records)
|
41
|
+
end
|
42
|
+
|
43
|
+
def record_for(fqdn, record_type)
|
44
|
+
record = connection.get("zones/#{@domain}/#{fqdn}/#{record_type}").body
|
45
|
+
|
46
|
+
answers = record['answers'].map do |answer_record|
|
47
|
+
case answer_record['answer'].length
|
48
|
+
when 2
|
49
|
+
priority, content = *answer_record['answer']
|
50
|
+
when 1
|
51
|
+
content = answer_record['answer'].first
|
52
|
+
else
|
53
|
+
raise "Unknown answer format: #{answer_record.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
Answer.new(content, priority)
|
57
|
+
end
|
58
|
+
|
59
|
+
Record.new(record['domain'], record['type'], record['ttl'], answers)
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_record(record)
|
63
|
+
answers = record.answers.map do |answer|
|
64
|
+
if answer.priority
|
65
|
+
{ :answer => [ answer.priority, answer.content ] }
|
66
|
+
else
|
67
|
+
{ :answer => [ answer.content ] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
connection.put("zones/#{@domain}/#{record.name}/#{record.type}") do |req|
|
72
|
+
req.body = {
|
73
|
+
:type => record.type,
|
74
|
+
:zone => @domain,
|
75
|
+
:domain => record.name,
|
76
|
+
:ttl => record.ttl,
|
77
|
+
:answers => answers
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_record(record)
|
83
|
+
answers = record.answers.map do |answer|
|
84
|
+
if answer.priority
|
85
|
+
{ :answer => [ answer.priority, answer.content ] }
|
86
|
+
else
|
87
|
+
{ :answer => [ answer.content ] }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
connection.post("zones/#{@domain}/#{record.name}/#{record.type}") do |req|
|
92
|
+
req.body = {
|
93
|
+
:type => record.type,
|
94
|
+
:zone => @domain,
|
95
|
+
:domain => record.name,
|
96
|
+
:ttl => record.ttl,
|
97
|
+
:answers => answers
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def remove_record(record)
|
103
|
+
connection.delete("zones/#{@domain}/#{record.name}/#{record.type}")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
require 'dnsync/answer'
|
4
|
+
require 'dnsync/record_identifier'
|
5
|
+
|
6
|
+
module Dnsync
|
7
|
+
class Record
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
attr_reader :identifier, :ttl, :answers
|
11
|
+
|
12
|
+
def initialize(name, type, ttl, answers)
|
13
|
+
unless ttl.present?
|
14
|
+
raise ArgumentError, 'ttl must be provided'
|
15
|
+
end
|
16
|
+
|
17
|
+
unless answers.present?
|
18
|
+
raise ArgumentError, 'at least one answer must be provided'
|
19
|
+
end
|
20
|
+
|
21
|
+
@identifier = RecordIdentifier.new(name, type)
|
22
|
+
@ttl = ttl
|
23
|
+
@answers = answers.sort
|
24
|
+
|
25
|
+
freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
@identifier.name
|
30
|
+
end
|
31
|
+
|
32
|
+
def type
|
33
|
+
@identifier.type
|
34
|
+
end
|
35
|
+
|
36
|
+
def <=>(other)
|
37
|
+
[ identifier, ttl, answers ] <=> [ other.identifier, other.ttl, other.answers ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def hash
|
41
|
+
[ identifier, ttl, answers ].hash
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :eql?, :==
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module Dnsync
|
4
|
+
class RecordIdentifier
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
attr_reader :name, :type
|
8
|
+
|
9
|
+
def initialize(name, type)
|
10
|
+
unless name.present?
|
11
|
+
raise ArgumentError, 'name must be provided'
|
12
|
+
end
|
13
|
+
|
14
|
+
unless type.present?
|
15
|
+
raise ArgumentError, 'type must be provided'
|
16
|
+
end
|
17
|
+
|
18
|
+
@name = name
|
19
|
+
@type = type
|
20
|
+
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def <=>(other)
|
25
|
+
[ name, type ] <=> [ other.name, other.type ]
|
26
|
+
end
|
27
|
+
|
28
|
+
def hash
|
29
|
+
[ name, type ].hash
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :eql?, :==
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'dnsync/zone_updater'
|
2
|
+
require 'atomic'
|
3
|
+
|
4
|
+
module Dnsync
|
5
|
+
class RecurringZoneUpdater
|
6
|
+
def initialize(source, destination, frequency)
|
7
|
+
@source = source
|
8
|
+
@destination = destination
|
9
|
+
@frequency = frequency
|
10
|
+
|
11
|
+
@thread = Atomic.new(nil)
|
12
|
+
@running = Atomic.new(false)
|
13
|
+
@last_updated_at = Atomic.new(nil)
|
14
|
+
@last_exception = Atomic.new(nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
if (thread = @thread.value) && thread.alive?
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
@running.value = true
|
23
|
+
|
24
|
+
@thread.value = Thread.new do
|
25
|
+
Thread.current.abort_on_exception = true
|
26
|
+
run
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@running.value = false
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def join
|
36
|
+
if thread = @thread.value
|
37
|
+
thread.join
|
38
|
+
end
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def healthy?
|
44
|
+
health_problems.blank?
|
45
|
+
end
|
46
|
+
|
47
|
+
def health_problems
|
48
|
+
thread = @thread.value
|
49
|
+
running = @running.value
|
50
|
+
updated = last_updated
|
51
|
+
exception = @last_exception.value
|
52
|
+
|
53
|
+
problems = []
|
54
|
+
|
55
|
+
unless running
|
56
|
+
problems << "Component not running"
|
57
|
+
end
|
58
|
+
|
59
|
+
unless thread && thread.alive?
|
60
|
+
problems << "Thread not alive"
|
61
|
+
end
|
62
|
+
|
63
|
+
unless recently_updated?(updated)
|
64
|
+
if updated
|
65
|
+
time_description = "in %0.2f seconds (should have been %d seconds)" % [ updated.to_f, @frequency ]
|
66
|
+
else
|
67
|
+
time_description = "ever"
|
68
|
+
end
|
69
|
+
|
70
|
+
problems << "Successful update hasn't occured #{time_description}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if exception
|
74
|
+
problems << "Last update failed with #{exception.class}: #{exception.message}"
|
75
|
+
end
|
76
|
+
|
77
|
+
unless problems.empty?
|
78
|
+
problems.join('; ')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def recently_updated?(updated = nil)
|
83
|
+
updated ||= last_updated
|
84
|
+
updated && updated < (@frequency * 2)
|
85
|
+
end
|
86
|
+
|
87
|
+
def last_updated
|
88
|
+
if at = @last_updated_at.value
|
89
|
+
Time.now.to_f - at.to_f
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
def run
|
95
|
+
active_zone = nil
|
96
|
+
|
97
|
+
while @running.value
|
98
|
+
begin
|
99
|
+
source_zone = nil
|
100
|
+
|
101
|
+
Scrolls.log(:from => :recurring_zone_updater, :zone => @source.domain, :for => :source) do
|
102
|
+
source_zone = @source.zone
|
103
|
+
end
|
104
|
+
|
105
|
+
if !active_zone
|
106
|
+
Scrolls.log(:from => :recurring_zone_updater, :zone => @source.domain, :for => :destination) do
|
107
|
+
active_zone = @destination.zone
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
diff = ZoneDifference.new(active_zone, source_zone, %w(NS SOA))
|
112
|
+
|
113
|
+
Scrolls.log(:from => :recurring_zone_updater, :zone => @source.domain,
|
114
|
+
:action => :updating, :adding => diff.added.length,
|
115
|
+
:updating => diff.changed.length, :removing => diff.removed.length) do
|
116
|
+
|
117
|
+
updater = ZoneUpdater.new(diff, @destination)
|
118
|
+
updater.call
|
119
|
+
end
|
120
|
+
|
121
|
+
active_zone = source_zone
|
122
|
+
@last_updated_at.value = Time.now
|
123
|
+
@last_exception.value = nil
|
124
|
+
rescue => e
|
125
|
+
Scrolls.log_exception({ :from => :recurring_zone_updater, :zone => @source.domain }, e)
|
126
|
+
@last_exception.value = e
|
127
|
+
end
|
128
|
+
|
129
|
+
if @running.value
|
130
|
+
sleep_until_next_deadline
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def sleep_until_next_deadline
|
136
|
+
if !@deadline
|
137
|
+
@deadline = Time.now.to_f
|
138
|
+
end
|
139
|
+
|
140
|
+
@deadline = @deadline + @frequency
|
141
|
+
|
142
|
+
sleep_duration = @deadline - Time.now.to_f
|
143
|
+
|
144
|
+
if sleep_duration <= 0
|
145
|
+
Scrolls.log(:from => :recurring_zone_updater, :for => :missed_deadline, :by => sleep_duration)
|
146
|
+
@deadline = Time.now.to_f
|
147
|
+
else
|
148
|
+
sleep sleep_duration
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/dnsync/zone.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'dnsync/record'
|
3
|
+
|
4
|
+
|
5
|
+
module Dnsync
|
6
|
+
class Zone
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize(name, records)
|
10
|
+
@name = name
|
11
|
+
|
12
|
+
@records_by_identifier = {}
|
13
|
+
records.each do |record|
|
14
|
+
@records_by_identifier[record.identifier] = record
|
15
|
+
end
|
16
|
+
|
17
|
+
freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](identifier)
|
21
|
+
@records_by_identifier[identifier]
|
22
|
+
end
|
23
|
+
|
24
|
+
def records_at(*identifiers)
|
25
|
+
@records_by_identifier.values_at(*identifiers.flatten)
|
26
|
+
end
|
27
|
+
|
28
|
+
def records
|
29
|
+
@records_by_identifier.values
|
30
|
+
end
|
31
|
+
|
32
|
+
def record_identifiers
|
33
|
+
@records_by_identifier.keys
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Dnsync
|
2
|
+
class ZoneDifference
|
3
|
+
def initialize(original, updated, ignored_types = nil)
|
4
|
+
@original = original
|
5
|
+
@updated = updated
|
6
|
+
@ignored_types = ignored_types || []
|
7
|
+
end
|
8
|
+
|
9
|
+
def added
|
10
|
+
@added ||= begin
|
11
|
+
added_identifiers = @updated.record_identifiers - @original.record_identifiers
|
12
|
+
added_identifiers = filter_types(added_identifiers)
|
13
|
+
@updated.records_at(added_identifiers)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def changed
|
18
|
+
@changed ||= begin
|
19
|
+
overlapping_identifiers = @updated.record_identifiers & @original.record_identifiers
|
20
|
+
overlapping_identifiers = filter_types(overlapping_identifiers)
|
21
|
+
|
22
|
+
overlapping_identifiers.map do |identifier|
|
23
|
+
original_record = @original[identifier]
|
24
|
+
updated_record = @updated[identifier]
|
25
|
+
|
26
|
+
if original_record != updated_record
|
27
|
+
updated_record
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end.compact
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def removed
|
36
|
+
@removed ||= begin
|
37
|
+
removed_identifiers = @original.record_identifiers - @updated.record_identifiers
|
38
|
+
removed_identifiers = filter_types(removed_identifiers)
|
39
|
+
|
40
|
+
@original.records_at(removed_identifiers)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def filter_types(identifiers)
|
45
|
+
if @ignored_types.blank?
|
46
|
+
return identifiers
|
47
|
+
end
|
48
|
+
|
49
|
+
identifiers.reject { |identifier| @ignored_types.include?(identifier.type) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Dnsync
|
2
|
+
class ZoneUpdater
|
3
|
+
def initialize(difference, target)
|
4
|
+
@difference = difference
|
5
|
+
@target = target
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
@difference.added.each do |record|
|
10
|
+
@target.create_record(record)
|
11
|
+
end
|
12
|
+
|
13
|
+
@difference.changed.each do |record|
|
14
|
+
@target.update_record(record)
|
15
|
+
end
|
16
|
+
|
17
|
+
@difference.removed.each do |record|
|
18
|
+
@target.remove_record(record)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/dnsync.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'dnsync/record_identifier'
|
6
|
+
|
7
|
+
class RecordIdentifierTest < Minitest::Test
|
8
|
+
def test_equality
|
9
|
+
a1 = Dnsync::RecordIdentifier.new("record1", "A")
|
10
|
+
a2 = Dnsync::RecordIdentifier.new("record1", "A")
|
11
|
+
|
12
|
+
assert a1 == a2
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_eql
|
16
|
+
a1 = Dnsync::RecordIdentifier.new("record1", "A")
|
17
|
+
a2 = Dnsync::RecordIdentifier.new("record1", "A")
|
18
|
+
|
19
|
+
assert a1.eql?(a2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_spaceship
|
23
|
+
a1 = Dnsync::RecordIdentifier.new("record1", "A")
|
24
|
+
a2 = Dnsync::RecordIdentifier.new("record1", "A")
|
25
|
+
|
26
|
+
assert (a1 <=> a2) == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_array_subtract
|
30
|
+
a1 = Dnsync::RecordIdentifier.new("record1", "A")
|
31
|
+
a2 = Dnsync::RecordIdentifier.new("record1", "A")
|
32
|
+
|
33
|
+
assert [a1] - [a2] == []
|
34
|
+
end
|
35
|
+
end
|
data/test/record_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'dnsync/record'
|
6
|
+
|
7
|
+
class RecordTest < Minitest::Test
|
8
|
+
def test_equality
|
9
|
+
a1 = Dnsync::Record.new("record1", "a", 300, [ Dnsync::Answer.new("127.0.0.1", nil) ])
|
10
|
+
a2 = Dnsync::Record.new("record1", "a", 300, [ Dnsync::Answer.new("127.0.0.1", nil) ])
|
11
|
+
|
12
|
+
assert a1 == a2
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_inequality
|
16
|
+
a1 = Dnsync::Record.new("record1", "a", 300, [ Dnsync::Answer.new("127.0.0.1", nil) ])
|
17
|
+
a2 = Dnsync::Record.new("record1", "a", 300, [ Dnsync::Answer.new("127.0.0.1", nil) ])
|
18
|
+
|
19
|
+
assert !(a1 != a2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_spaceship
|
23
|
+
a1 = Dnsync::Record.new("record1", "a", 300, [ Dnsync::Answer.new("127.0.0.1", nil) ])
|
24
|
+
a2 = Dnsync::Record.new("record1", "a", 300, [ Dnsync::Answer.new("127.0.0.1", nil) ])
|
25
|
+
|
26
|
+
assert (a1 <=> a2) == 0
|
27
|
+
end
|
28
|
+
end
|