logstash-output-qingstor 0.2.8 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fff7cb0198f0e11e80e348703e766db542be3f787b84238a9ad130d9d33c5b28
4
- data.tar.gz: 479052c30b8979f3382d2b1a30149b7a8597bbb3dc13eb073b1dadbf7647f1ab
3
+ metadata.gz: b634b7aee3754a9bfc4f88269a0ef6fc4135249d1143440ccae417c67852b47f
4
+ data.tar.gz: 05c469d151a6647bb985b15a368dc3c607d69bf6eee496e6ba1d5841185b2b75
5
5
  SHA512:
6
- metadata.gz: dfb42424e15a0b6dd99e4c257ebf45276a50bcaf2efc6d5d5aeb431062568833202966bc7d7ffaafd0552ec66068bd57755516c27aacee13e39a6ddfdb38813d
7
- data.tar.gz: 1978475fb39c13aa4cee4a5bb86e6d66533136b4fe116a5e513944e101ed6e9a2489ff80a0191284f789e87993853d1fdd566be12d2d945d72a621540bd70616
6
+ metadata.gz: 2579b496ce9f0da3c9831d4de53f70eb23c7f51febe23b7c82052df1cb2d99337c23724b34a3f8d66ddd385efe468e17f158bb8d786b4ceb5a6ee309ace88a2c
7
+ data.tar.gz: 545228916a49aa445d47b8242ef062c1d78834590352c7d32f81fa6cb4f93acc7208c78d3bbed7b133f5a3fbee2abd46aa6ab2dc5e7912cacb2db303dc25005e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.3.1
2
+ - Refactor module: rotation policy
3
+
4
+ ## 0.3.0
5
+ - Add MultipartUploader supporting large file size up to 500GB
6
+
1
7
  ## 0.2.8
2
8
  - Fix: add prefixes for restored file
3
9
 
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+
3
+ require 'logstash/outputs/qingstor'
4
+
5
+ module LogStash
6
+ module Outputs
7
+ class Qingstor
8
+ class Uploader
9
+ class MultipartUploader
10
+ attr_reader :bucket, :logger
11
+ attr_accessor :upload_headers
12
+
13
+ # According to QingStor API doc, the minimal part size is 4MB.
14
+ # the maximal part size is 1GB.
15
+ # Here the minimal size would never be used and the maximal part size is 50MB.
16
+ # Overlarge file size would consume java heap space and throw Exceptions.
17
+ MINIMAL_PART_SIZE = 4 * 1024 * 1024
18
+ MAXIMAL_PART_SIZE = 50 * 1024 * 1024
19
+
20
+ def initialize(bucket, logger, object, upload_headers)
21
+ @bucket = bucket
22
+ @logger = logger
23
+ @object = object
24
+ @upload_headers = upload_headers
25
+ end
26
+
27
+ def upload
28
+ initiate_multipart_upload
29
+ segment_size = calculate_segment
30
+ upload_multipart(segment_size)
31
+ end
32
+
33
+ def initiate_multipart_upload
34
+ res = @bucket.initiate_multipart_upload(@object.key, @upload_headers)
35
+ @upload_headers['upload_id'] = res.fetch(:upload_id) do
36
+ raise(QingStor::Error, :error_msg => res)
37
+ end
38
+ @logger.debug('initiated upload id', :file => @object.key,
39
+ :upload_id => res[:upload_id])
40
+ res[:upload_id]
41
+ end
42
+
43
+ def upload_multipart(part_number = 0, segment_size)
44
+ ::File.open(@object.path) do |f|
45
+ f.seek(part_number * segment_size) if part_number != 0
46
+ while data = f.read(segment_size)
47
+ content_md5 = Digest::MD5.hexdigest(data)
48
+ @upload_headers['body'] = data
49
+ @upload_headers['content_length'] = segment_size
50
+ @upload_headers['content_md5'] = content_md5
51
+ @upload_headers['part_number'] = part_number
52
+ @logger.debug('multipart uploading: ',
53
+ :file => @object.key,
54
+ :part_number => part_number)
55
+ @bucket.upload_multipart(@object.key, @upload_headers)
56
+ part_number += 1
57
+ end
58
+ end
59
+
60
+ complete_multipart_upload(part_number - 1)
61
+ end
62
+
63
+ def complete_multipart_upload(last_part_number)
64
+ object_parts = (0..last_part_number).to_a.map {|x| {'part_number' => x}}
65
+ @upload_headers['object_parts'] = object_parts
66
+ res = @bucket.complete_multipart_upload(@object.key, @upload_headers)
67
+ @logger.debug('multipart uploading completed', :file => @object.key)
68
+ end
69
+
70
+ def calculate_segment
71
+ segment_size = @object.size
72
+
73
+ while segment_size >= MAXIMAL_PART_SIZE
74
+ segment_size /= 2.0
75
+ segment_size = segment_size.ceil
76
+ end
77
+
78
+ segment_size
79
+ end
80
+
81
+ def resume_multipart_upload(upload_id)
82
+ @logger.debug('resume multipart uploading', :file => @object.key,
83
+ :upload_id => upload_id)
84
+ @upload_headers['upload_id'] = upload_id
85
+ res = @bucket.list_multipart(@object.key, upload_headers)
86
+ segment_size = res[:object_parts][0][:size]
87
+ part_number = res[:object_parts].count
88
+ upload_multipart(part_number, segment_size)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -6,71 +6,86 @@ module LogStash
6
6
  module Outputs
