dnsync 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|