content_server 0.0.8 → 0.0.9a

Sign up to get free protection for your applications and to get access to all the features.
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
- rescue LoadError => e
13
- print e.to_s
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', 64*1024,
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
- if !@streams.key? checksum
78
- begin
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
- Stream.close_delete_stream(content, @streams)
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
- Log.error(comment) if content_checksum != received_content_checksum
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
- path = FileReceiver.destination_filename(Params['backup_destination_folder'],
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
- FileReceiver.write_string_to_file(content, @streams[file_checksum].file)
152
- Log.info("Written already #{@streams[file_checksum].file.size} bytes, " \
153
- "out of #{file_size} (#{100.0*@streams[file_checksum].file.size/file_size}%)")
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
- if @streams.key?(file_checksum)
157
- # Check written file checksum!
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
- :ABORT_COPY
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
- @copy_prepare[instance.checksum] = instance.full_path
56
- Log.info("Sending ack for: #{instance.checksum}")
57
- @backup_tcp.send_obj([:ACK_MESSAGE, [instance.checksum, Time.now.to_i]])
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("Ack was already received:#{checksum}")
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
- file_checksum, file_size, content, content_checksum = message_content
82
- Log.info "Send chunk for file #{file_checksum}."
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), method(:abort_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
- @file_receiver.receive_chunk(*message_content)
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
- FileUtils.makedirs(@content_server_content_data_path)
23
- end
24
-
25
- def receive_content(message)
26
- Log.debug1("Remote content data received: #{message.to_s}")
27
- ref = @dynamic_content_data.last_content_data
28
- @dynamic_content_data.update(message)
29
-
30
- max_time_span = Params['max_content_timeout']
31
- if !@last_update_timestamp.nil?
32
- max_time_span = Time.now.to_i - @last_update_timestamp
33
- end
34
-
35
- @last_update_timestamp = Time.now.to_i
36
-
37
- if ref != message || max_time_span >= Params['max_content_timeout']
38
- Log.debug2("Remote content data changed or max time span is large, writing.")
39
- Log.debug3("max_time_span: #{max_time_span}")
40
- write_to = File.join(@content_server_content_data_path,
41
- @last_update_timestamp.to_s + '.cd')
42
- count = File.open(write_to, 'wb') { |f| f.write(message.to_s) }
43
- else
44
- Log.debug2("No need to write remote content data, it has not changed.")
45
- end
46
- end
47
-
48
- def run()
49
- threads = []
50
- threads << @remote_tcp.tcp_thread if @remote_tcp != nil
51
- threads << Thread.new do
52
- loop do
53
- # if need content data
54
- if @last_update_timestamp.nil?
55
- sleep_time_span = Params['remote_content_timeout']
56
- else
57
- sleep_time_span = Time.now.to_i - @last_update_timestamp
58
- end
59
-
60
- if sleep_time_span >= Params['remote_content_timeout']
61
- # Send ping!
62
- Log.debug2('Sending remote contend request.')
63
- bytes_written = @remote_tcp.send_obj(nil)
64
- Log.debug3("Bytes written #{bytes_written}.")
65
- end
66
-
67
- sleep_time_span = Time.now.to_i - @last_update_timestamp \
68
- unless @last_update_timestamp.nil?
69
- Log.debug2("sleep_time_span: #{sleep_time_span}")
70
- sleep(sleep_time_span) if sleep_time_span > 0
71
- end
72
- end
73
- end
74
- end
75
-
76
- class RemoteContentClient
77
- def initialize(dynamic_content_data, port)
78
- @dynamic_content_data = dynamic_content_data
79
- @tcp_server = Networking::TCPServer.new(port, method(:content_requested))
80
- end
81
-
82
- def content_requested(addr_info, message)
83
- # Send response.
84
- Log.debug1('Local content data requested.')
85
- @tcp_server.send_obj(@dynamic_content_data.last_content_data)
86
- end
87
-
88
- def tcp_thread
89
- return @tcp_server.tcp_thread if @tcp_server != nil
90
- nil
91
- end
92
-
93
- end
94
-
95
- end
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
@@ -1,5 +1,5 @@
1
1
  module BBFS
2
2
  module ContentServer
3
- VERSION = "0.0.8"
3
+ VERSION = "0.0.9a"
4
4
  end
5
5
  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
- #BBFS::Params['log_write_to_console'] = true
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 1) FileStreamer :NEW_STREAM and 2) FileReceiver receive_chunk.
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.8
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-02 00:00:00.000000000 Z
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: '0'
162
+ version: 1.3.1
163
163
  requirements: []
164
164
  rubyforge_project:
165
- rubygems_version: 1.8.23
165
+ rubygems_version: 1.8.24
166
166
  signing_key:
167
167
  specification_version: 3
168
168
  summary: Servers for backing up content.