karafka 2.4.8 → 2.4.9
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +0 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +8 -5
- data/Gemfile.lock +23 -14
- data/bin/integrations +5 -0
- data/certs/cert.pem +26 -0
- data/config/locales/errors.yml +4 -0
- data/config/locales/pro_errors.yml +17 -0
- data/karafka.gemspec +1 -1
- data/lib/karafka/admin.rb +42 -0
- data/lib/karafka/contracts/config.rb +2 -0
- data/lib/karafka/errors.rb +3 -2
- data/lib/karafka/pro/loader.rb +2 -1
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +16 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +5 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +17 -1
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +17 -1
- data/lib/karafka/pro/processing/strategies/dlq/mom.rb +22 -6
- data/lib/karafka/pro/recurring_tasks/consumer.rb +105 -0
- data/lib/karafka/pro/recurring_tasks/contracts/config.rb +53 -0
- data/lib/karafka/pro/recurring_tasks/contracts/task.rb +41 -0
- data/lib/karafka/pro/recurring_tasks/deserializer.rb +35 -0
- data/lib/karafka/pro/recurring_tasks/dispatcher.rb +87 -0
- data/lib/karafka/pro/recurring_tasks/errors.rb +34 -0
- data/lib/karafka/pro/recurring_tasks/executor.rb +152 -0
- data/lib/karafka/pro/recurring_tasks/listener.rb +38 -0
- data/lib/karafka/pro/recurring_tasks/matcher.rb +38 -0
- data/lib/karafka/pro/recurring_tasks/schedule.rb +63 -0
- data/lib/karafka/pro/recurring_tasks/serializer.rb +113 -0
- data/lib/karafka/pro/recurring_tasks/setup/config.rb +52 -0
- data/lib/karafka/pro/recurring_tasks/task.rb +151 -0
- data/lib/karafka/pro/recurring_tasks.rb +87 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +130 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/config.rb +28 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +40 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/proxy.rb +27 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/recurring_tasks.rb +25 -0
- data/lib/karafka/processing/strategies/dlq.rb +16 -2
- data/lib/karafka/processing/strategies/dlq_mom.rb +25 -6
- data/lib/karafka/processing/worker.rb +11 -1
- data/lib/karafka/railtie.rb +11 -22
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +3 -0
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +7 -2
- data/lib/karafka/routing/features/eofed/contracts/topic.rb +12 -0
- data/lib/karafka/routing/topic.rb +14 -0
- data/lib/karafka/setup/config.rb +3 -0
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +44 -24
- metadata.gz.sig +0 -0
- data/certs/cert_chain.pem +0 -26
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Recurring Tasks related contracts
|
18
|
+
module Contracts
|
19
|
+
# Ensures that task details are as expected
|
20
|
+
class Task < ::Karafka::Contracts::Base
|
21
|
+
configure do |config|
|
22
|
+
config.error_messages = YAML.safe_load(
|
23
|
+
File.read(
|
24
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
|
25
|
+
)
|
26
|
+
).fetch('en').fetch('validations').fetch('recurring_tasks')
|
27
|
+
end
|
28
|
+
|
29
|
+
# Regexp to ensure all tasks ids are URL safe
|
30
|
+
ID_REGEXP = /\A[a-zA-Z0-9_-]{1,}\z/
|
31
|
+
|
32
|
+
required(:id) { |val| val.is_a?(String) && val.match?(ID_REGEXP) }
|
33
|
+
required(:cron) { |val| val.is_a?(String) && val.length.positive? }
|
34
|
+
required(:enabled) { |val| [true, false].include?(val) }
|
35
|
+
required(:changed) { |val| [true, false].include?(val) }
|
36
|
+
required(:previous_time) { |val| val.is_a?(Numeric) || val.is_a?(Time) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Recurring Tasks data deserializer. We compress data ourselves because we cannot rely on
|
18
|
+
# any external optional features like certain compression types, etc. By doing this that way
|
19
|
+
# we can ensure we have full control over the compression.
|
20
|
+
#
|
21
|
+
# @note We use `symbolize_names` because we want to use the same convention of hash building
|
22
|
+
# for producing, consuming and displaying related data as in other places.
|
23
|
+
class Deserializer
|
24
|
+
# @param message [::Karafka::Messages::Message]
|
25
|
+
# @return [Hash] deserialized data
|
26
|
+
def call(message)
|
27
|
+
::JSON.parse(
|
28
|
+
Zlib::Inflate.inflate(message.raw_payload),
|
29
|
+
symbolize_names: true
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Dispatches appropriate recurring tasks related messages to expected topics
|
18
|
+
class Dispatcher
|
19
|
+
class << self
|
20
|
+
# Snapshots to Kafka current schedule state
|
21
|
+
def schedule
|
22
|
+
produce(
|
23
|
+
topics.schedules,
|
24
|
+
'state:schedule',
|
25
|
+
serializer.schedule(::Karafka::Pro::RecurringTasks.schedule)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Dispatches the command request
|
30
|
+
#
|
31
|
+
# @param name [String, Symbol] name of the command we want to deal with in the process
|
32
|
+
# @param task_id [String] id of the process. We use name instead of id only
|
33
|
+
# because in the web ui we work with the full name and it is easier. Since
|
34
|
+
def command(name, task_id)
|
35
|
+
produce(
|
36
|
+
topics.schedules,
|
37
|
+
"command:#{name}:#{task_id}",
|
38
|
+
serializer.command(name, task_id)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Dispatches the task execution log record
|
43
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
44
|
+
def log(event)
|
45
|
+
produce(
|
46
|
+
topics.logs,
|
47
|
+
event[:task].id,
|
48
|
+
serializer.log(event)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# @return [::WaterDrop::Producer] web ui producer
|
55
|
+
def producer
|
56
|
+
::Karafka::App.config.recurring_tasks.producer
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] consumers commands topic
|
60
|
+
def topics
|
61
|
+
::Karafka::App.config.recurring_tasks.topics
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Serializer]
|
65
|
+
def serializer
|
66
|
+
Serializer.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# Converts payload to json, compresses it and dispatches to Kafka
|
70
|
+
#
|
71
|
+
# @param topic [String] target topic
|
72
|
+
# @param key [String]
|
73
|
+
# @param payload [Hash] hash with payload
|
74
|
+
def produce(topic, key, payload)
|
75
|
+
producer.produce_async(
|
76
|
+
topic: topic,
|
77
|
+
key: key,
|
78
|
+
partition: 0,
|
79
|
+
payload: payload,
|
80
|
+
headers: { 'zlib' => 'true' }
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Recurring Tasks related errors
|
18
|
+
module Errors
|
19
|
+
# Base for all the recurring tasks errors
|
20
|
+
BaseError = Class.new(::Karafka::Errors::BaseError)
|
21
|
+
|
22
|
+
# Raised when older cron manager version is trying to work with newer schema
|
23
|
+
IncompatibleSchemaError = Class.new(BaseError)
|
24
|
+
|
25
|
+
# Raised if you use versioned schedule and an existing schedule from Kafka was picked up
|
26
|
+
# by a process with older schedule version.
|
27
|
+
# This is a safe-guard to protect against a cases where there would be a temporary
|
28
|
+
# reassignment of newer schedule data into older process during deployment. It should go
|
29
|
+
# away once all processes are rolled.
|
30
|
+
IncompatibleScheduleError = Class.new(BaseError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Executor is responsible for management of the state of recurring tasks schedule and
|
18
|
+
# is the heart of recurring tasks. It coordinates the replaying process as well as
|
19
|
+
# tracking of data on changes.
|
20
|
+
class Executor
|
21
|
+
# Task commands we support and that can be triggered on tasks (if matched)
|
22
|
+
COMMANDS = %w[
|
23
|
+
disable
|
24
|
+
enable
|
25
|
+
trigger
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@replaying = true
|
30
|
+
@incompatible = false
|
31
|
+
@catchup_commands = []
|
32
|
+
@catchup_schedule = nil
|
33
|
+
@matcher = Matcher.new
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Boolean] are we in the replaying phase or not (false means, regular operations)
|
37
|
+
def replaying?
|
38
|
+
@replaying
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Boolean] Is the current process schedule incompatible (older) than the one
|
42
|
+
# that we have in memory
|
43
|
+
def incompatible?
|
44
|
+
@incompatible
|
45
|
+
end
|
46
|
+
|
47
|
+
# Applies given command to task (or many tasks) by running the command on tasks that match
|
48
|
+
# @param command_hash [Hash] deserialized command data
|
49
|
+
def apply_command(command_hash)
|
50
|
+
cmd_name = command_hash[:command][:name]
|
51
|
+
|
52
|
+
raise(Karafka::Errors::UnsupportedCaseError, cmd_name) unless COMMANDS.include?(cmd_name)
|
53
|
+
|
54
|
+
schedule.each do |task|
|
55
|
+
next unless @matcher.matches?(task, command_hash)
|
56
|
+
|
57
|
+
task.public_send(cmd_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Updates the catchup state
|
62
|
+
# @param schedule_hash [Hash] deserialized schedule hash hash
|
63
|
+
def update_state(schedule_hash)
|
64
|
+
@catchup_schedule = schedule_hash
|
65
|
+
end
|
66
|
+
|
67
|
+
# Once all previous data is accumulated runs the catchup process to establish current
|
68
|
+
# state of the recurring tasks schedule execution.
|
69
|
+
#
|
70
|
+
# It includes applying any requested commands as well as synchronizing execution details
|
71
|
+
# for existing schedule and making sure all is loaded correctly.
|
72
|
+
def replay
|
73
|
+
# Ensure replaying happens only once
|
74
|
+
return unless @replaying
|
75
|
+
|
76
|
+
@replaying = false
|
77
|
+
|
78
|
+
# When there is nothing to replay and synchronize, we can just save the state and
|
79
|
+
# proceed
|
80
|
+
if @catchup_commands.empty? && @catchup_schedule.nil?
|
81
|
+
snapshot
|
82
|
+
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
# If the schedule version we have in Kafka is higher than ours, we cannot proceed
|
87
|
+
# This prevents us from applying older changes to a new schedule
|
88
|
+
if @catchup_schedule[:schedule_version] > schedule.version
|
89
|
+
@incompatible = true
|
90
|
+
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
# Now we can synchronize the in-memory state based on the last state stored in Kafka
|
95
|
+
schedule.each do |task|
|
96
|
+
stored_task = @catchup_schedule[:tasks][task.id.to_sym]
|
97
|
+
|
98
|
+
next unless stored_task
|
99
|
+
|
100
|
+
stored_previous_time = stored_task[:previous_time]
|
101
|
+
task.previous_time = stored_previous_time.zero? ? 0 : Time.at(stored_previous_time)
|
102
|
+
|
103
|
+
stored_task[:enabled] ? task.enable : task.disable
|
104
|
+
end
|
105
|
+
|
106
|
+
@catchup_commands.each do |cmd|
|
107
|
+
apply_command(cmd)
|
108
|
+
end
|
109
|
+
|
110
|
+
# We make sure to save in Kafka once more once everything is up to date
|
111
|
+
snapshot
|
112
|
+
|
113
|
+
@catchup_schedule = nil
|
114
|
+
@catchup_commands = []
|
115
|
+
end
|
116
|
+
|
117
|
+
# Run all tasks that should run at a given time and if any tasks were changed in any way
|
118
|
+
# or executed, stores the most recent state back to Kafka
|
119
|
+
def call
|
120
|
+
changed = false
|
121
|
+
|
122
|
+
schedule.each do |task|
|
123
|
+
changed = true if task.changed?
|
124
|
+
|
125
|
+
unless task.call?
|
126
|
+
task.clear
|
127
|
+
|
128
|
+
next
|
129
|
+
end
|
130
|
+
|
131
|
+
changed = true
|
132
|
+
task.call
|
133
|
+
end
|
134
|
+
|
135
|
+
snapshot if changed
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# @return [Karafka::Pro::RecurringTasks::Schedule] current in-memory schedule
|
141
|
+
def schedule
|
142
|
+
::Karafka::Pro::RecurringTasks.schedule
|
143
|
+
end
|
144
|
+
|
145
|
+
# Dispatches the current schedule state to Kafka
|
146
|
+
def snapshot
|
147
|
+
Dispatcher.schedule
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Listener we use to track execution of recurring tasks and publish those events into the
|
18
|
+
# recurring tasks log table
|
19
|
+
class Listener
|
20
|
+
# @param event [Karafka::Core::Monitoring::Event] task execution event
|
21
|
+
def on_recurring_tasks_task_executed(event)
|
22
|
+
Dispatcher.log(event)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param event [Karafka::Core::Monitoring::Event] error that occurred
|
26
|
+
# @note We do nothing with other errors. We only want to log and dispatch information about
|
27
|
+
# the recurring tasks errors. The general Web UI error tracking may also work but those
|
28
|
+
# are independent. It is not to replace the Web UI tracking but to just log failed
|
29
|
+
# executions in the same way as successful but just with the failure as an outcome.
|
30
|
+
def on_error_occurred(event)
|
31
|
+
return unless event[:type] == 'recurring_tasks.task.execute.error'
|
32
|
+
|
33
|
+
Dispatcher.log(event)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Matcher used to check if given command can be applied to a given task.
|
18
|
+
class Matcher
|
19
|
+
# @param task [Karafka::Pro::RecurringTasks::Task]
|
20
|
+
# @param payload [Hash] command message payload
|
21
|
+
# @return [Boolean] is this message dedicated to current process and is actionable
|
22
|
+
def matches?(task, payload)
|
23
|
+
# We only match commands
|
24
|
+
return false unless payload[:type] == 'command'
|
25
|
+
|
26
|
+
# * is a wildcard to match all for batch commands
|
27
|
+
return false unless payload[:task][:id] == '*' || payload[:task][:id] == task.id
|
28
|
+
|
29
|
+
# Ignore messages that have different schema. This can happen in the middle of
|
30
|
+
# upgrades of the framework. We ignore this not to risk compatibility issues
|
31
|
+
return false unless payload[:schema_version] == Serializer::SCHEMA_VERSION
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Represents the current code-context schedule with defined tasks and their cron execution
|
18
|
+
# details. Single schedule includes all the information about all the tasks that we have
|
19
|
+
# defined and to be executed in a given moment in time.
|
20
|
+
class Schedule
|
21
|
+
# @return [String]
|
22
|
+
attr_reader :version
|
23
|
+
|
24
|
+
# @return [Hash<String, Task>]
|
25
|
+
attr_reader :tasks
|
26
|
+
|
27
|
+
# @param version [String] schedule version. In case of usage of versioning it is used to
|
28
|
+
# ensure, that older still active processes do not intercept the assignment to run older
|
29
|
+
# version of the scheduler. It is important to make sure, that this string is comparable.
|
30
|
+
def initialize(version:)
|
31
|
+
@version = version
|
32
|
+
@tasks = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds task into the tasks accumulator
|
36
|
+
# @param task [Task]
|
37
|
+
# @note In case of multiple tasks with the same id, it will overwrite
|
38
|
+
def <<(task)
|
39
|
+
@tasks[task.id] = task
|
40
|
+
end
|
41
|
+
|
42
|
+
# Iterates over tasks yielding them one after another
|
43
|
+
# @param block [Proc] block that will be executed with each task
|
44
|
+
def each(&block)
|
45
|
+
@tasks.each_value(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param id [String] id of a particular recurring task
|
49
|
+
# @return [Task, nil] task with a given id or nil if not found
|
50
|
+
def find(id)
|
51
|
+
@tasks[id]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Allows us to have a nice DSL for defining schedules
|
55
|
+
# @param args [Array] attributes accepted by the task initializer
|
56
|
+
# @param block [Proc] block to execute
|
57
|
+
def schedule(**args, &block)
|
58
|
+
self << Task.new(**args, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Converts schedule command and log details into data we can dispatch to Kafka.
|
18
|
+
class Serializer
|
19
|
+
# Current recurring tasks related schema structure
|
20
|
+
SCHEMA_VERSION = '1.0'
|
21
|
+
|
22
|
+
# @param schedule [Karafka::Pro::RecurringTasks::Schedule] schedule to serialize
|
23
|
+
# @return [String] serialized and compressed current schedule data with its tasks and their
|
24
|
+
# current state.
|
25
|
+
def schedule(schedule)
|
26
|
+
tasks = {}
|
27
|
+
|
28
|
+
schedule.each do |task|
|
29
|
+
tasks[task.id] = {
|
30
|
+
id: task.id,
|
31
|
+
cron: task.cron.original,
|
32
|
+
previous_time: task.previous_time.to_i,
|
33
|
+
next_time: task.next_time.to_i,
|
34
|
+
enabled: task.enabled?
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
data = {
|
39
|
+
schema_version: SCHEMA_VERSION,
|
40
|
+
schedule_version: schedule.version,
|
41
|
+
dispatched_at: Time.now.to_f,
|
42
|
+
type: 'schedule',
|
43
|
+
tasks: tasks
|
44
|
+
}
|
45
|
+
|
46
|
+
compress(
|
47
|
+
serialize(data)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param command_name [String] command name
|
52
|
+
# @param task_id [String] task id or '*' to match all.
|
53
|
+
# @return [String] serialized and compressed command data
|
54
|
+
def command(command_name, task_id)
|
55
|
+
data = {
|
56
|
+
schema_version: SCHEMA_VERSION,
|
57
|
+
schedule_version: ::Karafka::Pro::RecurringTasks.schedule.version,
|
58
|
+
dispatched_at: Time.now.to_f,
|
59
|
+
type: 'command',
|
60
|
+
command: {
|
61
|
+
name: command_name
|
62
|
+
},
|
63
|
+
task: {
|
64
|
+
id: task_id
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
compress(
|
69
|
+
serialize(data)
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param event [Karafka::Core::Monitoring::Event] recurring task dispatch event
|
74
|
+
# @return [String] serialized and compressed event log data
|
75
|
+
def log(event)
|
76
|
+
task = event[:task]
|
77
|
+
|
78
|
+
data = {
|
79
|
+
schema_version: SCHEMA_VERSION,
|
80
|
+
schedule_version: ::Karafka::Pro::RecurringTasks.schedule.version,
|
81
|
+
dispatched_at: Time.now.to_f,
|
82
|
+
type: 'log',
|
83
|
+
task: {
|
84
|
+
id: task.id,
|
85
|
+
time_taken: event.payload[:time] || -1,
|
86
|
+
result: event.payload.key?(:error) ? 'failure' : 'success'
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
compress(
|
91
|
+
serialize(data)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# @param hash [Hash] hash to cast to json
|
98
|
+
# @return [String] json hash
|
99
|
+
def serialize(hash)
|
100
|
+
hash.to_json
|
101
|
+
end
|
102
|
+
|
103
|
+
# Compresses the provided data
|
104
|
+
#
|
105
|
+
# @param data [String] data to compress
|
106
|
+
# @return [String] compressed data
|
107
|
+
def compress(data)
|
108
|
+
Zlib::Deflate.deflate(data)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module RecurringTasks
|
17
|
+
# Setup and config related recurring tasks components
|
18
|
+
module Setup
|
19
|
+
# Config for recurring tasks
|
20
|
+
class Config
|
21
|
+
extend ::Karafka::Core::Configurable
|
22
|
+
|
23
|
+
setting(:consumer_class, default: Consumer)
|
24
|
+
setting(:deserializer, default: Deserializer.new)
|
25
|
+
setting(:group_id, default: 'karafka_recurring_tasks')
|
26
|
+
# By default we will run the scheduling every 15 seconds since we provide a minute-based
|
27
|
+
# precision
|
28
|
+
setting(:interval, default: 15_000)
|
29
|
+
# Should we log the executions. If true (default) with each cron execution, there will
|
30
|
+
# be a special message published. Useful for debugging.
|
31
|
+
setting(:logging, default: true)
|
32
|
+
|
33
|
+
# Producer to be used by the recurring tasks.
|
34
|
+
# By default it is a `Karafka.producer`, however it may be overwritten if we want to use
|
35
|
+
# a separate instance in case of heavy usage of the transactional producer, etc.
|
36
|
+
setting(
|
37
|
+
:producer,
|
38
|
+
constructor: -> { ::Karafka.producer },
|
39
|
+
lazy: true
|
40
|
+
)
|
41
|
+
|
42
|
+
setting(:topics) do
|
43
|
+
setting(:schedules, default: 'karafka_recurring_tasks_schedules')
|
44
|
+
setting(:logs, default: 'karafka_recurring_tasks_logs')
|
45
|
+
end
|
46
|
+
|
47
|
+
configure
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|