7
7
  class Qingstor
8
8
  class RotationPolicy
9
- def initialize(policy, file_size, file_time)
10
- @policy = policy
11
- case policy
12
- when 'time'
13
- init_time(file_time)
14
- when 'size'
15
- init_size(file_size)
16
- when 'size_and_time'
17
- init_size(file_size)
18
- init_time(file_time)
9
+ class Policy
10
+ def to_s
11
+ name
19
12
  end
20
- end
21
13
 
22
- def init_size(file_size)
23
- if file_size <= 0
24
- raise LogStash::ConfigurationError, "'file_size' need to "\
25
- + 'be greater than 0'
14
+ def name
15
+ self.class.name.split('::').last.downcase
16
+ end
17
+
18
+ def needs_periodic?
19
+ true
20
+ end
21
+
22
+ def positive_check(*arg)
23
+ arg.each do |x|
24
+ raise(LogStash::ConfigurationError,
25
+ "#{name} policy needs positive arguments") if x <= 0
26
+ end
26
27
  end
27
- @file_size = file_size
28
28
  end
29
29
 
30
- def init_time(file_time)
31
- if file_time <= 0
32
- raise LogStash::ConfigurationError, "'file_time' need to "\
33
- + 'be greater than 0'
30
+ class Time < Policy
31
+ def initialize(file_size, file_time)
32
+ @file_time = file_time
33
+ positive_check(@file_time)
34
+ end
35
+
36
+ def rotate?(file)
37
+ !file.empty? && (::Time.now - file.ctime) >= @file_time
34
38
  end
35
- @file_time = file_time * 60
39
+ end
40
+
41
+ class Size < Policy
42
+ def initialize(file_size, file_time)
43
+ @file_size = file_size
44
+ positive_check(@file_size)
45
+ end
46
+
47
+ def rotate?(file)
48
+ file.size >= @file_size
49
+ end
50
+
51
+ def needs_periodic?; false; end
36
52
  end
37
53
 
38
- def rotate?(file)
39
- case @policy
40
- when 'time'
41
- time_rotate?(file)
42
- when 'size'
43
- size_rotate?(file)
44
- when 'size_and_time'
45
- size_and_time_rotate?(file)
54
+ class SizeAndTime < Policy
55
+ def initialize(file_size, file_time)
56
+ @file_size, @file_time = file_size, file_time
57
+ positive_check(file_size, file_time)
58
+ end
59
+
60
+ def rotate?(file)
61
+ (!file.empty? && (::Time.now - file.ctime) >= @file_time) ||
62
+ (file.size >= @file_size)
46
63
  end
47
64
  end
48
65
 
49
- def size_and_time_rotate?(file)
50
- size_rotate?(file) || time_rotate?(file)
66
+ def Policy(policy, file_size, file_time)
67
+ case policy
68
+ when Policy then policy
69
+ else
70
+ self.class.const_get(policy.to_s.split('_').map(&:capitalize).join)
71
+ .new(file_size, file_time)
72
+ end
51
73
  end
52
74
 
53
- def size_rotate?(file)
54
- file.size >= @file_size
75
+ def initialize(policy, file_size, file_time)
76
+ @policy = Policy(policy, file_size, file_time)
55
77
  end
56
78
 
57
- def time_rotate?(file)
58
- !file.empty? && (Time.now - file.ctime) >= @file_time
79
+ def rotate?(file)
80
+ @policy.rotate?(file)
59
81
  end
60
82
 
61
83
  def needs_periodic?
62
- case @policy
63
- when 'time' then
64
- true
65
- when 'size_and_time' then
66
- true
67
- else
68
- false
69
- end
84
+ @policy.needs_periodic?
70
85
  end
71
86
 
72
87
  def to_s
73
- "#{@policy} #{@file_time} #{@file_size}"
88
+ @policy.to_s
74
89
  end
75
90
  end
76
91
  end
@@ -49,7 +49,7 @@ module LogStash
49
49
  rescue
50
50
  IOError
51
51
  end
52
- FileUtils.rm(::File.join(@tmp_path, @key), :force => true)
52
+ FileUtils.rm(path, :force => true)
53
53
  end
54
54
 
55
55
  def empty?
