logstash-input-oss 0.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/CONTRIBUTORS +10 -0
  4. data/DEVELOPER.md +10 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +11 -0
  7. data/README.md +143 -0
  8. data/lib/com/aliyun/aliyun-java-sdk-core/3.4.0/aliyun-java-sdk-core-3.4.0.jar +0 -0
  9. data/lib/com/aliyun/aliyun-java-sdk-ecs/4.2.0/aliyun-java-sdk-ecs-4.2.0.jar +0 -0
  10. data/lib/com/aliyun/aliyun-java-sdk-ram/3.0.0/aliyun-java-sdk-ram-3.0.0.jar +0 -0
  11. data/lib/com/aliyun/aliyun-java-sdk-sts/3.0.0/aliyun-java-sdk-sts-3.0.0.jar +0 -0
  12. data/lib/com/aliyun/oss/aliyun-sdk-oss/3.4.0/aliyun-sdk-oss-3.4.0.jar +0 -0
  13. data/lib/com/sun/jersey/jersey-core/1.9/jersey-core-1.9.jar +0 -0
  14. data/lib/com/sun/jersey/jersey-json/1.9/jersey-json-1.9.jar +0 -0
  15. data/lib/com/sun/xml/bind/jaxb-impl/2.2.3-1/jaxb-impl-2.2.3-1.jar +0 -0
  16. data/lib/commons-codec/commons-codec/1.9/commons-codec-1.9.jar +0 -0
  17. data/lib/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
  18. data/lib/javax/activation/activation/1.1/activation-1.1.jar +0 -0
  19. data/lib/javax/xml/bind/jaxb-api/2.2.2/jaxb-api-2.2.2.jar +0 -0
  20. data/lib/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar +0 -0
  21. data/lib/logstash-input-oss_jars.rb +54 -0
  22. data/lib/logstash/inputs/mns/message.rb +53 -0
  23. data/lib/logstash/inputs/mns/request.rb +85 -0
  24. data/lib/logstash/inputs/oss.rb +382 -0
  25. data/lib/logstash/inputs/version.rb +14 -0
  26. data/lib/org/apache/httpcomponents/httpclient/4.4.1/httpclient-4.4.1.jar +0 -0
  27. data/lib/org/apache/httpcomponents/httpcore/4.4.1/httpcore-4.4.1.jar +0 -0
  28. data/lib/org/codehaus/jackson/jackson-core-asl/1.8.3/jackson-core-asl-1.8.3.jar +0 -0
  29. data/lib/org/codehaus/jackson/jackson-jaxrs/1.8.3/jackson-jaxrs-1.8.3.jar +0 -0
  30. data/lib/org/codehaus/jackson/jackson-mapper-asl/1.8.3/jackson-mapper-asl-1.8.3.jar +0 -0
  31. data/lib/org/codehaus/jackson/jackson-xc/1.8.3/jackson-xc-1.8.3.jar +0 -0
  32. data/lib/org/codehaus/jettison/jettison/1.1/jettison-1.1.jar +0 -0
  33. data/lib/org/jdom/jdom/1.1/jdom-1.1.jar +0 -0
  34. data/lib/org/json/json/20170516/json-20170516.jar +0 -0
  35. data/lib/stax/stax-api/1.0.1/stax-api-1.0.1.jar +0 -0
  36. data/logstash-input-oss.gemspec +33 -0
  37. data/spec/integration/common.rb +79 -0
  38. data/spec/integration/oss_spec.rb +73 -0
  39. data/spec/sample/uncompressed.log +37 -0
  40. data/spec/sample/uncompressed.log.1.gz +0 -0
  41. metadata +249 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3623c612fe4e3a2267316232b1eb5af798ae26656d1299eb5508582a3982d9c
