ntp-mock-server 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +75 -0
- data/Rakefile +10 -0
- data/TODO +1 -0
- data/bin/ntp-mock-server +25 -0
- data/cucumber.yml +4 -0
- data/features/run.feature +22 -0
- data/features/step_definitions/server_steps.rb +38 -0
- data/features/support/env.rb +9 -0
- data/features/support/hooks.rb +11 -0
- data/lib/ntp.rb +212 -0
- data/lib/ntp/server.rb +8 -0
- data/lib/ntp/server/base.rb +102 -0
- data/lib/ntp/server/control.rb +76 -0
- data/lib/ntp/server/handler.rb +74 -0
- data/lib/ntp/server/version.rb +3 -0
- data/ntp-mock-server.gemspec +38 -0
- metadata +184 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 065c1668f3ff9fffc146a0b5530b82d964cf593c
|
4
|
+
data.tar.gz: 1149bd62ec40536c336e0686d7b1c2c9a108db97
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9ccbef820379434ebfaab0c5a1a885fb5a57cd6366c29ba7b9cb71c26365931972164a39b5b1d4451052abcfe3252b0962b4c39d0f48253a85e89244fc680684
|
7
|
+
data.tar.gz: 405165ecb2349df3e8750df0db6526eedff6e05db6690c763c6b43265a358287bae113f9547e213b7b3e0db9f7b6df4fc583221d72ed6412b5308a902136b034
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Dave McNulla and Malo Skrylevo
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# NTP Mock Server
|
2
|
+
|
3
|
+
[](https://gemnasium.com/majioa/ntp-mock-server)
|
4
|
+
[](http://rubygems.org/gems/ntp-mock-server)
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
### in ruby script
|
8
|
+
|
9
|
+
Install gem from `github`, by adding the gem line into the **Gemfile** as follows:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'ntp-mock-server', github: 'dmcnulla/ntp'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install flickr_oblak
|
22
|
+
|
23
|
+
Add to your script:
|
24
|
+
|
25
|
+
require 'ntp' # NOTE it automatically includes 'net/ntp' gem
|
26
|
+
|
27
|
+
# create a new server instance
|
28
|
+
server = NTP::Server::Control.new
|
29
|
+
|
30
|
+
# starting the server on 12345-th port.
|
31
|
+
server.start(12345)
|
32
|
+
# => "started NTP mock server on localhost:12345."
|
33
|
+
|
34
|
+
# get a time from the server
|
35
|
+
Net::NTP::get('127.0.0.1', 12345).time # => time...
|
36
|
+
|
37
|
+
# set base time for the server
|
38
|
+
server.time("2000/01/01 01:00")
|
39
|
+
|
40
|
+
# get a new rebased time from the server
|
41
|
+
Net::NTP::get('127.0.0.1', 12345).time # => "2000/01/01 01:05"
|
42
|
+
|
43
|
+
# stop the server. NOTE since the server is bind to another process, it shall be explicitly stopped.
|
44
|
+
server.stop
|
45
|
+
# => "stopped"
|
46
|
+
|
47
|
+
### From a command line
|
48
|
+
|
49
|
+
You can control the server by using a command line interface as follows:
|
50
|
+
|
51
|
+
# start the server
|
52
|
+
$ ntp-mock-server start
|
53
|
+
|
54
|
+
# setb server's base time
|
55
|
+
$ ntp-mock-server time "2000/01/01 01:00"
|
56
|
+
|
57
|
+
# stop the server
|
58
|
+
$ ntp-mock-server stop
|
59
|
+
|
60
|
+
Issue the CLI application without a command to view all available ones:
|
61
|
+
|
62
|
+
$ ntp-mock-server
|
63
|
+
Usage: ntp-mock-server [start|stop|restart|status|time <time>|reset]
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
1. Fork it ( https://github.com/dmcnulla/ntp/fork )
|
68
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
69
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
70
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
5. Create new Pull Request
|
72
|
+
|
73
|
+
## License
|
74
|
+
|
75
|
+
See `LICENSE.txt` file.
|
data/Rakefile
ADDED
data/bin/ntp-mock-server
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ntp'
|
4
|
+
|
5
|
+
control = NTP::Server::Control.new
|
6
|
+
|
7
|
+
line =
|
8
|
+
case ARGV[0]
|
9
|
+
when 'start'
|
10
|
+
control.start ARGV[1]
|
11
|
+
when 'stop'
|
12
|
+
control.stop
|
13
|
+
when 'restart'
|
14
|
+
control.restart
|
15
|
+
when 'time'
|
16
|
+
control.time ARGV[1]
|
17
|
+
when 'reset'
|
18
|
+
control.reset
|
19
|
+
when 'status'
|
20
|
+
control.status
|
21
|
+
else
|
22
|
+
control.usage
|
23
|
+
end
|
24
|
+
|
25
|
+
puts line
|
data/cucumber.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Feature: As a end-to-end tester, I want a NTP server that will adjust servers-under-test's time so those servers will process based on schedule.
|
2
|
+
|
3
|
+
@server.S1
|
4
|
+
Scenario: Server runs on demand
|
5
|
+
Given I have an NTP service that is running on localhost
|
6
|
+
When I request the time from it
|
7
|
+
Then the time is near to now
|
8
|
+
|
9
|
+
@server.S2
|
10
|
+
Scenario: Server can change time
|
11
|
+
Given I have an NTP service that is running on localhost
|
12
|
+
When I change the time to '2016/01/01T12:00:01'
|
13
|
+
And I request the time from it
|
14
|
+
Then the time is near to '2016/01/01T12:00:01'
|
15
|
+
|
16
|
+
@server.S3
|
17
|
+
Scenario: Server can reset to current time
|
18
|
+
Given I have an NTP service that is running on localhost
|
19
|
+
When I change the time to '2016/01/01T12:00:01'
|
20
|
+
And I reset it
|
21
|
+
And I request the time from it
|
22
|
+
Then the time is near to now
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Given(/^I have an NTP service that is running on localhost$/) do
|
2
|
+
# this is started in hooks
|
3
|
+
end
|
4
|
+
|
5
|
+
When(/^I request the time from it$/) do
|
6
|
+
begin
|
7
|
+
@time = Net::NTP.get('localhost', SERVER_PORT).time
|
8
|
+
rescue Errno::ECONNREFUSED
|
9
|
+
retry
|
10
|
+
end
|
11
|
+
# @time = Net::NTP.get.time.to_f
|
12
|
+
# This is just to test the cuke scenario
|
13
|
+
# @time = @server.get_time.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
Then(/^the time is near to now$/) do
|
17
|
+
expect(time_diff(@time, Time.now)).to be < 1
|
18
|
+
end
|
19
|
+
|
20
|
+
When(/^I change the time to '(\d+)\/(\d+)\/(\d+)T(\d+):(\d+):(\d+)'$/) do |year, month, day, hour, minute, second|
|
21
|
+
@new_time = Time.new(year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i)
|
22
|
+
@server.time(@new_time)
|
23
|
+
@server.status
|
24
|
+
sleep 0.5
|
25
|
+
end
|
26
|
+
|
27
|
+
Then(/^the time is near to '(\d+)\/(\d+)\/(\d+)T(\d+):(\d+):(\d+)'$/) do |_year, _month, _day, _hour, _minute, _second|
|
28
|
+
expect(time_diff(@time, @new_time)).to be < 1
|
29
|
+
end
|
30
|
+
|
31
|
+
When(/^I reset it$/) do
|
32
|
+
@server.reset
|
33
|
+
@server.status
|
34
|
+
end
|
35
|
+
|
36
|
+
def time_diff(time_a, time_b)
|
37
|
+
time_a.to_f - time_b.to_f
|
38
|
+
end
|
data/lib/ntp.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'net/ntp'
|
2
|
+
|
3
|
+
module NTP
|
4
|
+
Net::NTP::STRATUM.merge!( { 16 => 'non-synchronized' } )
|
5
|
+
|
6
|
+
class ClockId
|
7
|
+
def data
|
8
|
+
if @stratum < 2
|
9
|
+
"#{@id}\0".split('').map(&:ord).pack("C4")
|
10
|
+
else
|
11
|
+
# as IP
|
12
|
+
@id.split('.').map(&:to_i).pack("C4")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize stratum = 1, id
|
17
|
+
@stratum = stratum
|
18
|
+
@id = Net::NTP::REFERENCE_CLOCK_IDENTIFIER.invert[ id ] || id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Time
|
23
|
+
attr_reader :seconds, :fraction
|
24
|
+
|
25
|
+
def timestamp
|
26
|
+
[ seconds, fraction ].pack( "NB32" )
|
27
|
+
end
|
28
|
+
|
29
|
+
def short
|
30
|
+
[ seconds, fraction ].pack( "nB16" )
|
31
|
+
end
|
32
|
+
|
33
|
+
def time
|
34
|
+
t = ( seconds - Net::NTP::NTP_ADJ ).to_f + bin2frac( fraction )
|
35
|
+
::Time.at( t )
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def bin2frac(bin) #:nodoc:
|
41
|
+
frac = 0
|
42
|
+
|
43
|
+
bin.reverse.split("").each do |b|
|
44
|
+
frac = ( frac + b.to_i ) / 2.0
|
45
|
+
end
|
46
|
+
|
47
|
+
frac
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize *args
|
51
|
+
if args.size > 1
|
52
|
+
@seconds = args.shift
|
53
|
+
@fraction = args.shift
|
54
|
+
|
55
|
+
# w/a
|
56
|
+
if @seconds < Net::NTP::NTP_ADJ
|
57
|
+
@seconds += Net::NTP::NTP_ADJ
|
58
|
+
end
|
59
|
+
else
|
60
|
+
if args[ 0 ].is_a?( NTP::Time )
|
61
|
+
@seconds = args[ 0 ].seconds
|
62
|
+
@fraction = args[ 0 ].fraction
|
63
|
+
else
|
64
|
+
time =
|
65
|
+
case args[ 0 ]
|
66
|
+
when String
|
67
|
+
self.class.parse( args[ 0 ] )
|
68
|
+
when ::Time
|
69
|
+
args[ 0 ].utc
|
70
|
+
when Float, Integer
|
71
|
+
::Time.at( args[ 0 ] - Net::NTP::NTP_ADJ )
|
72
|
+
else
|
73
|
+
::Time.now.utc
|
74
|
+
end
|
75
|
+
|
76
|
+
@seconds = time.to_i + Net::NTP::NTP_ADJ
|
77
|
+
@fraction = Net::NTP.send( :frac2bin, time.to_f - time.to_i )
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Request
|
84
|
+
attr_reader :leap_indicator, :mode, :timestamp
|
85
|
+
attr_accessor :version_number
|
86
|
+
|
87
|
+
def leap_indicator= leap_indicator
|
88
|
+
@leap_indicator =
|
89
|
+
if leap_indicator.is_a?( String )
|
90
|
+
Net::NTP::Response::LEAP_INDICATOR.invert[ leap_indicator ]
|
91
|
+
else
|
92
|
+
leap_indicator
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def mode= mode
|
97
|
+
@mode =
|
98
|
+
if mode.is_a?( String )
|
99
|
+
Net::NTP::Response::MODE.invert[ mode ]
|
100
|
+
else
|
101
|
+
mode
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def timestamp= timestamp
|
106
|
+
@timestamp = NTP::Time.new( timestamp )
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.parse data
|
110
|
+
args = data.unpack( "C C3 N10 B32" )
|
111
|
+
request = Request.new
|
112
|
+
request.leap_indicator = ( args[ 0 ] & 0xc0 ) >> 6
|
113
|
+
request.version_number = ( args[ 0 ] & 0x38 ) >> 3
|
114
|
+
request.mode = args[ 0 ] & 0x7
|
115
|
+
request.timestamp = NTP::Time.new( args[ -2 ], args[ -1 ] )
|
116
|
+
request
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Response < Net::NTP::Response
|
121
|
+
|
122
|
+
attr_writer :version_number, :poll_interval, :precision,
|
123
|
+
:root_dispersion
|
124
|
+
|
125
|
+
def leap_indicator= leap_indicator
|
126
|
+
@leap_indicator_text = leap_indicator
|
127
|
+
@leap_indicator = Net::NTP::LEAP_INDICATOR.invert[ leap_indicator ]
|
128
|
+
end
|
129
|
+
|
130
|
+
def mode= mode
|
131
|
+
@mode_text ||= mode
|
132
|
+
@mode ||= Net::NTP::MODE.invert[ mode ]
|
133
|
+
end
|
134
|
+
|
135
|
+
def stratum= stratum
|
136
|
+
@stratum_text ||= stratum
|
137
|
+
@stratum = Net::NTP::STRATUM.invert[ stratum ]
|
138
|
+
end
|
139
|
+
|
140
|
+
def reference_clock_identifier= reference_clock_identifier
|
141
|
+
@reference_clock_identifier =
|
142
|
+
NTP::ClockId.new( @stratum, reference_clock_identifier )
|
143
|
+
end
|
144
|
+
|
145
|
+
def root_delay= root_delay
|
146
|
+
@root_delay = NTP::Time.new( root_delay )
|
147
|
+
end
|
148
|
+
|
149
|
+
def reference_timestamp= reference_timestamp
|
150
|
+
@reference_timestamp = NTP::Time.new( reference_timestamp )
|
151
|
+
end
|
152
|
+
|
153
|
+
def originate_timestamp= originate_timestamp
|
154
|
+
@originate_timestamp = NTP::Time.new( originate_timestamp )
|
155
|
+
end
|
156
|
+
|
157
|
+
def receive_timestamp= receive_timestamp
|
158
|
+
@receive_timestamp = NTP::Time.new( receive_timestamp )
|
159
|
+
end
|
160
|
+
|
161
|
+
def transmit_timestamp= transmit_timestamp
|
162
|
+
@transmit_timestamp = NTP::Time.new( transmit_timestamp )
|
163
|
+
end
|
164
|
+
|
165
|
+
def send &block
|
166
|
+
data = self.compile
|
167
|
+
yield "#{data[0...40]}#{transmit_timestamp.timestamp}"
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
def raw_data
|
173
|
+
if ! @raw_data
|
174
|
+
@packetdata = []
|
175
|
+
NTP_FIELDS.each do |field|
|
176
|
+
@packetdata.push( @packet_data_by_field[field] )
|
177
|
+
end
|
178
|
+
|
179
|
+
@raw_data = @packetdata.pack( "a C3 n B16 n B16 H8 N B32 N B32 N B32 N B32" )
|
180
|
+
end
|
181
|
+
|
182
|
+
@raw_data
|
183
|
+
end
|
184
|
+
|
185
|
+
def compile
|
186
|
+
data = [
|
187
|
+
( ( @leap_indicator & 0x3 ) << 6 |
|
188
|
+
( @version_number & 0x7 ) << 3 |
|
189
|
+
( @mode & 0x7 ) ),
|
190
|
+
@stratum,
|
191
|
+
@poll_interval,
|
192
|
+
@precision,
|
193
|
+
@root_delay.short,
|
194
|
+
@root_dispersion.short,
|
195
|
+
@reference_clock_identifier.data,
|
196
|
+
@reference_timestamp.timestamp,
|
197
|
+
@originate_timestamp.timestamp,
|
198
|
+
@receive_timestamp.timestamp,
|
199
|
+
@transmit_timestamp.timestamp,
|
200
|
+
]
|
201
|
+
data.pack( "C4 A4 A4 A4 A8 A8 A8 A8" )
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def initialize
|
207
|
+
super(nil, ::Time.now)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
require 'ntp/server'
|
data/lib/ntp/server.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
class NTP::Server::Base
|
4
|
+
attr_accessor :host, :port, :pipe_in, :pipe_out, :pid,
|
5
|
+
:gap_reader, :gap_writer
|
6
|
+
|
7
|
+
def initialize(port = 123)
|
8
|
+
self.pipe_in = Fifo.new('ntp-mock-server-in', :r, :nowait)
|
9
|
+
self.pipe_out = Fifo.new('ntp-mock-server-out', :w, :nowait)
|
10
|
+
self.host = 'localhost'
|
11
|
+
self.port = port
|
12
|
+
(self.gap_reader, self.gap_writer) = IO.pipe
|
13
|
+
handler.origin_time = Time.now
|
14
|
+
handler.gap = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# runs the server
|
18
|
+
def start
|
19
|
+
self.pid = fork do
|
20
|
+
self.gap_writer.close
|
21
|
+
self.handler.reader = self.gap_reader
|
22
|
+
EventMachine::run do
|
23
|
+
EventMachine::open_datagram_socket self.host, self.port, self.handler
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self.gap_reader.close
|
27
|
+
self.pipe_out.puts "started NTP mock server on #{host}:#{port}."
|
28
|
+
process_queue
|
29
|
+
end
|
30
|
+
|
31
|
+
# returns handler constant
|
32
|
+
def handler
|
33
|
+
NTP::Server::Handler
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# enters to the processing queue loop
|
39
|
+
def process_queue
|
40
|
+
while true
|
41
|
+
self.pipe_in.to_io.sync
|
42
|
+
message = self.pipe_in.gets
|
43
|
+
# puts message
|
44
|
+
case message
|
45
|
+
when /stop/
|
46
|
+
stop
|
47
|
+
when /status/
|
48
|
+
self.pipe_out.puts(status)
|
49
|
+
when /time/
|
50
|
+
/time (?<time>.*)/ =~ message
|
51
|
+
begin
|
52
|
+
change_time(Time.parse(time))
|
53
|
+
rescue ArgumentError
|
54
|
+
puts "can't set invalid time"
|
55
|
+
end
|
56
|
+
when /reset/
|
57
|
+
reset
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# stops the server
|
63
|
+
def stop
|
64
|
+
Process.kill("HUP", self.pid)
|
65
|
+
Process.wait2
|
66
|
+
Process.exit(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
# sets a new time for the NTP server to base future responses
|
70
|
+
def change_time(new_time)
|
71
|
+
send_gap(new_time.utc - Time.now.utc)
|
72
|
+
end
|
73
|
+
|
74
|
+
# sets the time to current time to base future responses
|
75
|
+
def reset
|
76
|
+
send_gap(0)
|
77
|
+
end
|
78
|
+
|
79
|
+
def status
|
80
|
+
'listening...'
|
81
|
+
end
|
82
|
+
|
83
|
+
def puts *args
|
84
|
+
Kernel.puts *args
|
85
|
+
end
|
86
|
+
|
87
|
+
def send_gap gap
|
88
|
+
begin
|
89
|
+
self.gap_writer.puts(gap)
|
90
|
+
rescue Errno::EPIPE
|
91
|
+
# TODO check and restore child server part
|
92
|
+
end
|
93
|
+
self.gap_writer.sync
|
94
|
+
Process.kill("USR1", self.pid)
|
95
|
+
Process.setpriority(Process::PRIO_PROCESS, self.pid, 19)
|
96
|
+
end
|
97
|
+
|
98
|
+
# only used for practing the cukes
|
99
|
+
# def get_time
|
100
|
+
# @time
|
101
|
+
# end
|
102
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'ntp/server/base'
|
2
|
+
require 'ruby-fifo'
|
3
|
+
|
4
|
+
class NTP::Server::Control
|
5
|
+
DEFAULT_PORT = 17890
|
6
|
+
|
7
|
+
def usage
|
8
|
+
"Usage: ntp-mock-server [start|stop|restart|status|time <time>|reset]"
|
9
|
+
end
|
10
|
+
|
11
|
+
def status
|
12
|
+
send_command('status')
|
13
|
+
status = read(@client_in)
|
14
|
+
status || "not running"
|
15
|
+
end
|
16
|
+
|
17
|
+
def start port = DEFAULT_PORT
|
18
|
+
fork do
|
19
|
+
Process.setsid
|
20
|
+
NTP::Server::Base.new(port || DEFAULT_PORT).start
|
21
|
+
end
|
22
|
+
read(@client_in, 5)
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop
|
26
|
+
send_command('stop')
|
27
|
+
|
28
|
+
s = true
|
29
|
+
begin
|
30
|
+
Timeout::timeout(5) do
|
31
|
+
while s do
|
32
|
+
send_command('status')
|
33
|
+
s = read(@client_in)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue Timeout::Error
|
37
|
+
end
|
38
|
+
|
39
|
+
if s
|
40
|
+
"failed to stop: status #{s}"
|
41
|
+
else
|
42
|
+
"stopped"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def restart
|
47
|
+
[ stop, start ].join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def time time
|
51
|
+
send_command("time #{time}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset
|
55
|
+
send_command('reset')
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def send_command data
|
61
|
+
@client_out.puts data
|
62
|
+
@client_out.to_io.sync
|
63
|
+
end
|
64
|
+
|
65
|
+
def read client_in, timeout = 1
|
66
|
+
begin
|
67
|
+
Timeout::timeout(timeout) { client_in.gets }
|
68
|
+
rescue Timeout::Error
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize
|
73
|
+
@client_out = Fifo.new('ntp-mock-server-in', :w, :nowait)
|
74
|
+
@client_in = Fifo.new('ntp-mock-server-out', :r, :nowait)
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module NTP::Server::Handler
|
2
|
+
def make_response request, receive_time
|
3
|
+
time = ::Time.now + self.handler.gap
|
4
|
+
response = NTP::Response.new
|
5
|
+
response.leap_indicator = 'no warning'
|
6
|
+
response.version_number = request.version_number
|
7
|
+
response.mode = 'reserved for private use'
|
8
|
+
response.stratum = 'non-synchronized'
|
9
|
+
response.poll_interval = 6
|
10
|
+
response.precision = 0
|
11
|
+
response.root_delay = request.timestamp.time - receive_time
|
12
|
+
response.root_dispersion = NTP::Time.new(0)
|
13
|
+
response.reference_clock_identifier = '127.0.0.1'
|
14
|
+
response.reference_timestamp = time
|
15
|
+
response.originate_timestamp = self.handler.origin_time
|
16
|
+
response.receive_timestamp = receive_time + self.handler.gap
|
17
|
+
response.transmit_timestamp = time
|
18
|
+
# Kernel.puts response.inspect
|
19
|
+
response
|
20
|
+
end
|
21
|
+
|
22
|
+
def receive_data data
|
23
|
+
receive_time = ::Time.now
|
24
|
+
request = NTP::Request.parse( data )
|
25
|
+
response = make_response( request, receive_time )
|
26
|
+
response.send do |data|
|
27
|
+
# Kernel.puts data.unpack("C*").map{|x| sprintf "%.2x", x}.join
|
28
|
+
send_data( data )
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def handler
|
33
|
+
NTP::Server::Handler
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.reader
|
37
|
+
@@reader
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.reader= reader
|
41
|
+
@@reader = reader
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.origin_time= origin_time
|
45
|
+
@@origin_time = origin_time
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.origin_time
|
49
|
+
@@origin_time
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.gap= gap
|
53
|
+
@@gap = gap
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.gap
|
57
|
+
@@gap
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.update_gap
|
61
|
+
self.reader.sync
|
62
|
+
self.gap = self.reader.read_nonblock(1024).to_f
|
63
|
+
rescue IO::EAGAINWaitReadable
|
64
|
+
retry
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def self.included klass
|
70
|
+
Signal.trap("USR1") do
|
71
|
+
update_gap
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
module NTP
|
4
|
+
module Server; end
|
5
|
+
end
|
6
|
+
require 'ntp/server/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = "ntp-mock-server"
|
10
|
+
s.version = ::NTP::Server::VERSION
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.authors = [ 'Dave McNulla', 'Малъ Скрылёвъ (Malo Skrylevo)' ]
|
13
|
+
s.email = [ 'mcnulla@gmail.com', 'majioa@yandex.ru' ]
|
14
|
+
s.homepage = 'https://github.com/dmcnulla/ntp'
|
15
|
+
s.summary = 'NTP Mock Server'
|
16
|
+
s.description = 'NTP Mock Server allows to rebase server time for various test suite purposes.'
|
17
|
+
s.license = 'MIT'
|
18
|
+
|
19
|
+
s.rubyforge_project = "ntp-mock-server"
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split( "\n" ).map{ |f| File.basename(f) }
|
23
|
+
s.bindir = "bin"
|
24
|
+
s.require_paths = [ "lib" ]
|
25
|
+
s.extra_rdoc_files = [ 'README.md', 'LICENSE.txt' ] | `find html/ 2>/dev/null`.split( "\n" )
|
26
|
+
|
27
|
+
s.add_development_dependency 'bundler', '~> 1.5'
|
28
|
+
s.add_development_dependency 'pry', '~> 0.10.3'
|
29
|
+
s.add_development_dependency 'cucumber', '~> 2.3.3'
|
30
|
+
s.add_development_dependency 'rake', '~> 11.1'
|
31
|
+
|
32
|
+
s.add_dependency 'eventmachine', '~> 1.2.0'
|
33
|
+
s.add_dependency 'ruby-fifo', '~> 0.1.0'
|
34
|
+
s.add_dependency 'mkfifo', '~> 0.1.1'
|
35
|
+
s.add_dependency 'net-ntp', '~> 2.1.3'
|
36
|
+
|
37
|
+
s.required_rubygems_version = '>= 1.6.0'
|
38
|
+
s.required_ruby_version = '>= 1.9.3' ; end
|
metadata
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ntp-mock-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dave McNulla
|
8
|
+
- Малъ Скрылёвъ (Malo Skrylevo)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-04-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.5'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.5'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: pry
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.10.3
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.10.3
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: cucumber
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 2.3.3
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.3.3
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '11.1'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '11.1'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: eventmachine
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.2.0
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 1.2.0
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: ruby-fifo
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 0.1.0
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 0.1.0
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: mkfifo
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 0.1.1
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 0.1.1
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: net-ntp
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 2.1.3
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 2.1.3
|
126
|
+
description: NTP Mock Server allows to rebase server time for various test suite purposes.
|
127
|
+
email:
|
128
|
+
- mcnulla@gmail.com
|
129
|
+
- majioa@yandex.ru
|
130
|
+
executables:
|
131
|
+
- ntp-mock-server
|
132
|
+
extensions: []
|
133
|
+
extra_rdoc_files:
|
134
|
+
- README.md
|
135
|
+
- LICENSE.txt
|
136
|
+
files:
|
137
|
+
- ".gitignore"
|
138
|
+
- Gemfile
|
139
|
+
- LICENSE.txt
|
140
|
+
- README.md
|
141
|
+
- Rakefile
|
142
|
+
- TODO
|
143
|
+
- bin/ntp-mock-server
|
144
|
+
- cucumber.yml
|
145
|
+
- features/run.feature
|
146
|
+
- features/step_definitions/server_steps.rb
|
147
|
+
- features/support/env.rb
|
148
|
+
- features/support/hooks.rb
|
149
|
+
- lib/ntp.rb
|
150
|
+
- lib/ntp/server.rb
|
151
|
+
- lib/ntp/server/base.rb
|
152
|
+
- lib/ntp/server/control.rb
|
153
|
+
- lib/ntp/server/handler.rb
|
154
|
+
- lib/ntp/server/version.rb
|
155
|
+
- ntp-mock-server.gemspec
|
156
|
+
homepage: https://github.com/dmcnulla/ntp
|
157
|
+
licenses:
|
158
|
+
- MIT
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 1.9.3
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.6.0
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project: ntp-mock-server
|
176
|
+
rubygems_version: 2.5.1
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: NTP Mock Server
|
180
|
+
test_files:
|
181
|
+
- features/run.feature
|
182
|
+
- features/step_definitions/server_steps.rb
|
183
|
+
- features/support/env.rb
|
184
|
+
- features/support/hooks.rb
|