eventhub-processor 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YjAxYjAwMjEyMzZiODlhNTIzNjI2MTQ5OWI2ZDNkYzJlM2I4MTE4MQ==
5
- data.tar.gz: !binary |-
6
- M2Q2NjM4YTg2YTBmMWViMzcyYTU0ODQ3NDAzZjgyYzhlNjlkYjQwYQ==
2
+ SHA1:
3
+ metadata.gz: ea2ee52ffdb9af4cd5133eb9febcf4e51e1ca363
4
+ data.tar.gz: bafee6fc1bb89684fd29ac0b4d3119bd6c82af28
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZGNlNGQ1MzE4NTRiYmFkMDE0MjY0ZTFlNjZiMGZlNGQzZjYxZTNkMWFhYzJl
10
- MzUyNGVlYTkzNDYyYzc4YTE3YmQ2N2NmM2M0NjdjYjVhNGE3MmEwZTI2M2Ux
11
- NDVmOWNiOGM2OGExYjQwN2U0NWIyZGMwYmYwZjE5ZmQ4MzQ0NjI=
12
- data.tar.gz: !binary |-
13
- ZGExOGJlMTVkNDA4NzVlOWRlNjRjMDVmMmUzY2M0MDcwYTNlMjRjNDBiMDFk
14
- ZDg4OGY0NmE0YWU4OTllMjllYWQ4YjNkN2ZiMmJjNTYyNjAwZTgwMjM3NGEw
15
- OWY4ZTBiMmYzYjFlMzQ0MzMyMWZiZTFkYWU2NmM2NzE2M2RhYjI=
6
+ metadata.gz: aad91acb51fff2d8078a85f762c5a7b43bb65d2b187a75760d01f3697cc67c445aa0e276675239506d6c35807b10f67cca9026f0e92463799f712d9339395023
7
+ data.tar.gz: ac51f8ba1761f9e833fc621df03f41826afbdfb2985a538d8eb5a4cd3861c2858ad504bb260fd1c546ef2c0bc35a9c82dbae1fa56bfe6f475799d752c8490a96
@@ -1,6 +1,7 @@
1
1
  module EventHub
2
2
 
3
3
  class ArgumentParser
4
+
4
5
  def self.parse(args)
5
6
  # The options specified on the command line will be collected in *options*.
6
7
  # We set default values here.
@@ -10,6 +11,7 @@ module EventHub
10
11
 
11
12
  opt_parser = OptionParser.new do |opts|
12
13
  opts.banner = "Usage: #{args[0]}.rb [options]"
14
+ yield(opts, options) if block_given? # allow to add more options
13
15
 
14
16
  opts.on("-e", "--environment ENVIRONMENT","Environment the processor is running") do |environment|
15
17
  options.environment = environment
@@ -0,0 +1,2 @@
1
+ class EventHub::BaseException < RuntimeError
2
+ end
@@ -1,27 +1,26 @@
1
- module EventHub
2
-
3
- class Configuration
4
- include Singleton
5
- include Helper
6
-
7
- attr_accessor :data, :folder, :environment
8
-
9
- def initialize
10
- @data = nil
11
- @folder = Dir.pwd
12
- @environment = 'development'
13
- end
14
-
15
- def load_file(input, env = 'development')
16
- tmp = JSON.parse( IO.read(input))
17
- @data = tmp[env]
18
- @environment = env
19
- true
20
- rescue => e
21
- EventHub.logger.info("Unexpected exception while loading configuration [#{input}]: #{format_string(e.message)}")
22
- false
23
- end
24
-
25
- end
26
-
1
+ module EventHub
2
+
3
+ class Configuration
4
+ include Singleton
5
+ include Helper
6
+
7
+ attr_accessor :data, :folder, :environment
8
+
9
+ def initialize
10
+ @data = nil
11
+ @environment = 'development'
12
+ end
13
+
14
+ def load_file(input, env = 'development')
15
+ json = JSON.parse(IO.read(input))
16
+ @data = json[env]
17
+ @environment = env
18
+ true
19
+ rescue => e
20
+ EventHub.logger.info("Unexpected exception while loading configuration [#{input}]: #{format_string(e.message)}")
21
+ false
22
+ end
23
+
24
+ end
25
+
27
26
  end
@@ -1,16 +1,16 @@
1
1
  module EventHub
2
2
 
3
- EH_X_INBOUND = 'event_hub.inbound'
4
-
5
- STATUS_INITIAL = 0 # To be set when dispatcher needs to dispatch to first process step.
6
- STATUS_SUCCESS = 200 # To be set to indicate successful processed message. Dispatcher will routes message to the next step.
7
- STATUS_RETRY = 300 # To be set to trigger retry cycle controlled by the dispatcher
8
- STATUS_RETRY_PENDING = 301 # Set and used by the dispatcher only.
9
- # Set before putting the message into a retry queue.
10
- # Once message has been retried it will sent do the same step with status.code = STATUS_SUCCESS
11
- STATUS_INVALID = 400 # To be set to indicate invalid message (not json, invalid Event Hub Message).
12
- # Dispatcher will publish message to the invalid queue.
13
- STATUS_DEADLETTER = 500 # To be set by dispatcher, processor or channel adapters to indicate
14
- # that message needs to be dead-lettered. Rejected messages could miss the
15
- # status.code = STATUS_DEADLETTER due to the RabbitMQ deadletter exchange mechanism.
3
+ EH_X_INBOUND = 'event_hub.inbound'
4
+
5
+ STATUS_INITIAL = 0 # To be set when dispatcher needs to dispatch to first process step.
6
+ STATUS_SUCCESS = 200 # To be set to indicate successful processed message. Dispatcher will routes message to the next step.
7
+ STATUS_RETRY = 300 # To be set to trigger retry cycle controlled by the dispatcher
8
+ STATUS_RETRY_PENDING = 301 # Set and used by the dispatcher only.
9
+ # Set before putting the message into a retry queue.
10
+ # Once message has been retried it will sent do the same step with status.code = STATUS_SUCCESS
11
+ STATUS_INVALID = 400 # To be set to indicate invalid message (not json, invalid Event Hub Message).
12
+ # Dispatcher will publish message to the invalid queue.
13
+ STATUS_DEADLETTER = 500 # To be set by dispatcher, processor or channel adapters to indicate
14
+ # that message needs to be dead-lettered. Rejected messages could miss the
15
+ # status.code = STATUS_DEADLETTER due to the RabbitMQ deadletter exchange mechanism.
16
16
  end
@@ -0,0 +1,77 @@
1
+ module EventHub
2
+
3
+ class Heartbeat
4
+ include Helper
5
+
6
+ attr_reader :processor, :statistics, :started_at
7
+
8
+ def initialize(processor)
9
+ @started_at = Time.now
10
+ @processor = processor
11
+ @statistics = @processor.statistics
12
+ end
13
+
14
+
15
+ def build_message(action = "running")
16
+ message = ::EventHub::Message.new
17
+ message.origin_module_id = processor.name
18
+ message.origin_type = "processor"
19
+ message.origin_site_id = 'global'
20
+
21
+ message.process_name = 'event_hub.heartbeat'
22
+
23
+ now = Time.now
24
+
25
+ # message structure needs more changes
26
+ message.body = {
27
+ version: processor.version,
28
+ action: action,
29
+ pid: Process.pid,
30
+ process_name: 'event_hub.heartbeat',
31
+
32
+ heartbeat: {
33
+ started: now_stamp(started_at),
34
+ stamp_last_beat: now_stamp(now),
35
+ uptime_in_ms: (now - started_at)*1000,
36
+ heartbeat_cycle_in_ms: processor.heartbeat_cycle_in_s * 1000,
37
+ queues_consuming_from: processor.listener_queues,
38
+ queues_publishing_to: ['event_hub.inbound'], # needs more dynamic in the future
39
+ host: Socket.gethostname,
40
+ addresses: addresses,
41
+ messages: {
42
+ total: statistics.messages_total,
43
+ successful: statistics.messages_successful,
44
+ unsuccessful: statistics.messages_unsuccessful,
45
+ average_size: statistics.messages_average_size,
46
+ average_process_time_in_ms: statistics.messages_average_process_time*1000,
47
+ total_prozess_time_in_ms: statistics.messages_total_process_time*1000
48
+ }
49
+ }
50
+ }
51
+ message
52
+ end
53
+
54
+ private
55
+
56
+ def addresses
57
+ interfaces = Socket.getifaddrs.select do |interface|
58
+ !interface.addr.ipv4_loopback? && !interface.addr.ipv6_loopback?
59
+ end
60
+
61
+ interfaces.map do |interface|
62
+ begin
63
+ {
64
+ :interface => interface.name,
65
+ :host_name => Socket.gethostname,
66
+ :ip_address => interface.addr.ip_address
67
+ }
68
+ rescue
69
+ nil # will be ignored
70
+ end
71
+ end.compact
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -1,55 +1,49 @@
1
- module EventHub
2
-
3
- module Helper
4
-
5
- # converts a class like EventHub::PlateStore::MyClassName to an array ['event_hub','plate_store','my_class_name']
6
- def class_to_array(class_name)
7
- class_name.to_s.split("::").map{ |m| m.gsub(/[A-Z]/) { |c| "_#{c}"}.gsub(/^_/,"").downcase }
8
- end
9
-
10
- # replaces CR, LF, CRLF with ";" and cut's string to requied length by adding "..." if string would be longer
11
- def format_string(message,max_characters=80)
12
- max_characters = 5 if max_characters < 5
13
- m = message.gsub(/\r\n|\n|\r/m,";")
14
- return (m[0..max_characters-4] + "...") if m.size > max_characters
15
- return m
16
- end
17
-
18
- def get_host
19
- Socket.gethostname
20
- end
21
-
22
- def get_ip_adresses
23
- list = Socket.ip_address_list.map { |i| i.ip_address unless i.ipv4_loopback? || i.ipv6_loopback? }.compact
24
- return list.size == 0 ? ["no ip address found (loopback excluded)"] : list
25
- end
26
-
27
- def now_stamp(now=nil)
28
- now ||= Time.now
29
- now.utc.strftime("%Y-%m-%dT%H:%M:%S.#{now.usec}Z")
30
- end
31
-
32
- def duration(difference)
33
- rest, secs = difference.divmod( 60 ) # self is the time difference t2 - t1
34
- rest, mins = rest.divmod( 60 )
35
- days, hours = rest.divmod( 24 )
36
- secs = secs.truncate
37
- milliseconds = ((difference - difference.truncate)*1000).round
38
-
39
- result = []
40
- result << "#{days} days" if days > 1
41
- result << "#{days} day" if days == 1
42
- result << "#{hours} hours" if hours > 1
43
- result << "#{hours} hour" if hours == 1
44
- result << "#{mins} minutes" if mins > 1
45
- result << "#{mins} minute" if mins == 1
46
- result << "#{secs} seconds" if secs > 1
47
- result << "#{secs} second" if secs == 1
48
- result << "#{milliseconds} milliseconds" if milliseconds > 1
49
- result << "#{milliseconds} millisecond" if milliseconds == 1
50
- return result.join(' ')
51
- end
52
-
53
- end
54
-
55
- end
1
+ module EventHub
2
+
3
+ module Helper
4
+
5
+ # converts a class like EventHub::PlateStore::MyClassName to an array ['event_hub','plate_store','my_class_name']
6
+ def class_to_array(class_name)
7
+ class_name.to_s.split("::").map{ |m| m.gsub(/[A-Z]/) { |c| "_#{c}"}.gsub(/^_/,"").downcase }
8
+ end
9
+
10
+ # replaces CR, LF, CRLF with ";" and cut's string to requied length by adding "..." if string would be longer
11
+ def format_string(message,max_characters=80)
12
+ max_characters = 5 if max_characters < 5
13
+ m = message.gsub(/\r\n|\n|\r/m,";")
14
+ return (m[0..max_characters-4] + "...") if m.size > max_characters
15
+ return m
16
+ end
17
+
18
+ def now_stamp(now=nil)
19
+ now ||= Time.now
20
+ now.utc.strftime("%Y-%m-%dT%H:%M:%S.#{now.usec}Z")
21
+ end
22
+
23
+ def duration(difference)
24
+ negative = difference < 0
25
+ difference = difference.abs
26
+
27
+ rest, secs = difference.divmod( 60 ) # self is the time difference t2 - t1
28
+ rest, mins = rest.divmod( 60 )
29
+ days, hours = rest.divmod( 24 )
30
+ secs = secs.truncate
31
+ milliseconds = ((difference - difference.truncate)*1000).round
32
+
33
+ result = []
34
+ result << "#{days} days" if days > 1
35
+ result << "#{days} day" if days == 1
36
+ result << "#{hours} hours" if hours > 1
37
+ result << "#{hours} hour" if hours == 1
38
+ result << "#{mins} minutes" if mins > 1
39
+ result << "#{mins} minute" if mins == 1
40
+ result << "#{secs} seconds" if secs > 1
41
+ result << "#{secs} second" if secs == 1
42
+ result << "#{milliseconds} milliseconds" if milliseconds > 1
43
+ result << "#{milliseconds} millisecond" if milliseconds == 1
44
+ return (negative ? "-" : "") + result.join(' ')
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -1,128 +1,138 @@
1
- module EventHub
2
-
3
- class Message
4
- include Helper
5
-
6
- VERSION = '1.0.0'
7
-
8
- # Headers that are required (value can be nil) in order to pass valid?
9
- REQUIRED_HEADERS = [
10
- 'message_id',
11
- 'version',
12
- 'created_at',
13
- 'origin.module_id',
14
- 'origin.type',
15
- 'origin.site_id',
16
- 'process.name',
17
- 'process.step_position',
18
- 'process.execution_id',
19
- 'status.retried_count',
20
- 'status.code',
21
- 'status.message'
22
- ]
23
-
24
- attr_accessor :header, :body, :raw, :vhost, :routing_key
25
-
26
- # Build accessors for all required headers
27
- REQUIRED_HEADERS.each do |header|
28
- name = header.gsub(/\./,"_")
29
-
30
- define_method(name) do
31
- self.header.get(header)
32
- end
33
-
34
- define_method("#{name}=") do |value|
35
- self.header.set(header,value)
36
- end
37
- end
38
-
39
- def self.from_json(raw)
40
- data = JSON.parse(raw)
41
- Message.new(data.get('header'), data.get('body'),raw)
42
- rescue => e
43
- Message.new({ "status" => { "code" => STATUS_INVALID, "message" => "JSON parse error: #{e}" }} ,{ "original_message_base64_encoded" => Base64.encode64(raw)},raw)
44
- end
45
-
46
- # process_step_position should be
47
- def initialize(header=nil, body=nil,raw=nil)
48
-
49
- @header = header || {}
50
- @body = body || {}
51
- @raw = raw
52
-
53
- # set message defaults, that we have required headers
54
- @header.set('message_id',UUIDTools::UUID.timestamp_create.to_s,false)
55
- @header.set('version',VERSION,false)
56
- @header.set('created_at',now_stamp,false)
57
-
58
- @header.set('origin.module_id','undefined',false)
59
- @header.set('origin.type','undefined',false)
60
- @header.set('origin.site_id','undefined',false)
61
-
62
- @header.set('process.name','undefined',false)
63
- @header.set('process.execution_id',UUIDTools::UUID.timestamp_create.to_s,false)
64
- @header.set('process.step_position',0,false)
65
-
66
- @header.set('status.retried_count',0,false)
67
- @header.set('status.code',STATUS_INITIAL,false)
68
- @header.set('status.message','',false)
69
-
70
- end
71
-
72
- def valid?
73
- # check for existence and defined value
74
- REQUIRED_HEADERS.all? { |key| @header.all_keys_with_path.include?(key) && !!self.send(key.gsub(/\./,"_").to_sym)}
75
- end
76
-
77
- def success?
78
- self.status_code == STATUS_SUCCESS
79
- end
80
-
81
- def retry?
82
- !success?
83
- end
84
-
85
- def initial?
86
- self.status_code == STATUS_INITIAL
87
- end
88
-
89
- def retry_pending?
90
- self.status_code == STATUS_RETRY_PENDING
91
- end
92
-
93
- def to_json
94
- {'header' => self.header, 'body' => self.body}.to_json
95
- end
96
-
97
- def to_s
98
- "Msg: process [#{self.process_name},#{self.process_step_position},#{self.process_execution_id}], status [#{self.status_code},#{self.status_message},#{self.status_retried_count}]"
99
- end
100
-
101
- # copies the message and set's provided status code (default: success), actual stamp, and a new message id
102
- def copy(status_code=STATUS_SUCCESS)
103
-
104
- # use Marshal dump and load to make a deep object copy
105
- copied_header = Marshal.load( Marshal.dump(header))
106
- copied_body = Marshal.load( Marshal.dump(body))
107
-
108
- copied_header.set("message_id",UUIDTools::UUID.timestamp_create.to_s)
109
- copied_header.set("created_at",now_stamp)
110
- copied_header.set("status.code",status_code)
111
-
112
- Message.new(copied_header, copied_body)
113
- end
114
-
115
- def self.translate_status_code(code)
116
- case code
117
- when EventHub::STATUS_INITIAL then return 'STATUS_INITIAL'
118
- when EventHub::STATUS_SUCCESS then return 'STATUS_SUCCESS'
119
- when EventHub::STATUS_RETRY then return 'STATUS_RETRY'
120
- when EventHub::STATUS_RETRY_PENDING then return 'STATUS_RETRY_PENDING'
121
- when EventHub::STATUS_INVALID then return 'STATUS_INVALID'
122
- when EventHub::STATUS_DEADLETTER then return 'STATUS_DEADLETTER'
123
- end
124
- end
125
-
126
- end
127
-
128
- end
1
+ module EventHub
2
+
3
+ class Message
4
+ include Helper
5
+
6
+ VERSION = '1.0.0'
7
+
8
+ # Headers that are required (value can be nil) in order to pass valid?
9
+ REQUIRED_HEADERS = [
10
+ 'message_id',
11
+ 'version',
12
+ 'created_at',
13
+ 'origin.module_id',
14
+ 'origin.type',
15
+ 'origin.site_id',
16
+ 'process.name',
17
+ 'process.step_position',
18
+ 'process.execution_id',
19
+ 'status.retried_count',
20
+ 'status.code',
21
+ 'status.message'
22
+ ]
23
+
24
+ attr_accessor :header, :body, :raw, :vhost, :routing_key
25
+
26
+ # Build accessors for all required headers
27
+ REQUIRED_HEADERS.each do |header|
28
+ name = header.gsub(/\./,"_")
29
+
30
+ define_method(name) do
31
+ self.header.get(header)
32
+ end
33
+
34
+ define_method("#{name}=") do |value|
35
+ self.header.set(header,value)
36
+ end
37
+ end
38
+
39
+ def self.from_json(raw)
40
+ data = JSON.parse(raw)
41
+ Message.new(data.get('header'), data.get('body'),raw)
42
+ rescue => e
43
+ Message.new({ "status" => { "code" => STATUS_INVALID, "message" => "JSON parse error: #{e}" }} ,{ "original_message_base64_encoded" => Base64.encode64(raw)},raw)
44
+ end
45
+
46
+ def initialize(header = nil, body = nil, raw = nil)
47
+
48
+ @header = header || {}
49
+ @body = body || {}
50
+ @raw = raw
51
+
52
+ # set message defaults, that we have required headers
53
+ @header.set('message_id', UUIDTools::UUID.timestamp_create.to_s, false)
54
+ @header.set('version', VERSION, false)
55
+ @header.set('created_at', now_stamp, false)
56
+
57
+ @header.set('origin.module_id', 'undefined', false)
58
+ @header.set('origin.type', 'undefined', false)
59
+ @header.set('origin.site_id', 'undefined', false)
60
+
61
+ @header.set('process.name', 'undefined', false)
62
+ @header.set('process.execution_id', UUIDTools::UUID.timestamp_create.to_s, false)
63
+ @header.set('process.step_position', 0, false)
64
+
65
+ @header.set('status.retried_count', 0, false)
66
+ @header.set('status.code', STATUS_INITIAL, false)
67
+ @header.set('status.message', '', false)
68
+
69
+ end
70
+
71
+ def valid?
72
+ # check for existence and defined value
73
+ REQUIRED_HEADERS.all? { |key| @header.all_keys_with_path.include?(key) && !!self.send(key.gsub(/\./,"_").to_sym)}
74
+ end
75
+
76
+ def success?
77
+ self.status_code == STATUS_SUCCESS
78
+ end
79
+
80
+ def retry?
81
+ !success?
82
+ end
83
+
84
+ def initial?
85
+ self.status_code == STATUS_INITIAL
86
+ end
87
+
88
+ def retry_pending?
89
+ self.status_code == STATUS_RETRY_PENDING
90
+ end
91
+
92
+ def invalid?
93
+ self.status_code == STATUS_INVALID
94
+ end
95
+
96
+ def to_json
97
+ {'header' => self.header, 'body' => self.body}.to_json
98
+ end
99
+
100
+ def to_s
101
+ "Msg: process [#{self.process_name},#{self.process_step_position},#{self.process_execution_id}], status [#{self.status_code},#{self.status_message},#{self.status_retried_count}]"
102
+ end
103
+
104
+ # copies the message and set's provided status code (default: success), actual stamp, and a new message id
105
+ def copy(status_code = STATUS_SUCCESS)
106
+
107
+ # use Marshal dump and load to make a deep object copy
108
+ copied_header = Marshal.load( Marshal.dump(header))
109
+ copied_body = Marshal.load( Marshal.dump(body))
110
+
111
+ copied_header.set("message_id",UUIDTools::UUID.timestamp_create.to_s)
112
+ copied_header.set("created_at",now_stamp)
113
+ copied_header.set("status.code",status_code)
114
+
115
+ Message.new(copied_header, copied_body)
116
+ end
117
+
118
+ def append_to_execution_history(processor_name)
119
+ unless header.get('execution_history')
120
+ header.set('execution_history', [])
121
+ end
122
+ header.get('execution_history') << {'processor' => processor_name, 'timestamp' => now_stamp}
123
+ end
124
+
125
+ def self.translate_status_code(code)
126
+ case code
127
+ when EventHub::STATUS_INITIAL then return 'STATUS_INITIAL'
128
+ when EventHub::STATUS_SUCCESS then return 'STATUS_SUCCESS'
129
+ when EventHub::STATUS_RETRY then return 'STATUS_RETRY'
130
+ when EventHub::STATUS_RETRY_PENDING then return 'STATUS_RETRY_PENDING'
131
+ when EventHub::STATUS_INVALID then return 'STATUS_INVALID'
132
+ when EventHub::STATUS_DEADLETTER then return 'STATUS_DEADLETTER'
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,31 @@
1
+ class EventHub::MessageProcessor
2
+ attr_reader :processor
3
+
4
+ def initialize(processor)
5
+ @processor = processor
6
+ end
7
+
8
+ def process(params, payload)
9
+ messages_to_send = []
10
+
11
+ # try to convert to EventHub message
12
+ message = EventHub::Message.from_json(payload)
13
+ EventHub.logger.info("-> #{message.to_s}")
14
+
15
+ message.append_to_execution_history(self.processor.name)
16
+
17
+ if message.invalid?
18
+ messages_to_send << message
19
+ EventHub.logger.info("-> #{message.to_s} => Put to queue [#{EventHub::EH_X_INBOUND}].")
20
+ else
21
+ # pass received message to handler or dervied handler
22
+ if processor.method(:handle_message).arity == 1
23
+ messages_to_send = Array(processor.handle_message(message))
24
+ else
25
+ messages_to_send = Array(processor.handle_message(message,params))
26
+ end
27
+ end
28
+
29
+ messages_to_send
30
+ end
31
+ end