4
+ data.tar.gz: 07a9bfe7d7ae1ad99f300ec7020a612f1570d05ddfaed4daa2abb517eecfc2f2
5
+ SHA512:
6
+ metadata.gz: 2fea65a2a8690cbce0d40a86843e2ff9457d8e042a28074d30238b5ffa01098264adcc5912669304ffb55b4dc49cb1ebba69f48691d18aa4bb64211dd18488e5
7
+ data.tar.gz: 55ba1325ca09e9a1f4fe51fce2bc976d30dcba1c1d7d6e70618e3406fc71249be34966843bf86fb4dc8e190ce2ac0316b19e12f95e02374228969d1c923cf79a
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.0.1
2
+ - First release to support OSS
data/CONTRIBUTORS ADDED
@@ -0,0 +1,10 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * jinhu.wu - jinhu.wu.nju@gmail.com
6
+
7
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
8
+ Logstash, and you aren't on the list above and want to be, please let us know
9
+ and we'll make sure you're here. Contributions from folks like you are what make
10
+ open source awesome.
data/DEVELOPER.md ADDED
@@ -0,0 +1,10 @@
1
+ # Running the tests
2
+ ```bash
3
+ bundle install
4
+ bundle exec rspec
5
+ ```
6
+
7
+ If you want to run the integration tests against a real bucket you need to pass your Aliyun credentials to the test runner or declare it in your environment.
8
+ ```bash
9
+ OSS_ENDPOINT="Aliyun OSS endpoint to connect to" OSS_ACCESS_KEY="Your access key id" OSS_SECRET_KEY="Your access secret" OSS_BUCKET="Your bucket" MNS_ENDPOINT="MNS endpoint" MNS_QUEUE="MNS queue" BACKUP_DIR="/tmp/oss" BACKUP_BUCKET="Your backup bucket" bundle exec rspec spec/integration/oss_spec.rb --tag integration
10
+ ```
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Logstash OSS Input Plugin
2
+
3
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
4
+
5
+ It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
6
+
7
+ ## Documentation
8
+
9
+ This plugin reads data from OSS periodically.
10
+
11
+ This plugin uses MNS on the same region of the OSS bucket. We must setup MNS and OSS event notification before using this plugin.
12
+
13
+ [This document](https://help.aliyun.com/document_detail/52656.html) shows how to setup MNS and OSS event notification.
14
+
15
+ This plugin will poll events from MNS queue and extract object keys from these events, and then will read those objects from OSS.
16
+
17
+ ### Usage:
18
+ This is an example of logstash config:
19
+
20
+ ```
21
+ input {
22
+ oss {
23
+ "endpoint" => "OSS endpoint to connect to" (required)
24
+ "bucket" => "Your bucket name" (required)
25
+ "access_key_id" => "Your access key id" (required)
26
+ "access_key_secret" => "Your secret key" (required)
27
+ "backup_to_bucket" => "Your backup bucket name" (optional, default = nil)
28
+ "prefix" => "sample" (optional, default = nil)
29
+ "delete" => false (optional, default = false)
30
+ "exclude_pattern" => "^sample-logstash" (optional, default = nil)
31
+ "backup_add_prefix" => "logstash-input/" (optional, default = nil)
32
+ "include_object_properties" => true (optional, default = false)
33
+ "additional_oss_settings" => {
34
+ "max_connections_to_oss" => 1024 (optional, default = 1024)
35
+ "secure_connection_enabled" => false (optional, default = false)
36
+ }
37
+ "mns_settings" => { (required)
38
+ "endpoint" => "MNS endpoint to connect to" (required)
39
+ "queue" => "MNS queue name" (required)
40
+ "poll_interval_seconds" => 10 (optional, default = 10)
41
+ "wait_seconds" => 3 (optional, default = nil)
42
+ }
43
+ codec => json {
44
+ charset => "UTF-8"
45
+ }
46
+ }
47
+ }
48
+ ```
49
+ ### Logstash OSS Input Configuration Options
50
+ This plugin supports the following configuration options
51
+
52
+ Note: Files end with `.gz` or `.gzip` are handled as gzip'ed files, others are handled as plain files.
53
+
54
+ |Configuration|Type|Required|Comments|
55
+ |:---:|:---:|:---:|:---|
56
+ |endpoint|string|Yes|OSS endpoint to connect|
57
+ |bucket|string|Yes|Your OSS bucket name|
58
+ |access_key_id|string|Yes|Your access key id|
59
+ |access_key_secret|string|Yes|Your access secret key|
60
+ |prefix|string|No|If specified, the prefix of filenames in the bucket must match (not a regexp)|
61
+ |additional_oss_settings|hash|No|Additional oss client configurations, valid keys are: `secure_connection_enabled` and `max_connections_to_oss`|
62
+ |delete|boolean|No|Whether to delete processed files from the original bucket|
63
+ |backup_to_bucket|string|No|Name of an OSS bucket to backup processed files to|
64
+ |backup_to_dir|string|No|Path of a local directory to backup processed files to|
65
+ |backup_add_prefix|string|No|Append a prefix to the key (full path including file name in OSS) after processing(If backing up to another (or the same) bucket, this effectively lets you choose a new 'folder' to place the files in)|
66
+ |include_object_properties|boolean|No|Whether or not to include the OSS object's properties (last_modified, content_type, metadata) into each Event at [@metadata][oss]. Regardless of this setting, [@metadata][oss][key] will always be present|
67
+ |exclude_pattern|string|No|Ruby style regexp of keys to exclude from the bucket|
68
+ |mns_settings|hash|Yes|MNS settings, valid keys are: `endpoint`, `queue`, `poll_interval_seconds`, `wait_seconds`|
69
+
70
+ #### mns_settings
71
+
72
+ [MNS consume messages](https://help.aliyun.com/document_detail/35136.html)
73
+
74
+ * endpoint
75
+ * queue
76
+ * wait_seconds
77
+ * poll_interval_seconds Poll messages interval from MNS if there is no message this time, default 10 seconds
78
+
79
+ For more details about mns configurations, please view MNS documentation in the link above.
80
+
81
+ ## Need Help?
82
+
83
+ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
84
+
85
+ ## Developing
86
+
87
+ ### 1. Plugin Development and Testing
88
+
89
+ #### Code
90
+ - To get started, you'll need JRuby with the Bundler gem installed.
91
+
92
+ - Install dependencies
93
+ ```sh
94
+ bundle install
95
+ ```
96
+
97
+ #### Test
98
+
99
+ - Update your dependencies
100
+
101
+ ```sh
102
+ bundle install
103
+ ```
104
+
105
+ - Run tests
106
+
107
+ ```sh
108
+ bundle exec rspec
109
+ ```
110
+
111
+ ### 2. Running your unpublished Plugin in Logstash
112
+
113
+ #### 2.1 Run in an installed Logstash
114
+
115
+ you can build the gem and install it using:
116
+
117
+ - Build your plugin gem
118
+
119
+ ```sh
120
+ gem build logstash-input-oss.gemspec
121
+ ```
122
+
123
+ - Install the plugin from the Logstash home
124
+
125
+ ```sh
126
+ bin/logstash-plugin install /path/to/logstash-input-oss-0.0.1-java.gem
127
+ ```
128
+
129
+ - Start Logstash and proceed to test the plugin
130
+
131
+ ```bash
132
+ ./bin/logstash -f config/logstash-sample.conf
133
+ ```
134
+
135
+ ## Contributing
136
+
137
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
138
+
139
+ Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
140
+
141
+ It is more important to the community that you are able to contribute.
142
+
143
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
@@ -0,0 +1,54 @@
1
+ # this is a generated file, to avoid over-writing it just delete this comment
2
+ begin
3
+ require 'jar_dependencies'
4
+ rescue LoadError
5
+ require 'org/apache/httpcomponents/httpcore/4.4.1/httpcore-4.4.1.jar'
6
+ require 'com/aliyun/oss/aliyun-sdk-oss/3.4.0/aliyun-sdk-oss-3.4.0.jar'
7
+ require 'commons-codec/commons-codec/1.9/commons-codec-1.9.jar'
8
+ require 'org/codehaus/jackson/jackson-xc/1.8.3/jackson-xc-1.8.3.jar'
9
+ require 'com/aliyun/aliyun-java-sdk-ram/3.0.0/aliyun-java-sdk-ram-3.0.0.jar'
10
+ require 'org/apache/httpcomponents/httpclient/4.4.1/httpclient-4.4.1.jar'
11
+ require 'javax/xml/bind/jaxb-api/2.2.2/jaxb-api-2.2.2.jar'
12
+ require 'com/sun/jersey/jersey-json/1.9/jersey-json-1.9.jar'
13
+ require 'org/codehaus/jettison/jettison/1.1/jettison-1.1.jar'
14
+ require 'com/sun/jersey/jersey-core/1.9/jersey-core-1.9.jar'
15
+ require 'javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar'
16
+ require 'org/json/json/20170516/json-20170516.jar'
17
+ require 'com/aliyun/aliyun-java-sdk-core/3.4.0/aliyun-java-sdk-core-3.4.0.jar'
18
+ require 'commons-logging/commons-logging/1.2/commons-logging-1.2.jar'
19
+ require 'org/codehaus/jackson/jackson-jaxrs/1.8.3/jackson-jaxrs-1.8.3.jar'
20
+ require 'javax/activation/activation/1.1/activation-1.1.jar'
21
+ require 'com/sun/xml/bind/jaxb-impl/2.2.3-1/jaxb-impl-2.2.3-1.jar'
22
+ require 'org/jdom/jdom/1.1/jdom-1.1.jar'
23
+ require 'org/codehaus/jackson/jackson-mapper-asl/1.8.3/jackson-mapper-asl-1.8.3.jar'
24
+ require 'org/codehaus/jackson/jackson-core-asl/1.8.3/jackson-core-asl-1.8.3.jar'
25
+ require 'com/aliyun/aliyun-java-sdk-ecs/4.2.0/aliyun-java-sdk-ecs-4.2.0.jar'
26
+ require 'com/aliyun/aliyun-java-sdk-sts/3.0.0/aliyun-java-sdk-sts-3.0.0.jar'
27
+ require 'stax/stax-api/1.0.1/stax-api-1.0.1.jar'
28
+ end
29
+
30
+ if defined? Jars
31
+ require_jar 'org.apache.httpcomponents', 'httpcore', '4.4.1'
32
+ require_jar 'com.aliyun.oss', 'aliyun-sdk-oss', '3.4.0'
33
+ require_jar 'commons-codec', 'commons-codec', '1.9'
34
+ require_jar 'org.codehaus.jackson', 'jackson-xc', '1.8.3'
35
+ require_jar 'com.aliyun', 'aliyun-java-sdk-ram', '3.0.0'
36
+ require_jar 'org.apache.httpcomponents', 'httpclient', '4.4.1'
37
+ require_jar 'javax.xml.bind', 'jaxb-api', '2.2.2'
38
+ require_jar 'com.sun.jersey', 'jersey-json', '1.9'
39
+ require_jar 'org.codehaus.jettison', 'jettison', '1.1'
40
+ require_jar 'com.sun.jersey', 'jersey-core', '1.9'
41
+ require_jar 'javax.xml.stream', 'stax-api', '1.0-2'
42
+ require_jar 'org.json', 'json', '20170516'
43
+ require_jar 'com.aliyun', 'aliyun-java-sdk-core', '3.4.0'
44
+ require_jar 'commons-logging', 'commons-logging', '1.2'
45
+ require_jar 'org.codehaus.jackson', 'jackson-jaxrs', '1.8.3'
46
+ require_jar 'javax.activation', 'activation', '1.1'
47
+ require_jar 'com.sun.xml.bind', 'jaxb-impl', '2.2.3-1'
48
+ require_jar 'org.jdom', 'jdom', '1.1'
49
+ require_jar 'org.codehaus.jackson', 'jackson-mapper-asl', '1.8.3'
50
+ require_jar 'org.codehaus.jackson', 'jackson-core-asl', '1.8.3'
51
+ require_jar 'com.aliyun', 'aliyun-java-sdk-ecs', '4.2.0'
52
+ require_jar 'com.aliyun', 'aliyun-java-sdk-sts', '3.0.0'
53
+ require_jar 'stax', 'stax-api', '1.0.1'
54
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+ require 'rexml/document'
3
+
4
+ module LogStash
5
+ module Inputs
6
+ module MNS
7
+ # Class for Aliyun MNS Message.
8
+ class Message
9
+ include REXML
10
+
11
+ attr_reader :queue, :id, :body_md5, :body, :receipt_handle, :enqueue_at,
12
+ :first_enqueue_at, :next_visible_at, :dequeue_count, :priority
13
+
14
+ def initialize(queue, content)
15
+ @queue = queue
16
+
17
+ doc = Document.new(content)
18
+ doc.elements[1].each do |e|
19
+ if e.node_type == :element
20
+ if e.name == 'MessageId'
21
+ @id = e.text
22
+ elsif e.name == 'MessageBodyMD5'
23
+ @body_md5 = e.text
24
+ elsif e.name == 'MessageBody'
25
+ @body = e.text
26
+ elsif e.name == 'EnqueueTime'
27
+ @enqueue_at = e.text.to_i
28
+ elsif e.name == 'FirstDequeueTime'
29
+ @first_enqueue_at = e.text.to_i
30
+ elsif e.name == 'DequeueCount'
31
+ @dequeue_count = e.text.to_i
32
+ elsif e.name == 'Priority'
33
+ @priority = e.text.to_i
34
+ elsif e.name == 'ReceiptHandle'
35
+ @receipt_handle = e.text
36
+ elsif e.name == 'NextVisibleTime'
37
+ @next_visible_at = e.text.to_i
38
+ end
39
+ end
40
+ end
41
+
42
+ # verify body
43
+ md5 = Digest::MD5.hexdigest(body).upcase
44
+ unless md5 == body_md5
45
+ raise Exception,
46
+ 'Invalid MNS Body, MD5 does not match, '\
47
+ "MD5 #{body_md5}, expect MD5 #{md5}, Body: #{body}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+
3
+ require 'base64'
4
+ require 'uri'
5
+ require 'rest-client'
6
+ require 'rexml/document'
7
+
8
+ module LogStash
9
+ module Inputs
10
+ module MNS
11
+ # Class for Aliyun MNS Request.
12
+ class Request
13
+ include REXML
14
+
15
+ attr_reader :log, :uri, :method, :body, :content_md5, :content_type,
16
+ :content_length, :mns_headers, :access_key_id,
17
+ :access_key_secret, :endpoint
18
+
19
+ def initialize(opts, headers, params)
20
+ @log = opts[:log]
21
+ conf = {
22
+ host: opts[:endpoint],
23
+ path: opts[:path]
24
+ }
25
+
26
+ conf[:query] = URI.encode_www_form(params) unless params.empty?
27
+ @uri = URI::HTTP.build(conf)
28
+ @method = opts[:method].to_s.downcase
29
+ @mns_headers = headers.merge('x-mns-version' => '2015-06-06')
30
+ @access_key_id = opts[:access_key_id]
31
+ @access_key_secret = opts[:access_key_secret]
32
+
33
+ log.info uri.to_s
34
+ end
35
+
36
+ def content(type, values = {})
37
+ ns = 'http://mns.aliyuncs.com/doc/v1/'
38
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
39
+ xml.send(type.to_sym, xmlns: ns) do |b|
40
+ values.each { |k, v| b.send k.to_sym, v }
41
+ end
42
+ end
43
+ @body = builder.to_xml
44
+ @content_md5 = Base64.encode64(Digest::MD5.hexdigest(body)).chop
45
+ @content_length = body.size
46
+ @content_type = 'text/xml;charset=utf-8'
47
+ end
48
+
49
+ def execute
50
+ date = DateTime.now.httpdate
51
+ headers = {
52
+ 'Authorization' => authorization(date),
53
+ 'Content-Length' => content_length || 0,
54
+ 'Content-Type' => content_type,
55
+ 'Content-MD5' => content_md5,
56
+ 'Date' => date,
57
+ 'Host' => uri.host
58
+ }.merge(@mns_headers).reject { |k, v| v.nil? }
59
+
60
+ begin
61
+ RestClient.send *[method, uri.to_s, headers, body].compact
62
+ rescue RestClient::Exception => e
63
+ doc = Document.new(e.response.to_s)
64
+ doc.elements[1].each do |e|
65
+ next unless e.node_type == :element
66
+ return nil if (e.name == 'Code') && (e.text == 'MessageNotExist')
67
+ end
68
+
69
+ log.error e.response
70
+
71
+ raise e
72
+ end
73
+ end
74
+
75
+ def authorization(date)
76
+ canonical_resource = [uri.path, uri.query].compact.join('?')
77
+ canonical_headers = mns_headers.sort.collect { |k, v| "#{k.downcase}:#{v}" }.join("\n")
78
+ signature = [method.to_s.upcase, content_md5 || '', content_type || '', date, canonical_headers, canonical_resource].join("\n")
79
+ sha1 = OpenSSL::HMAC.digest('sha1', access_key_secret, signature)
80
+ "MNS #{access_key_id}:#{Base64.encode64(sha1).chop}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,382 @@
1
+ # encoding: utf-8
2
+ require 'json'
3
+ require 'logstash/inputs/base'
4
+ require 'logstash-input-oss_jars'
5
+
6
+ java_import com.aliyun.oss.OSS
7
+ java_import com.aliyun.oss.OSSClientBuilder
8
+ java_import com.aliyun.oss.ClientBuilderConfiguration
9
+ java_import com.aliyun.oss.model.GetObjectRequest
10
+ java_import java.io.InputStream
11
+ java_import java.io.InputStreamReader
12
+ java_import java.io.FileInputStream
13
+ java_import java.io.BufferedReader
14
+ java_import java.util.zip.GZIPInputStream
15
+ java_import java.util.zip.ZipException
16
+
17
+ #
18
+ # LogStash OSS Input Plugin
19
+ # Stream events from files from an OSS bucket.
20
+ # Each line from each file generates an event.
21
+ # Files end with `.gz` or `.gzip` are handled as gzip'ed files.
22
+ #
23
+ class LogStash::Inputs::OSS < LogStash::Inputs::Base
24
+ require 'logstash/inputs/mns/message'
25
+ require 'logstash/inputs/mns/request'
26
+ require 'logstash/inputs/version'
27
+
28
+ MAX_CONNECTIONS_TO_OSS_KEY = "max_connections_to_oss"
29
+
30
+ SECURE_CONNECTION_ENABLED_KEY = "secure_connection_enabled"
31
+
32
+ MNS_ENDPOINT = "endpoint"
33
+
34
+ MNS_QUEUE = "queue"
35
+
36
+ MNS_WAIT_SECONDS = "wait_seconds"
37
+
38
+ MNS_POLL_INTERVAL_SECONDS = "poll_interval_seconds"
39
+
40
+ config_name "oss"
41
+
42
+ default :codec, "plain"
43
+
44
+ # The name of OSS bucket.
45
+ config :bucket, :validate => :string, :required => true
46
+
47
+ # OSS endpoint to connect
48
+ config :endpoint, :validate => :string, :required => true
49
+
50
+ # access key id
51
+ config :access_key_id, :validate => :string, :required => true
52
+
53
+ # access secret key
54
+ config :access_key_secret, :validate => :string, :required => true
55
+
56
+ # If specified, the prefix of filenames in the bucket must match (not a regexp)
57
+ config :prefix, :validate => :string, :default => nil
58
+
59
+ # additional oss client configurations, valid keys are:
60
+ # secure_connection_enabled(enable https or not)
61
+ # max_connections_to_oss(max connections to oss)
62
+ # TODO: add other oss configurations
63
+ config :additional_oss_settings, :validate => :hash, :default => nil
64
+
65
+ # Name of an OSS bucket to backup processed files to.
66
+ config :backup_to_bucket, :validate => :string, :default => nil
67
+
68
+ # Append a prefix to the key (full path including file name in OSS) after processing.
69
+ # If backing up to another (or the same) bucket, this effectively lets you
70
+ # choose a new 'folder' to place the files in
71
+ config :backup_add_prefix, :validate => :string, :default => nil
72
+
73
+ # Path of a local directory to backup processed files to.
74
+ config :backup_to_dir, :validate => :string, :default => nil
75
+
76
+ # Whether to delete processed files from the original bucket.
77
+ config :delete, :validate => :boolean, :default => false
78
+
79
+ # MNS configurations, valid keys are:
80
+ # endpoint: MNS endpoint to connect to
81
+ # queue: MNS queue to poll messages
82
+ # wait_seconds: MNS max waiting time to receive messages, not required
83
+ # poll_interval_seconds: Poll messages interval from MNS if there is no message this time, default 10 seconds
84
+ config :mns_settings, :validate => :hash, :required => true
85
+
86
+ # Ruby style regexp of keys to exclude from the bucket
87
+ config :exclude_pattern, :validate => :string, :default => nil
88
+
89
+ # Whether or not to include the OSS object's properties (last_modified, content_type, metadata)
90
+ # into each Event at [@metadata][oss]. Regardless of this setting, [@metadata][oss][key] will always
91
+ # be present.
92
+ config :include_object_properties, :validate => :boolean, :default => false
93
+
94
+ # For testing
95
+ config :stop_for_test, :validate => :boolean, :default => false
96
+
97
+ public
98
+ def register
99
+ @logger.info("Registering oss input", :bucket => @bucket)
100
+
101
+ # initialize oss client
102
+ @oss = initialize_oss_client
103
+
104
+ unless @backup_to_bucket.nil?
105
+ if @backup_to_bucket == @bucket
106
+ raise LogStash::ConfigurationError, "Logstash Input OSS Plugin: backup bucket and source bucket should be different"
107
+ end
108
+
109
+ unless @oss.doesBucketExist(@backup_to_bucket)
110
+ @oss.createBucket(@backup_to_bucket)
111
+ end
112
+ end
113
+
114
+ unless @backup_to_dir.nil?
115
+ Dir.mkdir(@backup_to_dir, 0700) unless File.exists?(@backup_to_dir)
116
+ end
117
+
118
+ if @mns_settings.include?(MNS_POLL_INTERVAL_SECONDS)
119
+ @interval = @mns_settings[MNS_POLL_INTERVAL_SECONDS]
120
+ else
121
+ @interval = 10
122
+ end
123
+
124
+ @mns_endpoint = @mns_settings[MNS_ENDPOINT]
125
+ @mns_queue = @mns_settings[MNS_QUEUE]
126
+ @mns_wait_seconds = @mns_settings[MNS_WAIT_SECONDS]
127
+ end
128
+
129
+ public
130
+ def run(queue)
131
+ @current_thread = Thread.current
132
+ Stud.interval(0.01) do
133
+ @logger.info "Start to poll message from MNS queue #{@mns_queue}"
134
+ process_objects(queue)
135
+ stop if stop?
136
+ if @stop_for_test and not @fetched
137
+ do_stop
138
+ end
139
+ sleep @interval unless @fetched
140
+ end
141
+ end
142
+
143
+ public
144
+ def stop
145
+ # @current_thread is initialized in the `#run` method,
146
+ # this variable is needed because the `#stop` is a called in another thread
147
+ # than the `#run` method and requiring us to call stop! with a explicit thread.
148
+ @logger.info("Logstash OSS Input Plugin is shutting down...")
149
+ Stud.stop!(@current_thread)
150
+ end
151
+
152
+ private
153
+ def process_objects(queue)
154
+ message = receive_message
155
+ if message.nil?
156
+ @fetched = false
157
+ else
158
+ @fetched = true
159
+ process(LogStash::Inputs::MNS::Message.new(@mns_queue, message), queue)
160
+ end
161
+ end
162
+
163
+ private
164
+ def receive_message
165
+ request_opts = {}
166
+ request_opts = { waitseconds: @mns_wait_seconds } if @mns_wait_seconds
167
+ opts = {
168
+ log: @logger,
169
+ method: 'GET',
170
+ endpoint: @mns_endpoint,
171
+ path: "/queues/#{@mns_queue}/messages",
172
+ access_key_id: @access_key_id,
173
+ access_key_secret: @access_key_secret
174
+ }
175
+ LogStash::Inputs::MNS::Request.new(opts, {}, request_opts).execute
176
+ end
177
+
178
+ private
179
+ def process(message, queue)
180
+ objects = get_objects(message)
181
+ objects.each do |object|
182
+ key = object.key
183
+ if key.end_with?("/")
184
+ @logger.info("Skip directory " + key)
185
+ elsif @prefix and @prefix != "" and not key.start_with?(@prefix)
186
+ @logger.info("Skip object " + key + " because object name does not match " + @prefix)
187
+ elsif @exclude_pattern and key =~ Regexp.new(@exclude_pattern)
188
+ @logger.info("Skip object " + key + " because object name matches exclude_pattern")
189
+ else
190
+ metadata = {}
191
+ begin
192
+ read_object(key) do |line, object_meta|
193
+ @codec.decode(line) do |event|
194
+ if event_is_metadata?(event)
195
+ @logger.debug('Event is metadata, updating the current cloudfront metadata', :event => event)
196
+ update_metadata(metadata, event)
197
+ else
198
+ decorate(event)
199
+
200
+ event.set("cloudfront_version", metadata[:cloudfront_version]) unless metadata[:cloudfront_version].nil?
201
+ event.set("cloudfront_fields", metadata[:cloudfront_fields]) unless metadata[:cloudfront_fields].nil?
202
+
203
+ if @include_object_properties
204
+ object_meta.each do |key, value|
205
+ event.set("[@metadata][oss][" + key + "]", value.to_s)
206
+ end
207
+ else
208
+ event.set("[@metadata][oss]", {})
209
+ end
210
+
211
+ event.set("[@metadata][oss][key]", key)
212
+ queue << event
213
+ end
214
+ end
215
+
216
+ @codec.flush do |event|
217
+ queue << event
218
+ end
219
+ end
220
+ backup_to_bucket(key)
221
+ backup_to_dir(key)
222
+ delete_file_from_bucket(key)
223
+ rescue Exception => e
224
+ # skip any broken file
225
+ @logger.error("Failed to read object. Skip processing.", :key => key, :exception => e.message)
226
+ end
227
+ end
228
+ end
229
+ delete_message(message)
230
+ end
231
+
232
+ private
233
+ def event_is_metadata?(event)
234
+ return false unless event.get("message").class == String
235
+ line = event.get("message")
236
+ version_metadata?(line) || fields_metadata?(line)
237
+ end
238
+
239
+ private
240
+ def version_metadata?(line)
241
+ line.start_with?('#Version: ')
242
+ end
243
+
244
+ private
245
+ def fields_metadata?(line)
246
+ line.start_with?('#Fields: ')
247
+ end
248
+
249
+ private
250
+ def update_metadata(metadata, event)
251
+ line = event.get('message').strip
252
+
253
+ if version_metadata?(line)
254
+ metadata[:cloudfront_version] = line.split(/#Version: (.+)/).last
255
+ end
256
+
257
+ if fields_metadata?(line)
258
+ metadata[:cloudfront_fields] = line.split(/#Fields: (.+)/).last
259
+ end
260
+ end
261
+
262
+ private
263
+ def read_object(key, &block)
264
+ @logger.info("Processing object " + key)
265
+ object = @oss.getObject(@bucket, key)
266
+ meta = object.getObjectMetadata.getRawMetadata
267
+ input = object.getObjectContent
268
+ content = input
269
+ if gzip?(key)
270
+ content = GZIPInputStream.new(input)
271
+ end
272
+
273
+ decoder = InputStreamReader.new(content, "UTF-8")
274
+ buffered = BufferedReader.new(decoder)
275
+
276
+ while (line = buffered.readLine)
277
+ block.call(line, meta)
278
+ end
279
+ ensure
280
+ buffered.close unless buffered.nil?
281
+ decoder.close unless decoder.nil?
282
+ content.close unless content.nil?
283
+ input.close unless input.nil?
284
+ end
285
+
286
+ private
287
+ def gzip?(filename)
288
+ filename.end_with?('.gz','.gzip')
289
+ end
290
+
291
+ private
292
+ def backup_to_bucket(key)
293
+ unless @backup_to_bucket.nil?
294
+ backup_key = "#{@backup_add_prefix}#{key}"
295
+ @oss.copyObject(@bucket, key, @backup_to_bucket, backup_key)
296
+ end
297
+ end
298
+
299
+ private
300
+ def backup_to_dir(key)
301
+ unless @backup_to_dir.nil?
302
+ file = @backup_to_dir + '/' + key
303
+
304
+ dirname = File.dirname(file)
305
+ unless File.directory?(dirname)
306
+ FileUtils.mkdir_p(dirname)
307
+ end
308
+
309
+ @oss.getObject(GetObjectRequest.new(@bucket, key), java.io.File.new(file))
310
+ end
311
+ end
312
+
313
+ private
314
+ def delete_file_from_bucket(key)
315
+ if @delete
316
+ @oss.deleteObject(@bucket, key)
317
+ end
318
+ end
319
+
320
+ private
321
+ def get_objects(message)
322
+ objects = []
323
+ events = JSON.parse(Base64.decode64(message.body))['events']
324
+ events.each do |event|
325
+ objects.push(OSSObject.new(event['eventName'],
326
+ @bucket,
327
+ event['oss']['object']['key'],
328
+ event['oss']['object']['size'],
329
+ event['oss']['object']['eTag']))
330
+ end
331
+ objects
332
+ end
333
+
334
+ private
335
+ def delete_message(message)
336
+ request_opts = { ReceiptHandle: message.receipt_handle }
337
+ opts = {
338
+ log: @logger,
339
+ method: 'DELETE',
340
+ endpoint: @mns_endpoint,
341
+ path: "/queues/#{@mns_queue}/messages",
342
+ access_key_id: @access_key_id,
343
+ access_key_secret: @access_key_secret
344
+ }
345
+ LogStash::Inputs::MNS::Request.new(opts, {}, request_opts).execute
346
+ end
347
+
348
+ # OSS Object class from MNS events
349
+ class OSSObject
350
+ attr_reader :event_name, :bucket, :key, :size, :etag
351
+ def initialize(event_name, bucket, key, size, etag)
352
+ @event_name = event_name
353
+ @bucket = bucket
354
+ @key = key
355
+ @size = size
356
+ @etag = etag
357
+ end
358
+ end
359
+
360
+ private
361
+ def initialize_oss_client
362
+ clientConf = ClientBuilderConfiguration.new
363
+ unless @additional_oss_settings.nil?
364
+ if @additional_oss_settings.include?(SECURE_CONNECTION_ENABLED_KEY)
365
+ clientConf.setProtocol(@additional_oss_settings[SECURE_CONNECTION_ENABLED_KEY] ?
366
+ com.aliyun.oss.common.comm.Protocol::HTTPS : com.aliyun.oss.common.comm.Protocol::HTTP)
367
+ end
368
+
369
+ if @additional_oss_settings.include?(MAX_CONNECTIONS_TO_OSS_KEY)
370
+ if @additional_oss_settings[MAX_CONNECTIONS_TO_OSS_KEY] <= 0
371
+ raise LogStash::ConfigurationError, "Logstash OSS Input plugin must have positive " + MAX_CONNECTIONS_TO_OSS_KEY
372
+ end
373
+ clientConf.setMaxConnections(@additional_oss_settings[MAX_CONNECTIONS_TO_OSS_KEY])
374
+ else
375
+ clientConf.setMaxConnections(1024)
376
+ end
377
+ end
378
+
379
+ clientConf.setUserAgent(clientConf.getUserAgent() + ", Logstash-oss/" + Version.version)
380
+ OSSClientBuilder.new().build(@endpoint, @access_key_id, @access_key_secret, clientConf)
381
+ end
382
+ end