logstash-input-oss 0.0.1-java

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