content_server 0.0.8 → 0.0.9a
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/bin/content_server
CHANGED
@@ -5,16 +5,25 @@
|
|
5
5
|
# Each unique content is backed up to the remote (backup) server.
|
6
6
|
|
7
7
|
begin
|
8
|
+
print "1"
|
8
9
|
require 'yaml'
|
10
|
+
print "2"
|
9
11
|
require 'params'
|
12
|
+
print "3"
|
10
13
|
require 'run_in_background'
|
14
|
+
print "4"
|
11
15
|
require 'content_server'
|
12
|
-
|
13
|
-
|
16
|
+
print "5"
|
17
|
+
rescue LoadError
|
18
|
+
print "6"
|
14
19
|
require 'rubygems'
|
20
|
+
print "7"
|
15
21
|
require 'yaml'
|
22
|
+
print "8"
|
16
23
|
require 'params'
|
24
|
+
print "9"
|
17
25
|
require 'run_in_background'
|
26
|
+
print "0"
|
18
27
|
require 'content_server'
|
19
28
|
end
|
20
29
|
include BBFS
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'tempfile'
|
1
2
|
require 'thread'
|
2
3
|
|
3
4
|
require 'file_indexing/index_agent'
|
@@ -6,7 +7,7 @@ require 'log'
|
|
6
7
|
module BBFS
|
7
8
|
module ContentServer
|
8
9
|
|
9
|
-
Params.integer('streaming_chunk_size',
|
10
|
+
Params.integer('streaming_chunk_size', 2*1024*1024,
|
10
11
|
'Max number of content bytes to send in one chunk.')
|
11
12
|
Params.integer('file_streaming_timeout', 5*60,
|
12
13
|
'If no action is taken on a file streamer, abort copy.')
|
@@ -14,7 +15,7 @@ module BBFS
|
|
14
15
|
'Backup server destination folder, default is the relative local folder.')
|
15
16
|
|
16
17
|
class Stream
|
17
|
-
attr_reader :checksum, :path, :file, :size
|
18
|
+
attr_reader :checksum, :path, :tmp_path, :file, :size
|
18
19
|
def initialize(checksum, path, file, size)
|
19
20
|
@checksum = checksum
|
20
21
|
@path = path
|
@@ -42,6 +43,7 @@ module BBFS
|
|
42
43
|
|
43
44
|
:NEW_STREAM
|
44
45
|
:ABORT_STREAM
|
46
|
+
:RESET_STREAM
|
45
47
|
:COPY_CHUNK
|
46
48
|
|
47
49
|
def initialize(send_chunk_clb, abort_streaming_clb=nil)
|
@@ -54,6 +56,10 @@ module BBFS
|
|
54
56
|
@thread = run
|
55
57
|
end
|
56
58
|
|
59
|
+
def copy_another_chuck(checksum)
|
60
|
+
@stream_queue << [:COPY_CHUNK, checksum]
|
61
|
+
end
|
62
|
+
|
57
63
|
def start_streaming(checksum, path)
|
58
64
|
@stream_queue << [:NEW_STREAM, [checksum, path]]
|
59
65
|
end
|
@@ -62,6 +68,10 @@ module BBFS
|
|
62
68
|
@stream_queue << [:ABORT_STREAM, checksum]
|
63
69
|
end
|
64
70
|
|
71
|
+
def reset_streaming(checksum, new_offset)
|
72
|
+
@stream_queue << [:RESET_STREAM, [checksum, new_offset]]
|
73
|
+
end
|
74
|
+
|
65
75
|
def run
|
66
76
|
return Thread.new do
|
67
77
|
loop {
|
@@ -74,30 +84,28 @@ module BBFS
|
|
74
84
|
type, content = message
|
75
85
|
if type == :NEW_STREAM
|
76
86
|
checksum, path = content
|
77
|
-
|
78
|
-
|
79
|
-
file = File.new(path, 'rb')
|
80
|
-
Log.info("File streamer: #{file.to_s}.")
|
81
|
-
rescue IOError => e
|
82
|
-
Log.warning("Could not stream local file #{path}. #{e.to_s}")
|
83
|
-
end
|
84
|
-
@streams[checksum] = Stream.new(checksum, path, file, file.size)
|
85
|
-
@stream_queue << [:COPY_CHUNK, checksum]
|
86
|
-
end
|
87
|
+
reset_stream(checksum, path, 0)
|
88
|
+
@stream_queue << [:COPY_CHUNK, checksum] if @streams.key?(checksum)
|
87
89
|
elsif type == :ABORT_STREAM
|
88
|
-
|
90
|
+
checksum = content
|
91
|
+
Stream.close_delete_stream(checksum, @streams)
|
92
|
+
elsif type == :RESET_STREAM
|
93
|
+
checksum, new_offset = content
|
94
|
+
reset_stream(checksum, nil, new_offset)
|
95
|
+
@stream_queue << [:COPY_CHUNK, checksum] if @streams.key?(checksum)
|
89
96
|
elsif type == :COPY_CHUNK
|
90
97
|
checksum = content
|
91
98
|
if @streams.key?(checksum)
|
99
|
+
offset = @streams[checksum].file.pos
|
100
|
+
Log.debug1("Sending chunk for #{checksum}, offset #{offset}.")
|
92
101
|
chunk = @streams[checksum].file.read(Params['streaming_chunk_size'])
|
93
102
|
if chunk.nil?
|
94
103
|
# No more to read, send end of file.
|
95
|
-
@send_chunk_clb.call(checksum, @streams[checksum].size, nil, nil)
|
104
|
+
@send_chunk_clb.call(checksum, offset, @streams[checksum].size, nil, nil)
|
96
105
|
Stream.close_delete_stream(checksum, @streams)
|
97
106
|
else
|
98
107
|
chunk_checksum = FileIndexing::IndexAgent.get_content_checksum(chunk)
|
99
|
-
@send_chunk_clb.call(checksum, @streams[checksum].size, chunk, chunk_checksum)
|
100
|
-
@stream_queue << [:COPY_CHUNK, checksum]
|
108
|
+
@send_chunk_clb.call(checksum, offset, @streams[checksum].size, chunk, chunk_checksum)
|
101
109
|
end
|
102
110
|
else
|
103
111
|
Log.info("No checksum found to copy chunk. #{checksum}.")
|
@@ -105,6 +113,23 @@ module BBFS
|
|
105
113
|
end
|
106
114
|
|
107
115
|
end
|
116
|
+
|
117
|
+
def reset_stream(checksum, path, offset)
|
118
|
+
if !@streams.key? checksum
|
119
|
+
begin
|
120
|
+
file = File.new(path, 'rb')
|
121
|
+
if offset > 0
|
122
|
+
file.seek(offset)
|
123
|
+
end
|
124
|
+
Log.info("File streamer: #{file.to_s}.")
|
125
|
+
rescue IOError => e
|
126
|
+
Log.warning("Could not stream local file #{path}. #{e.to_s}")
|
127
|
+
end
|
128
|
+
@streams[checksum] = Stream.new(checksum, path, file, file.size)
|
129
|
+
else
|
130
|
+
@streams[checksum].file.seek(offset)
|
131
|
+
end
|
132
|
+
end
|
108
133
|
end
|
109
134
|
|
110
135
|
# Start implementing as dummy, no self thread for now.
|
@@ -112,67 +137,132 @@ module BBFS
|
|
112
137
|
# need self thread.
|
113
138
|
class FileReceiver
|
114
139
|
|
115
|
-
def initialize(file_done_clb=nil, file_abort_clb=nil)
|
140
|
+
def initialize(file_done_clb=nil, file_abort_clb=nil, reset_copy=nil)
|
116
141
|
@file_done_clb = file_done_clb
|
117
142
|
@file_abort_clb = file_abort_clb
|
143
|
+
@reset_copy = reset_copy
|
118
144
|
@streams = {}
|
119
145
|
end
|
120
146
|
|
121
|
-
def receive_chunk(file_checksum, file_size, content, content_checksum)
|
147
|
+
def receive_chunk(file_checksum, offset, file_size, content, content_checksum)
|
148
|
+
# If standard chunk copy.
|
122
149
|
if !content.nil? && !content_checksum.nil?
|
123
150
|
received_content_checksum = FileIndexing::IndexAgent.get_content_checksum(content)
|
124
151
|
comment = "Calculated received chunk with content checksum #{received_content_checksum}" \
|
125
152
|
" vs message content checksum #{content_checksum}, " \
|
126
153
|
"file checksum #{file_checksum}"
|
127
154
|
Log.debug1(comment) if content_checksum == received_content_checksum
|
128
|
-
|
155
|
+
# TODO should be here a kind of abort?
|
156
|
+
if content_checksum != received_content_checksum
|
157
|
+
Log.warning(comment)
|
158
|
+
new_offset = 0
|
159
|
+
if @streams.key?(file_checksum)
|
160
|
+
new_offset = @streams[file_checksum].file.pos
|
161
|
+
end
|
162
|
+
@reset_copy.call(file_checksum, new_offset) unless @reset_copy.nil?
|
163
|
+
return false
|
164
|
+
end
|
129
165
|
|
130
166
|
if !@streams.key?(file_checksum)
|
131
|
-
|
132
|
-
file_checksum)
|
133
|
-
if File.exists?(path)
|
134
|
-
Log.warning("File already exists (#{path}) not writing.")
|
135
|
-
@file_abort_clb.call(file_checksum) unless @file_abort_clb.nil?
|
136
|
-
else
|
137
|
-
begin
|
138
|
-
# Make the directory if does not exists.
|
139
|
-
Log.debug1("Writing to: #{path}")
|
140
|
-
Log.debug1("Creating directory: #{File.dirname(path)}")
|
141
|
-
FileUtils.makedirs(File.dirname(path))
|
142
|
-
file = File.new(path, 'wb')
|
143
|
-
@streams[file_checksum] = Stream.new(file_checksum, path, file, file_size)
|
144
|
-
rescue IOError => e
|
145
|
-
Log.warning("Could not stream write to local file #{path}. #{e.to_s}")
|
146
|
-
end
|
147
|
-
end
|
167
|
+
handle_new_stream(file_checksum, file_size)
|
148
168
|
end
|
149
169
|
# We still check @streams has the key, because file can fail to open.
|
150
170
|
if @streams.key?(file_checksum)
|
151
|
-
|
152
|
-
|
153
|
-
|
171
|
+
return handle_new_chunk(file_checksum, offset, content)
|
172
|
+
else
|
173
|
+
Log.warning('Cannot handle chunk, stream does not exists, sending abort.')
|
174
|
+
@file_abort_clb.call(file_checksum) unless @file_abort_clb.nil?
|
175
|
+
return false
|
154
176
|
end
|
177
|
+
# If last chunk copy.
|
155
178
|
elsif content.nil? && content_checksum.nil?
|
156
|
-
|
157
|
-
|
158
|
-
local_path = @streams[file_checksum].path
|
159
|
-
Stream.close_delete_stream(file_checksum, @streams)
|
160
|
-
|
161
|
-
local_file_checksum = FileIndexing::IndexAgent.get_checksum(local_path)
|
162
|
-
message = "Local checksum (#{local_file_checksum}) received checksum (#{file_checksum})."
|
163
|
-
Log.info(message) ? local_file_checksum == file_checksum : Log.error(message)
|
164
|
-
Log.info("File fully received #{local_path}")
|
165
|
-
@file_done_clb.call(local_file_checksum, local_path) unless @file_done_clb.nil?
|
166
|
-
end
|
179
|
+
handle_last_chunk(file_checksum)
|
180
|
+
return false
|
167
181
|
else
|
168
182
|
Log.warning("Unexpected receive chuck message. file_checksum:#{file_checksum}, " \
|
169
183
|
"content.nil?:#{content.nil?}, content_checksum:#{content_checksum}")
|
184
|
+
return false
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# open new stream
|
189
|
+
def handle_new_stream(file_checksum, file_size)
|
190
|
+
# final destination path
|
191
|
+
tmp_path = FileReceiver.destination_filename(
|
192
|
+
File.join(Params['backup_destination_folder'], 'tmp'),
|
193
|
+
file_checksum)
|
194
|
+
path = FileReceiver.destination_filename(Params['backup_destination_folder'],
|
195
|
+
file_checksum)
|
196
|
+
if File.exists?(path)
|
197
|
+
Log.warning("File already exists (#{path}) not writing.")
|
198
|
+
@file_abort_clb.call(file_checksum) unless @file_abort_clb.nil?
|
199
|
+
else
|
200
|
+
# the file will be moved from tmp location once the transfer will be done
|
201
|
+
# system will use the checksum and some more unique key for tmp file name
|
202
|
+
FileUtils.makedirs(File.dirname(tmp_path)) unless File.directory?(File.dirname(tmp_path))
|
203
|
+
tmp_file = file = File.new(tmp_path, 'wb')
|
204
|
+
@streams[file_checksum] = Stream.new(file_checksum, tmp_path, tmp_file, file_size)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# write chunk to temp file
|
209
|
+
def handle_new_chunk(file_checksum, offset, content)
|
210
|
+
if offset == @streams[file_checksum].file.pos
|
211
|
+
FileReceiver.write_string_to_file(content, @streams[file_checksum].file)
|
212
|
+
Log.info("Written already #{@streams[file_checksum].file.pos} bytes, " \
|
213
|
+
"out of #{@streams[file_checksum].size} " \
|
214
|
+
"(#{100.0*@streams[file_checksum].file.size/@streams[file_checksum].size}%)")
|
215
|
+
return true
|
216
|
+
else
|
217
|
+
# Offset is wrong, send reset/resume copy from correct offset.
|
218
|
+
Log.warning("Received chunk with incorrect offset #{offset}, should " \
|
219
|
+
"be #{@streams[file_checksum].file.pos}, file_checksum:#{file_checksum}")
|
220
|
+
@reset_copy.call(file_checksum, @streams[file_checksum].file.pos) unless @reset_copy.nil?
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# copy file to permanent location
|
226
|
+
# close stream
|
227
|
+
# remove temp file
|
228
|
+
# check written file
|
229
|
+
def handle_last_chunk(file_checksum)
|
230
|
+
if @streams.key?(file_checksum)
|
231
|
+
# Make the directory if does not exists.
|
232
|
+
path = FileReceiver.destination_filename(Params['backup_destination_folder'],
|
233
|
+
file_checksum)
|
234
|
+
Log.debug1("Moving tmp file #{@streams[file_checksum].path} to #{path}")
|
235
|
+
Log.debug1("Creating directory: #{path}")
|
236
|
+
file_dir = File.dirname(path)
|
237
|
+
FileUtils.makedirs(file_dir) unless File.directory?(file_dir)
|
238
|
+
# Move tmp file to permanent location.
|
239
|
+
tmp_file_path = @streams[file_checksum].path
|
240
|
+
Stream.close_delete_stream(file_checksum, @streams) # temp file will be closed here
|
241
|
+
|
242
|
+
local_file_checksum = FileIndexing::IndexAgent.get_checksum(tmp_file_path)
|
243
|
+
message = "Local checksum (#{local_file_checksum}) received checksum (#{file_checksum})."
|
244
|
+
if local_file_checksum == file_checksum
|
245
|
+
Log.info(message)
|
246
|
+
begin
|
247
|
+
File.rename(tmp_file_path, path)
|
248
|
+
Log.info("End move tmp file to permanent location #{path}.")
|
249
|
+
@file_done_clb.call(local_file_checksum, path) unless @file_done_clb.nil?
|
250
|
+
rescue IOError => e
|
251
|
+
Log.warning("Could not move tmp file to permanent file #{path}. #{e.to_s}")
|
252
|
+
end
|
253
|
+
else
|
254
|
+
Log.error(message)
|
255
|
+
Log.debug1("Deleting tmp file: #{tmp_file_path}")
|
256
|
+
File.delete(tmp_file_path)
|
257
|
+
end
|
258
|
+
else
|
259
|
+
Log.error("Handling last chunk and tmp stream does not exists.")
|
170
260
|
end
|
171
261
|
end
|
172
262
|
|
173
263
|
def self.write_string_to_file(str, file)
|
174
|
-
Log.info("writing to file: #{file.to_s}.")
|
175
264
|
bytes_to_write = str.bytesize
|
265
|
+
Log.info("writing to file: #{file.to_s}, #{bytes_to_write} bytes.")
|
176
266
|
while bytes_to_write > 0
|
177
267
|
bytes_to_write -= file.write(str)
|
178
268
|
end
|
@@ -185,6 +275,7 @@ module BBFS
|
|
185
275
|
File.join(folder, sha1[0,2], sha1[2,2], sha1)
|
186
276
|
end
|
187
277
|
|
278
|
+
private :handle_new_stream, :handle_new_chunk, :handle_last_chunk
|
188
279
|
end
|
189
280
|
|
190
281
|
end
|
@@ -14,7 +14,9 @@ module BBFS
|
|
14
14
|
:COPY_MESSAGE
|
15
15
|
:SEND_COPY_MESSAGE
|
16
16
|
:COPY_CHUNK
|
17
|
-
:
|
17
|
+
:COPY_CHUNK_FROM_REMOTE
|
18
|
+
:ABORT_COPY # Asks the sender to abort file copy.
|
19
|
+
:RESET_RESUME_COPY # Sends the stream sender to resend chunk or resume from different offset.
|
18
20
|
|
19
21
|
# Simple copier, gets inputs events (files to copy), requests ack from backup to copy
|
20
22
|
# then copies one file.
|
@@ -52,9 +54,12 @@ module BBFS
|
|
52
54
|
Log.info "Copy file event: #{message_content}"
|
53
55
|
# Prepare source,dest map for copy.
|
54
56
|
message_content.instances.each { |key, instance|
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
# If not already sending.
|
58
|
+
if !@copy_prepare.key?(instance.checksum) || !@copy_prepare[instance.checksum][1]
|
59
|
+
@copy_prepare[instance.checksum] = [instance.full_path, false]
|
60
|
+
Log.info("Sending ack for: #{instance.checksum}")
|
61
|
+
@backup_tcp.send_obj([:ACK_MESSAGE, [instance.checksum, Time.now.to_i]])
|
62
|
+
end
|
58
63
|
}
|
59
64
|
elsif message_type == :ACK_MESSAGE
|
60
65
|
# Received ack from backup, copy file if all is good.
|
@@ -66,20 +71,28 @@ module BBFS
|
|
66
71
|
|
67
72
|
# Copy file if ack (does not exists on backup and not too much time passed)
|
68
73
|
if ack && (Time.now.to_i - timestamp < Params['ack_timeout'])
|
69
|
-
if !@copy_prepare.key?(checksum)
|
70
|
-
Log.warning("
|
74
|
+
if !@copy_prepare.key?(checksum) || @copy_prepare[checksum][1]
|
75
|
+
Log.warning("File was aborted, copied, or started copy just now: #{checksum}")
|
71
76
|
else
|
72
|
-
path = @copy_prepare[checksum]
|
77
|
+
path = @copy_prepare[checksum][0]
|
73
78
|
Log.info "Streaming file: #{checksum} #{path}."
|
74
79
|
@file_streamer.start_streaming(checksum, path)
|
80
|
+
# Ack received, setting prepare to true
|
81
|
+
@copy_prepare[checksum][1] = true
|
75
82
|
end
|
76
83
|
else
|
77
84
|
Log.debug1("Ack timed out span: #{Time.now.to_i - timestamp} > " \
|
78
85
|
"timeout: #{Params['ack_timeout']}")
|
79
86
|
end
|
87
|
+
elsif message_type == :COPY_CHUNK_FROM_REMOTE
|
88
|
+
checksum = message_content
|
89
|
+
@file_streamer.copy_another_chuck(checksum)
|
80
90
|
elsif message_type == :COPY_CHUNK
|
81
|
-
|
82
|
-
|
91
|
+
# We open the message here for printing info and deleting copy_prepare!
|
92
|
+
file_checksum, offset, file_size, content, content_checksum = message_content
|
93
|
+
Log.info("Send chunk for file #{file_checksum}, offset: #{offset} " \
|
94
|
+
"filesize: #{file_size}.")
|
95
|
+
# Blocking send.
|
83
96
|
@backup_tcp.send_obj([:COPY_CHUNK, message_content])
|
84
97
|
if content.nil? and content_checksum.nil?
|
85
98
|
@copy_prepare.delete(file_checksum)
|
@@ -87,14 +100,19 @@ module BBFS
|
|
87
100
|
elsif message_type == :ABORT_COPY
|
88
101
|
Log.info("Aborting file copy: #{message_content}")
|
89
102
|
if @copy_prepare.key?(message_content)
|
90
|
-
Log.info("Aborting: #{@copy_prepare[message_content]}")
|
103
|
+
Log.info("Aborting: #{@copy_prepare[message_content][0]}")
|
91
104
|
@copy_prepare.delete(message_content)
|
92
105
|
end
|
93
106
|
@file_streamer.abort_streaming(message_content)
|
107
|
+
elsif message_type == :RESET_RESUME_COPY
|
108
|
+
file_checksum, new_offset = message_content
|
109
|
+
Log.info("Resetting/Resuming file (#{file_checksum}) copy to #{new_offset}")
|
110
|
+
@file_streamer.reset_streaming(file_checksum, new_offset)
|
94
111
|
else
|
95
112
|
Log.error("Copy event not supported: #{message_type}")
|
96
113
|
end # handle messages here
|
97
114
|
end
|
115
|
+
Log.error("Should not reach here, loop should continue.")
|
98
116
|
end
|
99
117
|
end
|
100
118
|
end # class QueueCopy
|
@@ -104,12 +122,15 @@ module BBFS
|
|
104
122
|
@local_queue = Queue.new
|
105
123
|
@dynamic_content_data = dynamic_content_data
|
106
124
|
@tcp_server = Networking::TCPClient.new(host, port, method(:handle_message))
|
107
|
-
@file_receiver = FileReceiver.new(method(:done_copy),
|
125
|
+
@file_receiver = FileReceiver.new(method(:done_copy),
|
126
|
+
method(:abort_copy),
|
127
|
+
method(:reset_copy))
|
108
128
|
@local_thread = Thread.new do
|
109
129
|
loop do
|
110
130
|
handle(@local_queue.pop)
|
111
131
|
end
|
112
132
|
end
|
133
|
+
@local_thread.abort_on_exception = true
|
113
134
|
end
|
114
135
|
|
115
136
|
def threads
|
@@ -126,6 +147,10 @@ module BBFS
|
|
126
147
|
handle_message([:ABORT_COPY, checksum])
|
127
148
|
end
|
128
149
|
|
150
|
+
def reset_copy(checksum, new_offset)
|
151
|
+
handle_message([:RESET_RESUME_COPY, [checksum, new_offset]])
|
152
|
+
end
|
153
|
+
|
129
154
|
def done_copy(local_file_checksum, local_path)
|
130
155
|
Log.info("Done copy file: #{local_path}, #{local_file_checksum}")
|
131
156
|
end
|
@@ -145,7 +170,11 @@ module BBFS
|
|
145
170
|
bytes_written = @tcp_server.send_obj([:COPY_MESSAGE, message_content])
|
146
171
|
Log.debug1("Sending copy message succeeded? bytes_written: #{bytes_written}.")
|
147
172
|
elsif message_type == :COPY_CHUNK
|
148
|
-
|
173
|
+
Log.debug1('Chunk received.')
|
174
|
+
if @file_receiver.receive_chunk(*message_content)
|
175
|
+
file_checksum, offset, file_size, content, content_checksum = message_content
|
176
|
+
@tcp_server.send_obj([:COPY_CHUNK_FROM_REMOTE, file_checksum])
|
177
|
+
end
|
149
178
|
elsif message_type == :ACK_MESSAGE
|
150
179
|
checksum, timestamp = message_content
|
151
180
|
# Here we should check file existence
|
@@ -156,6 +185,8 @@ module BBFS
|
|
156
185
|
checksum]])
|
157
186
|
elsif message_type == :ABORT_COPY
|
158
187
|
@tcp_server.send_obj([:ABORT_COPY, message_content])
|
188
|
+
elsif message_type == :RESET_RESUME_COPY
|
189
|
+
@tcp_server.send_obj([:RESET_RESUME_COPY, message_content])
|
159
190
|
else
|
160
191
|
Log.error("Unexpected message type: #{message_type}")
|
161
192
|
end
|
@@ -1,96 +1,97 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'content_data/dynamic_content_data'
|
4
|
-
require 'log'
|
5
|
-
require 'networking/tcp'
|
6
|
-
require 'params'
|
7
|
-
|
8
|
-
module BBFS
|
9
|
-
module ContentServer
|
10
|
-
|
11
|
-
Params.integer('remote_content_timeout', 10, 'Remote content desired freshness in seconds.')
|
12
|
-
Params.integer('max_content_timeout', 60*60, 'Remote content force refresh in seconds.')
|
13
|
-
|
14
|
-
# TODO(kolman): Use only one tcp/ip socket by utilizing one NQueue for many queues!
|
15
|
-
class RemoteContent
|
16
|
-
def initialize(dynamic_content_data, host, port, local_backup_folder)
|
17
|
-
@dynamic_content_data = dynamic_content_data
|
18
|
-
@remote_tcp = Networking::TCPClient.new(host, port, method(:receive_content))
|
19
|
-
@last_update_timestamp = nil
|
20
|
-
@content_server_content_data_path = File.join(local_backup_folder, 'remote',
|
21
|
-
host + '_' + port.to_s)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
Log.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
threads
|
51
|
-
threads <<
|
52
|
-
|
53
|
-
|
54
|
-
if
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
nil
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'content_data/dynamic_content_data'
|
4
|
+
require 'log'
|
5
|
+
require 'networking/tcp'
|
6
|
+
require 'params'
|
7
|
+
|
8
|
+
module BBFS
|
9
|
+
module ContentServer
|
10
|
+
|
11
|
+
Params.integer('remote_content_timeout', 10, 'Remote content desired freshness in seconds.')
|
12
|
+
Params.integer('max_content_timeout', 60*60, 'Remote content force refresh in seconds.')
|
13
|
+
|
14
|
+
# TODO(kolman): Use only one tcp/ip socket by utilizing one NQueue for many queues!
|
15
|
+
class RemoteContent
|
16
|
+
def initialize(dynamic_content_data, host, port, local_backup_folder)
|
17
|
+
@dynamic_content_data = dynamic_content_data
|
18
|
+
@remote_tcp = Networking::TCPClient.new(host, port, method(:receive_content))
|
19
|
+
@last_update_timestamp = nil
|
20
|
+
@content_server_content_data_path = File.join(local_backup_folder, 'remote',
|
21
|
+
host + '_' + port.to_s)
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive_content(message)
|
25
|
+
Log.debug1("Remote content data received: #{message.to_s}")
|
26
|
+
ref = @dynamic_content_data.last_content_data
|
27
|
+
@dynamic_content_data.update(message)
|
28
|
+
|
29
|
+
max_time_span = Params['max_content_timeout']
|
30
|
+
if !@last_update_timestamp.nil?
|
31
|
+
max_time_span = Time.now.to_i - @last_update_timestamp
|
32
|
+
end
|
33
|
+
|
34
|
+
@last_update_timestamp = Time.now.to_i
|
35
|
+
|
36
|
+
if ref != message || max_time_span >= Params['max_content_timeout']
|
37
|
+
Log.debug2("Remote content data changed or max time span is large, writing.")
|
38
|
+
Log.debug3("max_time_span: #{max_time_span}")
|
39
|
+
write_to = File.join(@content_server_content_data_path,
|
40
|
+
@last_update_timestamp.to_s + '.cd')
|
41
|
+
FileUtils.makedirs(@content_server_content_data_path) unless \
|
42
|
+
File.directory?(@content_server_content_data_path)
|
43
|
+
count = File.open(write_to, 'wb') { |f| f.write(message.to_s) }
|
44
|
+
else
|
45
|
+
Log.debug2("No need to write remote content data, it has not changed.")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def run()
|
50
|
+
threads = []
|
51
|
+
threads << @remote_tcp.tcp_thread if @remote_tcp != nil
|
52
|
+
threads << Thread.new do
|
53
|
+
loop do
|
54
|
+
# if need content data
|
55
|
+
if @last_update_timestamp.nil?
|
56
|
+
sleep_time_span = Params['remote_content_timeout']
|
57
|
+
else
|
58
|
+
sleep_time_span = Time.now.to_i - @last_update_timestamp
|
59
|
+
end
|
60
|
+
|
61
|
+
if sleep_time_span >= Params['remote_content_timeout']
|
62
|
+
# Send ping!
|
63
|
+
Log.debug2('Sending remote contend request.')
|
64
|
+
bytes_written = @remote_tcp.send_obj(nil)
|
65
|
+
Log.debug3("Bytes written #{bytes_written}.")
|
66
|
+
end
|
67
|
+
|
68
|
+
sleep_time_span = Time.now.to_i - @last_update_timestamp \
|
69
|
+
unless @last_update_timestamp.nil?
|
70
|
+
Log.debug2("sleep_time_span: #{sleep_time_span}")
|
71
|
+
sleep(sleep_time_span) if sleep_time_span > 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class RemoteContentClient
|
78
|
+
def initialize(dynamic_content_data, port)
|
79
|
+
@dynamic_content_data = dynamic_content_data
|
80
|
+
@tcp_server = Networking::TCPServer.new(port, method(:content_requested))
|
81
|
+
end
|
82
|
+
|
83
|
+
def content_requested(addr_info, message)
|
84
|
+
# Send response.
|
85
|
+
Log.debug1('Local content data requested.')
|
86
|
+
@tcp_server.send_obj(@dynamic_content_data.last_content_data)
|
87
|
+
end
|
88
|
+
|
89
|
+
def tcp_thread
|
90
|
+
return @tcp_server.tcp_thread if @tcp_server != nil
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -5,7 +5,7 @@ require 'stringio'
|
|
5
5
|
require_relative '../../lib/content_server/file_streamer'
|
6
6
|
|
7
7
|
# Uncomment to debug spec.
|
8
|
-
|
8
|
+
BBFS::Params['log_write_to_console'] = false
|
9
9
|
BBFS::Params['log_write_to_file'] = false
|
10
10
|
BBFS::Params['log_debug_level'] = 0
|
11
11
|
BBFS::Params['streaming_chunk_size'] = 5
|
@@ -21,8 +21,18 @@ module BBFS
|
|
21
21
|
Log.info('#0 start')
|
22
22
|
orig_file = StringIO.new('Some content. Some content. Some content. Some content.')
|
23
23
|
Log.info("orig_file #{orig_file.to_s}.")
|
24
|
+
|
25
|
+
# should simulate Tempfile object, thus need to add to this object Tempfile methsods
|
26
|
+
# that are absent in StringIO, but used in tested ruby code
|
24
27
|
dest_file = StringIO.new
|
28
|
+
def dest_file.path
|
29
|
+
'/tmp/path/tmp_basename'
|
30
|
+
end
|
31
|
+
def dest_file.unlink
|
32
|
+
true
|
33
|
+
end
|
25
34
|
Log.info("dest_file #{dest_file.to_s}.")
|
35
|
+
|
26
36
|
streamer = nil
|
27
37
|
done = lambda{ |checksum, filename|
|
28
38
|
Log.info('#4 streaming done, check content ok.')
|
@@ -31,13 +41,20 @@ module BBFS
|
|
31
41
|
Log.info('#5 exiting streamer thread.')
|
32
42
|
streamer.thread.exit
|
33
43
|
}
|
44
|
+
|
34
45
|
receiver = BBFS::ContentServer::FileReceiver.new(done)
|
35
46
|
send_chunk = lambda { |*args|
|
36
47
|
receiver.receive_chunk(*args)
|
48
|
+
streamer.copy_another_chuck('da39a3ee5e6b4b0d3255bfef95601890afd80709')
|
37
49
|
}
|
50
|
+
|
38
51
|
Log.info('#2 start streaming.')
|
39
|
-
# This is for
|
52
|
+
# This is for FileStreamer :NEW_STREAM and FileReceiver :receive_chunk
|
40
53
|
::File.stub(:new).and_return(orig_file, dest_file)
|
54
|
+
::FileUtils.stub(:makedirs).and_return(true)
|
55
|
+
::FileUtils.stub(:copy_file).and_return(true)
|
56
|
+
# This is for FileReceiver :handle_last_chunk
|
57
|
+
::File.stub(:rename)
|
41
58
|
# This is for Index agent 'get_checksum' which opens file, read content and validates
|
42
59
|
# checksum.
|
43
60
|
::File.stub(:open).and_return(dest_file)
|
@@ -46,6 +63,7 @@ module BBFS
|
|
46
63
|
Log.info('#3 start streaming.')
|
47
64
|
streamer.start_streaming('da39a3ee5e6b4b0d3255bfef95601890afd80709', 'dummy')
|
48
65
|
streamer.thread.join()
|
66
|
+
sleep Params['log_param_max_elapsed_time_in_seconds_from_last_flush'] + 1
|
49
67
|
end
|
50
68
|
end
|
51
69
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: content_server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.9a
|
5
|
+
prerelease: 5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Gena Petelko, Kolman Vornovitsky
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: content_data
|
@@ -157,12 +157,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
158
|
none: false
|
159
159
|
requirements:
|
160
|
-
- - ! '
|
160
|
+
- - ! '>'
|
161
161
|
- !ruby/object:Gem::Version
|
162
|
-
version:
|
162
|
+
version: 1.3.1
|
163
163
|
requirements: []
|
164
164
|
rubyforge_project:
|
165
|
-
rubygems_version: 1.8.
|
165
|
+
rubygems_version: 1.8.24
|
166
166
|
signing_key:
|
167
167
|
specification_version: 3
|
168
168
|
summary: Servers for backing up content.
|