logstash-output-qingstor 0.2.8 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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