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
- 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.