@@ -10,6 +10,8 @@ module LogStash
10
10
  module Outputs
11
11
  class Qingstor
12
12
  class Uploader
13
+ require 'logstash/outputs/qingstor/multipart_uploader'
14
+
13
15
  TIME_BEFORE_RETRYING_SECONDS = 1
14
16
  DEFAULT_THREADPOOL = Concurrent::ThreadPoolExecutor.new(
15
17
  :min_thread => 1,
@@ -34,29 +36,38 @@ module LogStash
34
36
 
35
37
  def upload(file, options = {})
36
38
  upload_options = options.fetch(:upload_options, {})
39
+ upload_headers = process_encrypt_options(upload_options)
40
+
41
+ if file.size > 50 * 1024 * 1024
42
+ @logger.debug('multipart uploading file', :file => file.key)
43
+ multipart_uploader = MultipartUploader.new(@bucket, @logger, file, upload_headers)
44
+ multipart_uploader.upload
45
+ else
46
+ upload_headers['content_md5'] = Digest::MD5.file(file.path).to_s
47
+ upload_headers['body'] = ::File.read(file.path)
48
+ @logger.debug('uploading file', :file => file.key)
49
+ @bucket.put_object(file.key, upload_headers)
50
+ end
37
51
 
38
- file_md5 = Digest::MD5.file(file.path).to_s
52
+ options[:on_complete].call(file) unless options[:on_complete].nil?
53
+ end
39
54
 
40
- upload_headers = {
41
- 'content_md5' => file_md5,
42
- 'body' => ::File.read(file.path)
43
- }
55
+ def process_encrypt_options(upload_options)
56
+ res = {}
44
57
 
45
58
  unless upload_options[:server_side_encryption_algorithm].nil?
46
59
  base64_key = Base64.strict_encode64(upload_options[:customer_key])
47
60
  key_md5 = Digest::MD5.hexdigest(upload_options[:customer_key])
48
61
  base64_key_md5 = Base64.strict_encode64(key_md5)
49
- upload_headers.merge!(
62
+ res.merge!(
50
63
  'x_qs_encryption_customer_algorithm' =>
51
64
  upload_options[:server_side_encryption_algorithm],
52
65
  'x_qs_encryption_customer_key' => base64_key,
53
66
  'x_qs_encryption_customer_key_md5' => base64_key_md5
54
67
  )
55
68
  end
56
- @logger.debug('uploading file', :file => file.key)
57
- bucket.put_object(file.key, upload_headers)
58
69
 
59
- options[:on_complete].call(file) unless options[:on_complete].nil?
70
+ res
60
71
  end
61
72
 
62
73
  def stop
@@ -256,11 +256,9 @@ class LogStash::Outputs::Qingstor < LogStash::Outputs::Base
256
256
  .each do |file|
257
257
  temp_file = TemporaryFile.create_from_existing_file(file,
258
258
  temp_folder_path)
259
-
260
- # Restoring too large file would cause java heap out of memory
261
- if temp_file.size > 0 && temp_file.size < 100 * 1024 * 1024
262
- temp_file.key = 'Restored/' + Time.new.strftime('%Y-%m-%d/')
263
- + temp_file.key
259
+ # Now multipart uploader supports file size up to 500GB
260
+ if temp_file.size > 0
261
+ temp_file.key = 'Restored/' + Time.new.strftime('%Y-%m-%d/') + temp_file.key
264
262
  @logger.debug('Recoving from crash and uploading',
265
263
  :file => temp_file.key)
266
264
  @crash_uploader.upload_async(
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-qingstor'
3
- s.version = '0.2.8'
3
+ s.version = '0.3.1'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = 'logstash output plugin for qingstor'
6
6
  s.description = 'Collect the outputs of logstash and store into QingStor'
@@ -28,6 +28,10 @@ describe LogStash::Outputs::Qingstor do
28
28
  clean_remote_files
29
29
  end
30
30
 
31
+ before do
32
+ FileUtils.mkdir_p(tmpdir) unless File.exist?(tmpdir)
33
+ end
34
+
31
35
  it 'done work with minimal options' do
32
36
  fetch_event(options, events_and_encoded)
33
37
  expect(list_remote_file.size).to eq(1)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-qingstor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Zhao
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-26 00:00:00.000000000 Z
11
+ date: 2018-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -111,6 +111,7 @@ files:
111
111
  - README_zh_CN.md
112
112
  - lib/logstash/outputs/qingstor.rb
113
113
  - lib/logstash/outputs/qingstor/file_repository.rb
114
+ - lib/logstash/outputs/qingstor/multipart_uploader.rb
114
115
  - lib/logstash/outputs/qingstor/qingstor_validator.rb
115
116
  - lib/logstash/outputs/qingstor/rotation_policy.rb
116
117
  - lib/logstash/outputs/qingstor/temporary_file.rb