eventhub-processor 0.0.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 +7 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +1 -0
- data/lib/eventhub-processor.rb +22 -0
- data/lib/eventhub/configuration.rb +19 -0
- data/lib/eventhub/hash.rb +34 -0
- data/lib/eventhub/message.rb +106 -0
- data/lib/eventhub/multi_logger.rb +61 -0
- data/lib/eventhub/processor.rb +139 -0
- data/lib/eventhub/version.rb +3 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1d655c3ad766e679c042f39ba0b9959e9607fa11
|
4
|
+
data.tar.gz: f0cf376227b402a5491ac3bc45ae10b4625aad89
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b71cca2fbc7db9c483bd4cb689fb07efc61ca6409a21958c36a629198c5b1056ef3d26786ebb8edaa6aaeed3e2255a95e57449cf0af0e7f6a5269dd02da11f37
|
7
|
+
data.tar.gz: cd85de1eee523df747f5f198e1c55f9b7fa8c655cb190e7f73e6fd175454b1305b5f62cb8efc8c619262d61d8717dfb5480cde9bcd0d7fb7581bc3e903ee6283
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 thomis
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'amqp'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'json'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
require_relative 'eventhub/version'
|
7
|
+
require_relative 'eventhub/multi_logger'
|
8
|
+
|
9
|
+
require_relative 'eventhub/configuration'
|
10
|
+
require_relative 'eventhub/hash'
|
11
|
+
require_relative 'eventhub/processor'
|
12
|
+
require_relative 'eventhub/message'
|
13
|
+
|
14
|
+
module EventHub
|
15
|
+
def self.logger
|
16
|
+
unless @logger
|
17
|
+
a = Logger.new(STDOUT)
|
18
|
+
@logger = MultiLogger.new(a)
|
19
|
+
end
|
20
|
+
@logger
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module EventHub
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
attr_accessor :data
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@data = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_file(input, env='development')
|
13
|
+
tmp = JSON.parse( IO.read(input))
|
14
|
+
@data = tmp[env]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class ::Hash
|
2
|
+
|
3
|
+
# get value from provided key path, e.g. hash.get('a.b.c')
|
4
|
+
def get(key_path)
|
5
|
+
key_path.split('.').inject(self,:[])
|
6
|
+
rescue NoMethodError
|
7
|
+
return nil
|
8
|
+
end
|
9
|
+
|
10
|
+
# set value from provided key path, e.h. hash.set('a.b.c','new value')
|
11
|
+
# if overwrite is false, value will be set if it was nil previously
|
12
|
+
def set(key_path,value,overwrite=true)
|
13
|
+
*key_path, last = key_path.split(".")
|
14
|
+
if overwrite
|
15
|
+
key_path.inject(self) { |h,key| h.has_key?(key) ? h[key] : h[key]={}} [last] = value
|
16
|
+
else
|
17
|
+
key_path.inject(self) { |h,key| h.has_key?(key) ? h[key] : h[key]={}} [last] ||= value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# get all keys path, { 'a' => 'value1', 'b' => { 'c' => 'value2'}}.all_keys_with_path => ['a','b.c']
|
22
|
+
def all_keys_with_path(parent=nil)
|
23
|
+
a = []
|
24
|
+
each do |k,v|
|
25
|
+
if v.is_a?(Hash)
|
26
|
+
a << v.all_keys_with_path([parent,k].compact.join('.'))
|
27
|
+
else
|
28
|
+
a << "#{[parent,k].compact.join(".")}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
a.flatten
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module EventHub
|
2
|
+
|
3
|
+
class Message
|
4
|
+
|
5
|
+
VERSION = '1.0.0'
|
6
|
+
|
7
|
+
# Headers that are required (value can be nil) in order to pass valid?
|
8
|
+
REQUIRED_HEADERS = [
|
9
|
+
'message_id',
|
10
|
+
'version',
|
11
|
+
'created_at',
|
12
|
+
'origin.module_id',
|
13
|
+
'origin.type',
|
14
|
+
'origin.site_id',
|
15
|
+
'process.name',
|
16
|
+
'process.step_position',
|
17
|
+
'process.execution_id',
|
18
|
+
'status.retried_count',
|
19
|
+
'status.code',
|
20
|
+
'status.message'
|
21
|
+
]
|
22
|
+
|
23
|
+
attr_accessor :header, :body, :raw, :vhost, :routing_key
|
24
|
+
|
25
|
+
# Build accessors for all required headers
|
26
|
+
REQUIRED_HEADERS.each do |header|
|
27
|
+
name = header.gsub(/\./,"_")
|
28
|
+
|
29
|
+
define_method(name) do
|
30
|
+
self.header.get(header)
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method("#{name}=") do |value|
|
34
|
+
self.header.set(header,value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_json(json)
|
39
|
+
data = JSON.parse(json)
|
40
|
+
Eventhub::Message.new(data.get('header'), data.get('body'),json)
|
41
|
+
rescue => e
|
42
|
+
Eventhub::Message.new({ "status" => { "code" => STATUS_INVALID, "message" => "JSON parse error: #{e}" }} ,{},json)
|
43
|
+
end
|
44
|
+
|
45
|
+
# process_step_position should be
|
46
|
+
def initialize(header, body,raw=nil)
|
47
|
+
|
48
|
+
@header = header || {}
|
49
|
+
@body = body || {}
|
50
|
+
@raw = raw
|
51
|
+
|
52
|
+
# set message defaults, that we have required headers
|
53
|
+
now = Time.now
|
54
|
+
@header.set('message_id',UUIDTools::UUID.timestamp_create.to_s,false)
|
55
|
+
@header.set('version',VERSION,false)
|
56
|
+
@header.set('created_at',now.utc.strftime("%Y-%m-%dT%H:%M:%S.#{now.usec/1000}Z"),false)
|
57
|
+
|
58
|
+
@header.set('process.name',nil,false)
|
59
|
+
@header.set('process.execution_id',UUIDTools::UUID.timestamp_create.to_s,false)
|
60
|
+
@header.set('process.step_position',0,false)
|
61
|
+
|
62
|
+
@header.set('status.retried_count',0,false)
|
63
|
+
@header.set('status.code',STATUS_INITIAL,false)
|
64
|
+
@header.set('status.message',nil,false)
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def valid?
|
69
|
+
REQUIRED_HEADERS.all? { |key| @header.all_keys_with_path.include?(key) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def success?
|
73
|
+
self.status_code == STATUS_SUCCESS
|
74
|
+
end
|
75
|
+
|
76
|
+
def retry?
|
77
|
+
!success?
|
78
|
+
end
|
79
|
+
|
80
|
+
def initial?
|
81
|
+
self.status_code == STATUS_INITIAL
|
82
|
+
end
|
83
|
+
|
84
|
+
def retried?
|
85
|
+
self.status_code == STATUS_RETRIED
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_json
|
89
|
+
{'header' => self.header, 'body' => self.body}.to_json
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
"Message: message_id [#{self.message_id}], status.code [#{status_code}], status.message [#{status_message}], status.retried_count [#{status_retried_count}] "
|
94
|
+
end
|
95
|
+
|
96
|
+
def copy(args={})
|
97
|
+
copied_header = self.header.dup
|
98
|
+
copied_body = self.body.dup
|
99
|
+
|
100
|
+
args.each { |key,value| copied_header.set(key,value) } if args.is_a?(Hash)
|
101
|
+
Eventhub::Message.new(copied_header, copied_body)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
# format adaptation
|
4
|
+
class Logger
|
5
|
+
class Formatter
|
6
|
+
def call(severity, time, progname, msg)
|
7
|
+
time_in_string = "#{time.strftime("%Y-%m-%d %H:%M:%S")}.#{"%04d" % (time.usec/100)}"
|
8
|
+
[time_in_string,Process.pid,severity,msg].join("\t") + "\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
module EventHub
|
16
|
+
|
17
|
+
class MultiLogger
|
18
|
+
def initialize(*targets)
|
19
|
+
@targets = targets
|
20
|
+
end
|
21
|
+
|
22
|
+
def save_detailed_error(feedback,message=nil)
|
23
|
+
time = Time.now
|
24
|
+
stamp = "#{time.strftime("%Y%m%d_%H%M%S")}_#{"%03d" % (time.usec/1000)}"
|
25
|
+
filename = "#{stamp}.log"
|
26
|
+
|
27
|
+
FileUtils.makedirs("exceptions")
|
28
|
+
|
29
|
+
File.open("exceptions/#{filename}","w") do |output|
|
30
|
+
output.write("#{feedback}\n\n")
|
31
|
+
output.write("Exception: #{feedback.class.to_s}\n\n")
|
32
|
+
output.write("Call Stack:\n")
|
33
|
+
feedback.backtrace.each do |line|
|
34
|
+
output.write("#{line}\n")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# save message if provided
|
39
|
+
if message
|
40
|
+
File.open("exceptions/#{stamp}.msg.raw","wb") do |output|
|
41
|
+
output.write(message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
return stamp
|
46
|
+
end
|
47
|
+
|
48
|
+
%w(log debug info warn error).each do |m|
|
49
|
+
define_method(m) do |*args|
|
50
|
+
@targets.map { |t| t.send(m, *args) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module EventHub
|
2
|
+
class Processor
|
3
|
+
|
4
|
+
def hostname
|
5
|
+
configuration.get('server.hostname') || 'localhost'
|
6
|
+
end
|
7
|
+
|
8
|
+
def user
|
9
|
+
configuration.get('server.user') || 'admin'
|
10
|
+
end
|
11
|
+
|
12
|
+
def password
|
13
|
+
configuration.get('server.password') || 'admin'
|
14
|
+
end
|
15
|
+
|
16
|
+
def management_port
|
17
|
+
configuration.get('server.management_port') || 15672
|
18
|
+
end
|
19
|
+
|
20
|
+
def queue_name
|
21
|
+
configuration.get('processor.queue') || 'inbound'
|
22
|
+
end
|
23
|
+
|
24
|
+
def vhost
|
25
|
+
configuration.get('server.vhost') || nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def watchdog_cycle
|
29
|
+
configuration.get('processor.watchdog_cycle') || 5
|
30
|
+
end
|
31
|
+
|
32
|
+
def configuration
|
33
|
+
EventHub::Configuration.instance.data
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
def start
|
39
|
+
restart = true
|
40
|
+
while restart
|
41
|
+
|
42
|
+
begin
|
43
|
+
AMQP.start(configuration.get('server')) do |connection, open_ok|
|
44
|
+
|
45
|
+
# deal with tcp connection issues
|
46
|
+
connection.on_tcp_connection_loss do |conn, settings|
|
47
|
+
EventHub.logger.warn("Processor lost tcp connection. Trying to restart in 5 seconds...")
|
48
|
+
sleep 5
|
49
|
+
EventMachine.stop
|
50
|
+
end
|
51
|
+
|
52
|
+
# create channel
|
53
|
+
channel = AMQP::Channel.new(connection)
|
54
|
+
channel.auto_recovery = true
|
55
|
+
|
56
|
+
# connect to queue
|
57
|
+
queue = channel.queue(configuration.get('processor.queue'), durable: true, auto_delete: false)
|
58
|
+
|
59
|
+
# subscribe to queue
|
60
|
+
queue.subscribe do |metadata, payload|
|
61
|
+
handle_heartbeat(payload)
|
62
|
+
handle_message(metadata,payload)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Features to stop main event loop
|
66
|
+
stop_main_loop = Proc.new {
|
67
|
+
connection.disconnect {
|
68
|
+
EventHub.logger.info("Processor is stopping main event loop")
|
69
|
+
EventMachine.stop
|
70
|
+
restart = false
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
Signal.trap "TERM", stop_main_loop
|
75
|
+
Signal.trap "INT", stop_main_loop
|
76
|
+
|
77
|
+
EventMachine.add_timer(self.watchdog_cycle) { watchdog }
|
78
|
+
|
79
|
+
EventHub.logger.info("Processor is listening to queue [#{[configuration.get('server.vhost'),configuration.get('processor.queue')].compact.join(".")}]")
|
80
|
+
end
|
81
|
+
rescue => e
|
82
|
+
EventHub.logger.error("Unexpected exception: #{e}. Trying to restart in 5 seconds...")
|
83
|
+
sleep 5
|
84
|
+
end
|
85
|
+
|
86
|
+
end # while
|
87
|
+
|
88
|
+
EventHub.logger.info("Processor has been stopped")
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_message(metadata,payload)
|
92
|
+
raise "Please implement method in derived class"
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_heartbeat(message)
|
96
|
+
# sends a standard message back to dispatcher
|
97
|
+
end
|
98
|
+
|
99
|
+
def watchdog
|
100
|
+
begin
|
101
|
+
response = RestClient.get "http://#{self.user}:#{self.password}@#{hostname}:#{management_port}/api/queues/#{self.vhost}/#{self.queue_name}/bindings", { :content_type => :json}
|
102
|
+
data = JSON.parse(response.body)
|
103
|
+
|
104
|
+
if response.code != 200
|
105
|
+
EventHub.logger.warn("Watchdog: Server did not answered properly. Trying to restart in 5 seconds...")
|
106
|
+
sleep 5
|
107
|
+
EventMachine.stop
|
108
|
+
elsif data.size == 0
|
109
|
+
EventHub.logger.warn("Watchdog: Something is wrong with the vhost, queue, and/or bindings. Trying to restart in 5 seconds...")
|
110
|
+
sleep 5
|
111
|
+
EventMachine.stop
|
112
|
+
else
|
113
|
+
# Watchdog is happy :-)
|
114
|
+
end
|
115
|
+
|
116
|
+
rescue => e
|
117
|
+
EventHub.logger.error("Watchdog: Unexpected exception: #{e}. Trying to restart in 5 seconds...")
|
118
|
+
sleep 5
|
119
|
+
EventMachine.stop
|
120
|
+
end
|
121
|
+
|
122
|
+
# place next time
|
123
|
+
EventMachine.add_timer(watchdog_cycle) { watchdog }
|
124
|
+
end
|
125
|
+
|
126
|
+
def sent_to_dispatcher(payload)
|
127
|
+
# send_connection = AMQP.connect({hostname: self.hostname, user: self.user, password: self.password, vhost: "event_hub"})
|
128
|
+
|
129
|
+
# send_channel = AMQP::Channel.new(send_connection)
|
130
|
+
# send_exchange = send_channel.direct("")
|
131
|
+
|
132
|
+
# send_exchange.publish payload, :routing_key => 'inbound'
|
133
|
+
|
134
|
+
# send_channel.close
|
135
|
+
# send_conncetion.close
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eventhub-processor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thomas Steiner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rest-client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: amqp
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Gem to build Event Hub processors
|
70
|
+
email:
|
71
|
+
- thomas.steiner@ikey.ch
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- lib/eventhub/configuration.rb
|
77
|
+
- lib/eventhub/hash.rb
|
78
|
+
- lib/eventhub/message.rb
|
79
|
+
- lib/eventhub/multi_logger.rb
|
80
|
+
- lib/eventhub/processor.rb
|
81
|
+
- lib/eventhub/version.rb
|
82
|
+
- lib/eventhub-processor.rb
|
83
|
+
- LICENSE.txt
|
84
|
+
- Rakefile
|
85
|
+
homepage: http://github.com/thomis/eventhub-processor
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.0.3
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Gem to build Event Hub processors
|
109
|
+
test_files: []
|