eventhub-processor 0.2.3 → 0.3.0

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