Commit 3a99a6b9 authored by Shinya Maeda's avatar Shinya Maeda

Consolidate ChunkedIO

parent ebf69adc
...@@ -22,18 +22,32 @@ module Gitlab ...@@ -22,18 +22,32 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
# Write data to chunk store. Always overwrite.
#
# @param [String] data
# @return [Fixnum] length of the data after writing
def write!(data) def write!(data)
raise NotImplementedError raise NotImplementedError
end end
# Append data to chunk store
#
# @param [String] data
# @return [Fixnum] length of the appended
def append!(data) def append!(data)
raise NotImplementedError raise NotImplementedError
end end
# Truncate data to chunk store
#
# @param [String] offset
def truncate!(offset) def truncate!(offset)
raise NotImplementedError raise NotImplementedError
end end
# Delete data from chunk store
#
# @param [String] offset
def delete! def delete!
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -48,6 +48,8 @@ module Gitlab ...@@ -48,6 +48,8 @@ module Gitlab
end end
def get def get
puts "#{self.class.name} - #{__callee__}: params[:chunk_index]: #{params[:chunk_index]}"
job_trace_chunk.data job_trace_chunk.data
end end
...@@ -56,9 +58,10 @@ module Gitlab ...@@ -56,9 +58,10 @@ module Gitlab
end end
def write!(data) def write!(data)
raise NotImplementedError, 'Partial writing is not supported' unless params[:buffer_size] == data&.length
raise NotImplementedError, 'UPDATE (Overwriting data) is not supported' if job_trace_chunk.data
puts "#{self.class.name} - #{__callee__}: data.length: #{data.length.inspect} params[:chunk_index]: #{params[:chunk_index]}" puts "#{self.class.name} - #{__callee__}: data.length: #{data.length.inspect} params[:chunk_index]: #{params[:chunk_index]}"
raise NotImplementedError, 'Partial write is not supported' unless params[:buffer_size] == data&.length
raise NotImplementedError, 'UPDATE is not supported' if job_trace_chunk.data
job_trace_chunk.data = data job_trace_chunk.data = data
job_trace_chunk.save! job_trace_chunk.save!
...@@ -75,7 +78,10 @@ module Gitlab ...@@ -75,7 +78,10 @@ module Gitlab
end end
def delete! def delete!
raise ActiveRecord::RecordNotFound, 'Could not find deletable record' unless job_trace_chunk.persisted?
puts "#{self.class.name} - #{__callee__}: params[:chunk_index]: #{params[:chunk_index]}" puts "#{self.class.name} - #{__callee__}: params[:chunk_index]: #{params[:chunk_index]}"
job_trace_chunk.destroy! job_trace_chunk.destroy!
end end
end end
......
...@@ -51,6 +51,9 @@ module Gitlab ...@@ -51,6 +51,9 @@ module Gitlab
end end
end end
BufferKeyNotFoundError = Class.new(StandardError)
WriteError = Class.new(StandardError)
attr_reader :buffer_key attr_reader :buffer_key
def initialize(buffer_key, **params) def initialize(buffer_key, **params)
...@@ -64,6 +67,8 @@ module Gitlab ...@@ -64,6 +67,8 @@ module Gitlab
end end
def get def get
puts "#{self.class.name} - #{__callee__}: params[:chunk_index]: #{params[:chunk_index]}"
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
redis.get(buffer_key) redis.get(buffer_key)
end end
...@@ -76,35 +81,47 @@ module Gitlab ...@@ -76,35 +81,47 @@ module Gitlab
end end
def write!(data) def write!(data)
raise ArgumentError, 'Could not write empty data' unless data.present?
puts "#{self.class.name} - #{__callee__}: data.length: #{data.length.inspect} params[:chunk_index]: #{params[:chunk_index]}" puts "#{self.class.name} - #{__callee__}: data.length: #{data.length.inspect} params[:chunk_index]: #{params[:chunk_index]}"
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
redis.set(buffer_key, data) unless redis.set(buffer_key, data) == 'OK'
raise WriteError, 'Failed to write'
end
redis.strlen(buffer_key) redis.strlen(buffer_key)
end end
end end
def append!(data) def append!(data)
raise ArgumentError, 'Could not write empty data' unless data.present?
puts "#{self.class.name} - #{__callee__}: data.length: #{data.length.inspect} params[:chunk_index]: #{params[:chunk_index]}" puts "#{self.class.name} - #{__callee__}: data.length: #{data.length.inspect} params[:chunk_index]: #{params[:chunk_index]}"
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
redis.append(buffer_key, data) raise BufferKeyNotFoundError, 'Buffer key is not found' unless redis.exists(buffer_key)
data.length
original_size = size
new_size = redis.append(buffer_key, data)
appended_size = new_size - original_size
raise WriteError, 'Failed to append' unless appended_size == data.length
appended_size
end end
end end
def truncate!(offset) def truncate!(offset)
puts "#{self.class.name} - #{__callee__}: offset: #{offset.inspect} params[:chunk_index]: #{params[:chunk_index]}" raise NotImplementedError
Gitlab::Redis::Cache.with do |redis|
return 0 unless redis.exists(buffer_key)
truncated_data = redis.getrange(buffer_key, 0, offset)
redis.set(buffer_key, truncated_data)
end
end end
def delete! def delete!
puts "#{self.class.name} - #{__callee__}: params[:chunk_index]: #{params[:chunk_index]}" puts "#{self.class.name} - #{__callee__}: params[:chunk_index]: #{params[:chunk_index]}"
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
redis.del(buffer_key) raise BufferKeyNotFoundError, 'Buffer key is not found' unless redis.exists(buffer_key)
unless redis.del(buffer_key) == 1
raise WriteError, 'Failed to delete'
end
end end
end end
end end
......
...@@ -8,7 +8,7 @@ module Gitlab ...@@ -8,7 +8,7 @@ module Gitlab
class Trace class Trace
module ChunkedFile module ChunkedFile
class ChunkedIO class ChunkedIO
extend ChunkedFile::Concerns::Opener # extend ChunkedFile::Concerns::Opener
include ChunkedFile::Concerns::Errors include ChunkedFile::Concerns::Errors
include ChunkedFile::Concerns::Hooks include ChunkedFile::Concerns::Hooks
include ChunkedFile::Concerns::Callbacks include ChunkedFile::Concerns::Callbacks
...@@ -22,13 +22,21 @@ module Gitlab ...@@ -22,13 +22,21 @@ module Gitlab
alias_method :pos, :tell alias_method :pos, :tell
def initialize(job_id, size, mode = 'rb') def initialize(job_id, size = nil, mode = 'rb', &block)
@size = size raise NotImplementedError, "Mode 'w' is not supported" if mode.include?('w')
@size = size || calculate_size(job_id)
@tell = 0 @tell = 0
@job_id = job_id @job_id = job_id
@mode = mode @mode = mode
raise NotImplementedError, "Mode 'w' is not supported" if mode.include?('w') if block_given?
begin
yield self
ensure
self.close
end
end
end end
def close def close
...@@ -128,7 +136,7 @@ module Gitlab ...@@ -128,7 +136,7 @@ module Gitlab
end end
def present? def present?
chunk_store.chunks_count(job_id) > 0 chunks_count > 0
end end
def delete def delete
...@@ -177,19 +185,21 @@ module Gitlab ...@@ -177,19 +185,21 @@ module Gitlab
end end
def write_chunk(data) def write_chunk(data)
written_size = 0
chunk_store.open(job_id, chunk_index, params_for_store) do |store| chunk_store.open(job_id, chunk_index, params_for_store) do |store|
with_callbacks(:write_chunk, store) do with_callbacks(:write_chunk, store) do
written_size = if buffer_size == data.length written_size = if buffer_size == data.length || store.size == 0
store.write!(data) store.write!(data)
else else
store.append!(data) store.append!(data)
end end
raise WriteError, 'Written size mismatch' unless data.length == written_size raise WriteError, 'Written size mismatch' unless data.length == written_size
written_size
end end
end end
written_size
end end
def truncate_chunk(offset) def truncate_chunk(offset)
...@@ -228,7 +238,7 @@ module Gitlab ...@@ -228,7 +238,7 @@ module Gitlab
end end
def chunks_count def chunks_count
(size / buffer_size) (size / buffer_size.to_f).ceil
end end
def first_chunk? def first_chunk?
...@@ -246,6 +256,10 @@ module Gitlab ...@@ -246,6 +256,10 @@ module Gitlab
def buffer_size def buffer_size
raise NotImplementedError raise NotImplementedError
end end
def calculate_size(job_id)
chunk_store.chunks_size(job_id)
end
end end
end end
end end
......
module Gitlab
module Ci
class Trace
module ChunkedFile
module Concerns
module Hooks
extend ActiveSupport::Concern
included do
class_attribute :_before_methods, :_after_methods,
:instance_writer => false
self._before_methods = Hash.new []
self._after_methods = Hash.new []
end
class_methods do
def before_method(kind, callback)
self._before_methods = self._before_methods.
merge kind => _before_methods[kind] + [callback]
end
def after_method(kind, callback)
self._after_methods = self._after_methods.
merge kind => _after_methods[kind] + [callback]
end
end
def method_added(method_name)
return if self.class._before_methods.values.include?(method_name)
return if self.class._after_methods.values.include?(method_name)
return if hooked_methods.include?(method_name)
add_hooks_to(method_name)
end
private
def hooked_methods
@hooked_methods ||= []
end
def add_hooks_to(method_name)
hooked_methods << method_name
original_method = instance_method(method_name)
# re-define the method, but notice how we reference the original
# method definition
define_method(method_name) do |*args, &block|
self.class._before_methods[method_name].each { |hook| method(hook).call }
# now invoke the original method
original_method.bind(self).call(*args, &block).tap do
self.class._after_methods[method_name].each { |hook| method(hook).call }
end
end
end
end
end
end
end
end
end
...@@ -6,28 +6,19 @@ module Gitlab ...@@ -6,28 +6,19 @@ module Gitlab
module Permissions module Permissions
extend ActiveSupport::Concern extend ActiveSupport::Concern
WRITABLE_MODE = %w[a]
READABLE_MODE = %w[r +]
included do included do
PermissionError = Class.new(StandardError) PermissionError = Class.new(StandardError)
attr_reader :write_lock_uuid attr_reader :write_lock_uuid
# mode checks
before_method :read, :can_read!
before_method :readline, :can_read!
before_method :each_line, :can_read!
before_method :write, :can_write!
before_method :truncate, :can_write!
# write_lock
before_method :write, :check_lock!
before_method :truncate, :check_lock!
before_method :delete, :check_lock!
end end
def initialize(job_id, size, mode = 'rb') def initialize(job_id, size, mode = 'rb')
if /(w|a)/ =~ mode if WRITABLE_MODE.any? { |m| mode.include?(m) }
@write_lock_uuid = Gitlab::ExclusiveLease @write_lock_uuid = Gitlab::ExclusiveLease
.new(write_lock_key, timeout: 1.hour.to_i).try_obtain .new(write_lock_key(job_id), timeout: 1.hour.to_i).try_obtain
raise PermissionError, 'Already opened by another process' unless write_lock_uuid raise PermissionError, 'Already opened by another process' unless write_lock_uuid
end end
...@@ -37,25 +28,63 @@ module Gitlab ...@@ -37,25 +28,63 @@ module Gitlab
def close def close
if write_lock_uuid if write_lock_uuid
Gitlab::ExclusiveLease.cancel(write_lock_key, write_lock_uuid) Gitlab::ExclusiveLease.cancel(write_lock_key(job_id), write_lock_uuid)
end end
super super
end end
def check_lock! def read(*args)
raise PermissionError, 'Could not modify the file without lock' unless write_lock_uuid can_read!
super
end
def readline(*args)
can_read!
super
end
def each_line(*args)
can_read!
super
end end
def write(*args)
can_write!
super
end
def truncate(*args)
can_write!
super
end
def delete(*args)
can_write!
super
end
private
def can_read! def can_read!
raise IOError, 'not opened for reading' unless /(r|+)/ =~ mode unless READABLE_MODE.any? { |m| mode.include?(m) }
raise IOError, 'not opened for reading'
end
end end
def can_write! def can_write!
raise IOError, 'not opened for writing' unless /(w|a)/ =~ mode unless WRITABLE_MODE.any? { |m| mode.include?(m) }
raise IOError, 'not opened for writing'
end
end end
def write_lock_key def write_lock_key(job_id)
"live_trace:operation:write:#{job_id}" "live_trace:operation:write:#{job_id}"
end end
end end
......
...@@ -12,10 +12,6 @@ module Gitlab ...@@ -12,10 +12,6 @@ module Gitlab
after_callback :write_chunk, :stash_to_database after_callback :write_chunk, :stash_to_database
def initialize(job_id, mode)
super(job_id, calculate_size(job_id), mode)
end
def stash_to_database(store) def stash_to_database(store)
# Once data is filled into redis, move the data to database # Once data is filled into redis, move the data to database
if store.filled? if store.filled?
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
let(:job) { create(:ci_build) }
let(:job_id) { job.id } let(:job_id) { job.id }
let(:chunk_index) { 0 } let(:chunk_index) { 0 }
let(:buffer_size) { 256 } let(:buffer_size) { 256 }
let(:job_trace_chunk) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index) } let(:job_trace_chunk) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index) }
let(:params) { { buffer_size: buffer_size } } let(:params) { { buffer_size: buffer_size } }
let(:trace) { 'A' * buffer_size } let(:data) { 'A' * buffer_size }
let(:job) { create(:ci_build) }
describe '.open' do describe '.open' do
subject { described_class.open(job_id, chunk_index, params) } subject { described_class.open(job_id, chunk_index, params) }
...@@ -35,7 +35,7 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -35,7 +35,7 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -51,17 +51,17 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -51,17 +51,17 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it { is_expected.to eq(1) } it { is_expected.to eq(1) }
context 'when two chunks exists' do context 'when two chunks exists' do
let(:job_trace_chunk_2) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index + 1) } let(:job_trace_chunk_2) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index + 1) }
let(:trace_2) { 'B' * buffer_size } let(:data_2) { 'B' * buffer_size }
before do before do
described_class.new(job_trace_chunk_2, params).write!(trace_2) described_class.new(job_trace_chunk_2, params).write!(data_2)
end end
it { is_expected.to eq(2) } it { is_expected.to eq(2) }
...@@ -78,18 +78,18 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -78,18 +78,18 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it { is_expected.to eq(trace.length) } it { is_expected.to eq(data.length) }
context 'when two chunks exists' do context 'when two chunks exists' do
let(:job_trace_chunk_2) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index + 1) } let(:job_trace_chunk_2) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index + 1) }
let(:trace_2) { 'B' * buffer_size } let(:data_2) { 'B' * buffer_size }
let(:chunks_size) { trace.length + trace_2.length } let(:chunks_size) { data.length + data_2.length }
before do before do
described_class.new(job_trace_chunk_2, params).write!(trace_2) described_class.new(job_trace_chunk_2, params).write!(data_2)
end end
it { is_expected.to eq(chunks_size) } it { is_expected.to eq(chunks_size) }
...@@ -101,15 +101,48 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -101,15 +101,48 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
end end
end end
describe '.delete_all' do
subject { described_class.delete_all(job_id) }
context 'when job_trace_chunk exists' do
before do
described_class.new(job_trace_chunk, params).write!(data)
end
it 'deletes all' do
expect { subject }.to change { described_class.chunks_count(job_id) }.by(-1)
end
context 'when two chunks exists' do
let(:job_trace_chunk_2) { ::Ci::JobTraceChunk.new(job_id: job_id, chunk_index: chunk_index + 1) }
let(:data_2) { 'B' * buffer_size }
before do
described_class.new(job_trace_chunk_2, params).write!(data_2)
end
it 'deletes all' do
expect { subject }.to change { described_class.chunks_count(job_id) }.by(-2)
end
end
end
context 'when buffer_key does not exist' do
it 'deletes all' do
expect { subject }.not_to change { described_class.chunks_count(job_id) }
end
end
end
describe '#get' do describe '#get' do
subject { described_class.new(job_trace_chunk, params).get } subject { described_class.new(job_trace_chunk, params).get }
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it { is_expected.to eq(trace) } it { is_expected.to eq(data) }
end end
context 'when job_trace_chunk does not exist' do context 'when job_trace_chunk does not exist' do
...@@ -122,10 +155,10 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -122,10 +155,10 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it { is_expected.to eq(trace.length) } it { is_expected.to eq(data.length) }
end end
context 'when job_trace_chunk does not exist' do context 'when job_trace_chunk does not exist' do
...@@ -134,45 +167,39 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -134,45 +167,39 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
end end
describe '#write!' do describe '#write!' do
subject { described_class.new(job_trace_chunk, params).write!(trace) } subject { described_class.new(job_trace_chunk, params).write!(data) }
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it { expect { subject }.to raise_error('UPDATE is not supported') } it { expect { subject }.to raise_error('UPDATE (Overwriting data) is not supported') }
end end
context 'when job_trace_chunk does not exist' do context 'when job_trace_chunk does not exist' do
let(:expected_data) { ::Ci::JobTraceChunk.find_by(job_id: job_id, chunk_index: chunk_index).data } let(:expected_data) { ::Ci::JobTraceChunk.find_by(job_id: job_id, chunk_index: chunk_index).data }
it 'writes' do it 'writes' do
is_expected.to eq(trace.length) is_expected.to eq(data.length)
expect(expected_data).to eq(trace) expect(expected_data).to eq(data)
end end
end end
context 'when data is nil' do context 'when data is nil' do
let(:trace) { nil } let(:data) { nil }
it { expect { subject }.to raise_error('Partial write is not supported') } it { expect { subject }.to raise_error('Partial writing is not supported') }
end end
end end
describe '#truncate!' do
subject { described_class.new(job_trace_chunk, params).truncate!(0) }
it { expect { subject }.to raise_error(NotImplementedError) }
end
describe '#delete!' do describe '#delete!' do
subject { described_class.new(job_trace_chunk, params).delete! } subject { described_class.new(job_trace_chunk, params).delete! }
context 'when job_trace_chunk exists' do context 'when job_trace_chunk exists' do
before do before do
described_class.new(job_trace_chunk, params).write!(trace) described_class.new(job_trace_chunk, params).write!(data)
end end
it 'deletes' do it 'deletes' do
...@@ -187,14 +214,8 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do ...@@ -187,14 +214,8 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Database do
end end
context 'when job_trace_chunk does not exist' do context 'when job_trace_chunk does not exist' do
it 'deletes' do it 'raises an error' do
expect(::Ci::JobTraceChunk.exists?(job_id: job_id, chunk_index: chunk_index)) expect { subject }.to raise_error('Could not find deletable record')
.to be_falsy
subject
expect(::Ci::JobTraceChunk.exists?(job_id: job_id, chunk_index: chunk_index))
.to be_falsy
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_cache do describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_cache do
let(:job_id) { 1 } let(:job) { create(:ci_build) }
let(:job_id) { job.id }
let(:chunk_index) { 0 } let(:chunk_index) { 0 }
let(:buffer_size) { 128.kilobytes } let(:buffer_size) { 128.kilobytes }
let(:buffer_key) { described_class.buffer_key(job_id, chunk_index) } let(:buffer_key) { described_class.buffer_key(job_id, chunk_index) }
let(:params) { { buffer_size: buffer_size } } let(:params) { { buffer_size: buffer_size } }
let(:trace) { 'Here is the trace' } let(:data) { 'Here is the trace' }
describe '.open' do describe '.open' do
subject { described_class.open(job_id, chunk_index, params) } subject { described_class.open(job_id, chunk_index, params) }
...@@ -34,7 +35,7 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -34,7 +35,7 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(data)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -50,17 +51,17 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -50,17 +51,17 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(data)
end end
it { is_expected.to eq(1) } it { is_expected.to eq(1) }
context 'when two chunks exists' do context 'when two chunks exists' do
let(:buffer_key_2) { described_class.buffer_key(job_id, chunk_index + 1) } let(:buffer_key_2) { described_class.buffer_key(job_id, chunk_index + 1) }
let(:trace_2) { 'Another trace' } let(:data_2) { 'Another data' }
before do before do
described_class.new(buffer_key_2, params).write!(trace_2) described_class.new(buffer_key_2, params).write!(data_2)
end end
it { is_expected.to eq(2) } it { is_expected.to eq(2) }
...@@ -77,18 +78,18 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -77,18 +78,18 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(data)
end end
it { is_expected.to eq(trace.length) } it { is_expected.to eq(data.length) }
context 'when two chunks exists' do context 'when two chunks exists' do
let(:buffer_key_2) { described_class.buffer_key(job_id, chunk_index + 1) } let(:buffer_key_2) { described_class.buffer_key(job_id, chunk_index + 1) }
let(:trace_2) { 'Another trace' } let(:data_2) { 'Another data' }
let(:chunks_size) { trace.length + trace_2.length } let(:chunks_size) { data.length + data_2.length }
before do before do
described_class.new(buffer_key_2, params).write!(trace_2) described_class.new(buffer_key_2, params).write!(data_2)
end end
it { is_expected.to eq(chunks_size) } it { is_expected.to eq(chunks_size) }
...@@ -100,6 +101,39 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -100,6 +101,39 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
end end
end end
describe '.delete_all' do
subject { described_class.delete_all(job_id) }
context 'when buffer_key exists' do
before do
described_class.new(buffer_key, params).write!(data)
end
it 'deletes all' do
expect { subject }.to change { described_class.chunks_count(job_id) }.by(-1)
end
context 'when two chunks exists' do
let(:buffer_key_2) { described_class.buffer_key(job_id, chunk_index + 1) }
let(:data_2) { 'Another data' }
before do
described_class.new(buffer_key_2, params).write!(data_2)
end
it 'deletes all' do
expect { subject }.to change { described_class.chunks_count(job_id) }.by(-2)
end
end
end
context 'when buffer_key does not exist' do
it 'deletes all' do
expect { subject }.not_to change { described_class.chunks_count(job_id) }
end
end
end
describe '.buffer_key' do describe '.buffer_key' do
subject { described_class.buffer_key(job_id, chunk_index) } subject { described_class.buffer_key(job_id, chunk_index) }
...@@ -111,10 +145,10 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -111,10 +145,10 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(data)
end end
it { is_expected.to eq(trace) } it { is_expected.to eq(data) }
end end
context 'when buffer_key does not exist' do context 'when buffer_key does not exist' do
...@@ -127,10 +161,10 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -127,10 +161,10 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(data)
end end
it { is_expected.to eq(trace.length) } it { is_expected.to eq(data.length) }
end end
context 'when buffer_key does not exist' do context 'when buffer_key does not exist' do
...@@ -139,91 +173,72 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -139,91 +173,72 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
end end
describe '#write!' do describe '#write!' do
subject { described_class.new(buffer_key, params).write!(trace) } subject { described_class.new(buffer_key, params).write!(data) }
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!('Already data in the chunk') described_class.new(buffer_key, params).write!('Already data in the data')
end end
it 'overwrites' do it 'overwrites' do
is_expected.to eq(trace.length) is_expected.to eq(data.length)
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
expect(redis.get(buffer_key)).to eq(trace) expect(redis.get(buffer_key)).to eq(data)
end end
end end
end end
context 'when buffer_key does not exist' do context 'when buffer_key does not exist' do
it 'writes' do it 'writes' do
is_expected.to eq(trace.length) is_expected.to eq(data.length)
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
expect(redis.get(buffer_key)).to eq(trace) expect(redis.get(buffer_key)).to eq(data)
end end
end end
end end
context 'when data is nil' do context 'when data is nil' do
let(:trace) { nil } let(:data) { nil }
it 'clears value' do it 'clears value' do
is_expected.to eq(0) expect { described_class.new(buffer_key, params).write!(data) }
.to raise_error('Could not write empty data')
end end
end end
end end
describe '#truncate!' do describe '#append!' do
subject { described_class.new(buffer_key, params).truncate!(offset) } subject { described_class.new(buffer_key, params).append!(data) }
let(:offset) { 5 }
context 'when buffer_key exists' do context 'when buffer_key exists' do
let(:written_chunk) { 'Already data in the data' }
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(written_chunk)
end end
it 'truncates' do it 'appends' do
Gitlab::Redis::Cache.with do |redis| is_expected.to eq(data.length)
expect(redis.get(buffer_key)).to eq(trace)
end
subject
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
expect(redis.get(buffer_key)).to eq(trace.slice(0..offset)) expect(redis.get(buffer_key)).to eq(written_chunk + data)
end
end
context 'when offset is larger than data size' do
let(:offset) { 100 }
it 'truncates' do
Gitlab::Redis::Cache.with do |redis|
expect(redis.get(buffer_key)).to eq(trace)
end
subject
Gitlab::Redis::Cache.with do |redis|
expect(redis.get(buffer_key)).to eq(trace.slice(0..offset))
end
end end
end end
end end
context 'when buffer_key does not exist' do context 'when buffer_key does not exist' do
it 'truncates' do it 'raises an error' do
Gitlab::Redis::Cache.with do |redis| expect { subject }.to raise_error(described_class::BufferKeyNotFoundError)
expect(redis.get(buffer_key)).to be_nil end
end end
subject context 'when data is nil' do
let(:data) { nil }
Gitlab::Redis::Cache.with do |redis| it 'raises an error' do
expect(redis.get(buffer_key)).to be_nil expect { subject }.to raise_error('Could not write empty data')
end
end end
end end
end end
...@@ -233,7 +248,7 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -233,7 +248,7 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
context 'when buffer_key exists' do context 'when buffer_key exists' do
before do before do
described_class.new(buffer_key, params).write!(trace) described_class.new(buffer_key, params).write!(data)
end end
it 'deletes' do it 'deletes' do
...@@ -250,16 +265,8 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_ ...@@ -250,16 +265,8 @@ describe Gitlab::Ci::Trace::ChunkedFile::ChunkStore::Redis, :clean_gitlab_redis_
end end
context 'when buffer_key does not exist' do context 'when buffer_key does not exist' do
it 'deletes' do it 'raises an error' do
Gitlab::Redis::Cache.with do |redis| expect { subject }.to raise_error(described_class::BufferKeyNotFoundError)
expect(redis.exists(buffer_key)).to be_falsy
end
subject
Gitlab::Redis::Cache.with do |redis|
expect(redis.exists(buffer_key)).to be_falsy
end
end end
end end
end end
......
...@@ -3,10 +3,9 @@ require 'spec_helper' ...@@ -3,10 +3,9 @@ require 'spec_helper'
describe Gitlab::Ci::Trace::ChunkedFile::ChunkedIO, :clean_gitlab_redis_cache do describe Gitlab::Ci::Trace::ChunkedFile::ChunkedIO, :clean_gitlab_redis_cache do
include ChunkedIOHelpers include ChunkedIOHelpers
let(:chunked_io) { described_class.new(job_id, size, mode) } let(:chunked_io) { described_class.new(job_id, nil, mode) }
let(:job) { create(:ci_build) } let(:job) { create(:ci_build) }
let(:job_id) { job.id } let(:job_id) { job.id }
let(:size) { sample_trace_size }
let(:mode) { 'rb' } let(:mode) { 'rb' }
describe 'ChunkStore is Redis', :partial_support do describe 'ChunkStore is Redis', :partial_support do
......
module ChunkedIOHelpers module ChunkedIOHelpers
def fill_trace_to_chunks(data) def fill_trace_to_chunks(data)
stream = described_class.new(job_id, data.length, 'wb') stream = described_class.new(job_id, nil, 'a+b')
stream.write(data) stream.write(data)
stream.close stream.close
end end
...@@ -13,27 +13,23 @@ module ChunkedIOHelpers ...@@ -13,27 +13,23 @@ module ChunkedIOHelpers
end end
end end
def sample_trace_size # def sample_trace_raw_for_live_trace
sample_trace_raw.length # File.read(expand_fixture_path('trace/sample_trace'))
end # end
def sample_trace_raw_for_live_trace
File.read(expand_fixture_path('trace/sample_trace'))
end
def sample_trace_size_for_live_trace # def sample_trace_size_for_live_trace
sample_trace_raw_for_live_trace.length # sample_trace_raw_for_live_trace.length
end # end
def fill_trace_to_chunks_for_live_trace(data) # def fill_trace_to_chunks_for_live_trace(data)
stream = described_class.new(job_id, 'wb') # stream = described_class.new(job_id, 'a+b')
stream.write(data) # stream.write(data)
stream.close # stream.close
end # end
def stub_chunk_store_get_failed # def stub_chunk_store_get_failed
allow_any_instance_of(chunk_store).to receive(:get).and_return(nil) # allow_any_instance_of(chunk_store).to receive(:get).and_return(nil)
end # end
def set_smaller_buffer_size_than(file_size) def set_smaller_buffer_size_than(file_size)
blocks = (file_size / 128) blocks = (file_size / 128)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment