cloudtasker 0.6.0 → 0.9.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 +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +38 -0
- data/README.md +142 -26
- data/_config.yml +1 -0
- data/app/controllers/cloudtasker/worker_controller.rb +21 -5
- data/cloudtasker.gemspec +2 -2
- data/docs/BATCH_JOBS.md +29 -4
- data/docs/CRON_JOBS.md +18 -14
- data/exe/cloudtasker +13 -1
- data/gemfiles/google_cloud_tasks_1.0.gemfile.lock +26 -9
- data/gemfiles/google_cloud_tasks_1.1.gemfile.lock +26 -9
- data/gemfiles/google_cloud_tasks_1.2.gemfile.lock +27 -10
- data/gemfiles/google_cloud_tasks_1.3.gemfile.lock +26 -9
- data/gemfiles/rails_5.2.gemfile.lock +26 -9
- data/gemfiles/rails_6.0.gemfile.lock +27 -10
- data/lib/cloudtasker.rb +0 -1
- data/lib/cloudtasker/backend/google_cloud_task.rb +65 -12
- data/lib/cloudtasker/backend/memory_task.rb +5 -3
- data/lib/cloudtasker/backend/redis_task.rb +24 -13
- data/lib/cloudtasker/batch/batch_progress.rb +11 -2
- data/lib/cloudtasker/batch/job.rb +18 -4
- data/lib/cloudtasker/cli.rb +6 -5
- data/lib/cloudtasker/cloud_task.rb +4 -2
- data/lib/cloudtasker/config.rb +30 -9
- data/lib/cloudtasker/cron/job.rb +2 -2
- data/lib/cloudtasker/cron/schedule.rb +26 -14
- data/lib/cloudtasker/local_server.rb +44 -22
- data/lib/cloudtasker/redis_client.rb +10 -7
- data/lib/cloudtasker/unique_job/job.rb +2 -2
- data/lib/cloudtasker/version.rb +1 -1
- data/lib/cloudtasker/worker.rb +46 -10
- data/lib/cloudtasker/worker_handler.rb +7 -5
- data/lib/cloudtasker/worker_logger.rb +1 -1
- data/lib/cloudtasker/worker_wrapper.rb +52 -0
- data/lib/tasks/setup_queue.rake +12 -2
- metadata +6 -6
- data/Gemfile.lock +0 -280
- data/lib/cloudtasker/railtie.rb +0 -10
@@ -7,7 +7,7 @@ module Cloudtasker
|
|
7
7
|
# Manage local tasks pushed to memory.
|
8
8
|
# Used for testing.
|
9
9
|
class MemoryTask
|
10
|
-
attr_reader :id, :http_request, :schedule_time
|
10
|
+
attr_reader :id, :http_request, :schedule_time, :queue
|
11
11
|
|
12
12
|
#
|
13
13
|
# Return the task queue. A worker class name
|
@@ -116,10 +116,11 @@ module Cloudtasker
|
|
116
116
|
# @param [Hash] http_request The HTTP request content.
|
117
117
|
# @param [Integer] schedule_time When to run the task (Unix timestamp)
|
118
118
|
#
|
119
|
-
def initialize(id:, http_request:, schedule_time: nil)
|
119
|
+
def initialize(id:, http_request:, schedule_time: nil, queue: nil)
|
120
120
|
@id = id
|
121
121
|
@http_request = http_request
|
122
122
|
@schedule_time = Time.at(schedule_time || 0)
|
123
|
+
@queue = queue
|
123
124
|
end
|
124
125
|
|
125
126
|
#
|
@@ -149,7 +150,8 @@ module Cloudtasker
|
|
149
150
|
{
|
150
151
|
id: id,
|
151
152
|
http_request: http_request,
|
152
|
-
schedule_time: schedule_time.to_i
|
153
|
+
schedule_time: schedule_time.to_i,
|
154
|
+
queue: queue
|
153
155
|
}
|
154
156
|
end
|
155
157
|
|
@@ -7,17 +7,17 @@ module Cloudtasker
|
|
7
7
|
module Backend
|
8
8
|
# Manage local tasks pushed to Redis
|
9
9
|
class RedisTask
|
10
|
-
attr_reader :id, :http_request, :schedule_time, :retries
|
10
|
+
attr_reader :id, :http_request, :schedule_time, :retries, :queue
|
11
11
|
|
12
12
|
RETRY_INTERVAL = 20 # seconds
|
13
13
|
|
14
14
|
#
|
15
15
|
# Return the cloudtasker redis client
|
16
16
|
#
|
17
|
-
# @return [
|
17
|
+
# @return [Cloudtasker::RedisClient] The cloudtasker redis client..
|
18
18
|
#
|
19
19
|
def self.redis
|
20
|
-
RedisClient
|
20
|
+
@redis ||= RedisClient.new
|
21
21
|
end
|
22
22
|
|
23
23
|
#
|
@@ -48,20 +48,26 @@ module Cloudtasker
|
|
48
48
|
#
|
49
49
|
# Reeturn all tasks ready to process.
|
50
50
|
#
|
51
|
+
# @param [String] queue The queue to retrieve items from.
|
52
|
+
#
|
51
53
|
# @return [Array<Cloudtasker::Backend::RedisTask>] All the tasks ready to process.
|
52
54
|
#
|
53
|
-
def self.ready_to_process
|
54
|
-
all.select { |e| e.schedule_time <= Time.now }
|
55
|
+
def self.ready_to_process(queue = nil)
|
56
|
+
list = all.select { |e| e.schedule_time <= Time.now }
|
57
|
+
list = list.select { |e| e.queue == queue } if queue
|
58
|
+
list
|
55
59
|
end
|
56
60
|
|
57
61
|
#
|
58
62
|
# Retrieve and remove a task from the queue.
|
59
63
|
#
|
64
|
+
# @param [String] queue The queue to retrieve items from.
|
65
|
+
#
|
60
66
|
# @return [Cloudtasker::Backend::RedisTask] A task ready to process.
|
61
67
|
#
|
62
|
-
def self.pop
|
68
|
+
def self.pop(queue = nil)
|
63
69
|
redis.with_lock('cloudtasker/server') do
|
64
|
-
ready_to_process.first&.tap(&:destroy)
|
70
|
+
ready_to_process(queue).first&.tap(&:destroy)
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
@@ -110,11 +116,12 @@ module Cloudtasker
|
|
110
116
|
# @param [Integer] schedule_time When to run the task (Unix timestamp)
|
111
117
|
# @param [Integer] retries The number of times the job failed.
|
112
118
|
#
|
113
|
-
def initialize(id:, http_request:, schedule_time: nil, retries: 0)
|
119
|
+
def initialize(id:, http_request:, schedule_time: nil, retries: 0, queue: nil)
|
114
120
|
@id = id
|
115
121
|
@http_request = http_request
|
116
122
|
@schedule_time = Time.at(schedule_time || 0)
|
117
123
|
@retries = retries || 0
|
124
|
+
@queue = queue || Cloudtasker::Config::DEFAULT_JOB_QUEUE
|
118
125
|
end
|
119
126
|
|
120
127
|
#
|
@@ -136,7 +143,8 @@ module Cloudtasker
|
|
136
143
|
id: id,
|
137
144
|
http_request: http_request,
|
138
145
|
schedule_time: schedule_time.to_i,
|
139
|
-
retries: retries
|
146
|
+
retries: retries,
|
147
|
+
queue: queue
|
140
148
|
}
|
141
149
|
end
|
142
150
|
|
@@ -155,10 +163,13 @@ module Cloudtasker
|
|
155
163
|
# @param [Integer] interval The delay in seconds before retrying the task
|
156
164
|
#
|
157
165
|
def retry_later(interval, is_error: true)
|
158
|
-
redis.write(
|
159
|
-
|
160
|
-
|
161
|
-
|
166
|
+
redis.write(
|
167
|
+
gid,
|
168
|
+
retries: is_error ? retries + 1 : retries,
|
169
|
+
http_request: http_request,
|
170
|
+
schedule_time: (Time.now + interval).to_i,
|
171
|
+
queue: queue
|
172
|
+
)
|
162
173
|
end
|
163
174
|
|
164
175
|
#
|
@@ -77,7 +77,16 @@ module Cloudtasker
|
|
77
77
|
# @return [Integer] The number of jobs pending.
|
78
78
|
#
|
79
79
|
def pending
|
80
|
-
total -
|
80
|
+
total - done
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Return the number of jobs completed or dead.
|
85
|
+
#
|
86
|
+
# @return [Integer] The number of jobs done.
|
87
|
+
#
|
88
|
+
def done
|
89
|
+
completed + dead
|
81
90
|
end
|
82
91
|
|
83
92
|
#
|
@@ -88,7 +97,7 @@ module Cloudtasker
|
|
88
97
|
def percent
|
89
98
|
return 0 if total.zero?
|
90
99
|
|
91
|
-
|
100
|
+
(done.to_f / total) * 100
|
92
101
|
end
|
93
102
|
|
94
103
|
#
|
@@ -16,10 +16,10 @@ module Cloudtasker
|
|
16
16
|
#
|
17
17
|
# Return the cloudtasker redis client
|
18
18
|
#
|
19
|
-
# @return [
|
19
|
+
# @return [Cloudtasker::RedisClient] The cloudtasker redis client..
|
20
20
|
#
|
21
21
|
def self.redis
|
22
|
-
RedisClient
|
22
|
+
@redis ||= RedisClient.new
|
23
23
|
end
|
24
24
|
|
25
25
|
#
|
@@ -87,7 +87,7 @@ module Cloudtasker
|
|
87
87
|
#
|
88
88
|
# Return the cloudtasker redis client
|
89
89
|
#
|
90
|
-
# @return [
|
90
|
+
# @return [Cloudtasker::RedisClient] The cloudtasker redis client..
|
91
91
|
#
|
92
92
|
def redis
|
93
93
|
self.class.redis
|
@@ -180,9 +180,23 @@ module Cloudtasker
|
|
180
180
|
# @return [Array<Cloudtasker::Worker>] The updated list of jobs.
|
181
181
|
#
|
182
182
|
def add(worker_klass, *args)
|
183
|
+
add_to_queue(worker.job_queue, worker_klass, *args)
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Add a worker to the batch using a specific queue.
|
188
|
+
#
|
189
|
+
# @param [String, Symbol] queue The name of the queue
|
190
|
+
# @param [Class] worker_klass The worker class.
|
191
|
+
# @param [Array<any>] *args The worker arguments.
|
192
|
+
#
|
193
|
+
# @return [Array<Cloudtasker::Worker>] The updated list of jobs.
|
194
|
+
#
|
195
|
+
def add_to_queue(queue, worker_klass, *args)
|
183
196
|
jobs << worker_klass.new(
|
184
197
|
job_args: args,
|
185
|
-
job_meta: { key(:parent_id) => batch_id }
|
198
|
+
job_meta: { key(:parent_id) => batch_id },
|
199
|
+
job_queue: queue
|
186
200
|
)
|
187
201
|
end
|
188
202
|
|
data/lib/cloudtasker/cli.rb
CHANGED
@@ -70,7 +70,7 @@ module Cloudtasker
|
|
70
70
|
#
|
71
71
|
# Run the cloudtasker development server.
|
72
72
|
#
|
73
|
-
def run
|
73
|
+
def run(opts = {})
|
74
74
|
boot_system
|
75
75
|
|
76
76
|
# Print banner
|
@@ -90,16 +90,17 @@ module Cloudtasker
|
|
90
90
|
logger.info "[Cloudtasker/Server] Running in #{RUBY_DESCRIPTION}"
|
91
91
|
|
92
92
|
# Wait for signals
|
93
|
-
|
93
|
+
run_server(self_read, opts)
|
94
94
|
end
|
95
95
|
|
96
96
|
#
|
97
|
-
#
|
97
|
+
# Run server and wait for signals.
|
98
98
|
#
|
99
99
|
# @param [IO] read_pipe Where to read signals.
|
100
|
+
# @param [Hash] opts Server options.
|
100
101
|
#
|
101
|
-
def
|
102
|
-
local_server.start
|
102
|
+
def run_server(read_pipe, opts = {})
|
103
|
+
local_server.start(opts)
|
103
104
|
|
104
105
|
while (readable_io = IO.select([read_pipe]))
|
105
106
|
signal = readable_io.first[0].gets.strip
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Cloudtasker
|
4
4
|
# An interface class to manage tasks on the backend (Cloud Task or Redis)
|
5
5
|
class CloudTask
|
6
|
-
attr_accessor :id, :http_request, :schedule_time, :retries
|
6
|
+
attr_accessor :id, :http_request, :schedule_time, :retries, :queue
|
7
7
|
|
8
8
|
#
|
9
9
|
# The backend to use for cloud tasks.
|
@@ -69,12 +69,14 @@ module Cloudtasker
|
|
69
69
|
# @param [Hash] http_request The content of the http request.
|
70
70
|
# @param [Integer] schedule_time When to run the job (Unix timestamp)
|
71
71
|
# @param [Integer] retries The number of times the job failed.
|
72
|
+
# @param [String] queue The queue the task is in.
|
72
73
|
#
|
73
|
-
def initialize(id:, http_request:, schedule_time: nil, retries: 0)
|
74
|
+
def initialize(id:, http_request:, schedule_time: nil, retries: 0, queue: nil)
|
74
75
|
@id = id
|
75
76
|
@http_request = http_request
|
76
77
|
@schedule_time = schedule_time
|
77
78
|
@retries = retries || 0
|
79
|
+
@queue = queue
|
78
80
|
end
|
79
81
|
|
80
82
|
#
|
data/lib/cloudtasker/config.rb
CHANGED
@@ -7,15 +7,29 @@ module Cloudtasker
|
|
7
7
|
class Config
|
8
8
|
attr_accessor :redis
|
9
9
|
attr_writer :secret, :gcp_location_id, :gcp_project_id,
|
10
|
-
:
|
10
|
+
:gcp_queue_prefix, :processor_path, :logger, :mode, :max_retries
|
11
11
|
|
12
12
|
# Retry header in Cloud Task responses
|
13
13
|
RETRY_HEADER = 'X-CloudTasks-TaskExecutionCount'
|
14
14
|
|
15
|
+
# Content-Transfer-Encoding header in Cloud Task responses
|
16
|
+
ENCODING_HEADER = 'Content-Transfer-Encoding'
|
17
|
+
|
18
|
+
# Content Type
|
19
|
+
CONTENT_TYPE_HEADER = 'Content-Type'
|
20
|
+
|
21
|
+
# Authorization header
|
22
|
+
AUTHORIZATION_HEADER = 'Authorization'
|
23
|
+
|
15
24
|
# Default values
|
16
25
|
DEFAULT_LOCATION_ID = 'us-east1'
|
17
26
|
DEFAULT_PROCESSOR_PATH = '/cloudtasker/run'
|
18
27
|
|
28
|
+
# Default queue values
|
29
|
+
DEFAULT_JOB_QUEUE = 'default'
|
30
|
+
DEFAULT_QUEUE_CONCURRENCY = 10
|
31
|
+
DEFAULT_QUEUE_RETRIES = -1 # unlimited
|
32
|
+
|
19
33
|
# The number of times jobs will be attempted before declaring them dead
|
20
34
|
DEFAULT_MAX_RETRY_ATTEMPTS = 25
|
21
35
|
|
@@ -23,9 +37,10 @@ module Cloudtasker
|
|
23
37
|
Missing host for processing.
|
24
38
|
Please specify a processor hostname in form of `https://some-public-dns.example.com`'
|
25
39
|
DOC
|
26
|
-
|
27
|
-
Missing GCP queue
|
28
|
-
Please specify a queue
|
40
|
+
QUEUE_PREFIX_MISSING_ERROR = <<~DOC
|
41
|
+
Missing GCP queue prefix.
|
42
|
+
Please specify a queue prefix in the form of `my-app`.
|
43
|
+
You can create a default queue using the Google SDK via `gcloud tasks queues create my-app-default`
|
29
44
|
DOC
|
30
45
|
PROJECT_ID_MISSING_ERROR = <<~DOC
|
31
46
|
Missing GCP project ID.
|
@@ -95,8 +110,14 @@ module Cloudtasker
|
|
95
110
|
def processor_host=(val)
|
96
111
|
@processor_host = val
|
97
112
|
|
113
|
+
# Check if Rails supports host filtering
|
114
|
+
return unless val &&
|
115
|
+
defined?(Rails) &&
|
116
|
+
Rails.application.config.respond_to?(:hosts) &&
|
117
|
+
Rails.application.config.hosts&.any?
|
118
|
+
|
98
119
|
# Add processor host to the list of authorized hosts
|
99
|
-
Rails.application.config.hosts << val.gsub(%r{https?://}, '')
|
120
|
+
Rails.application.config.hosts << val.gsub(%r{https?://}, '')
|
100
121
|
end
|
101
122
|
|
102
123
|
#
|
@@ -121,12 +142,12 @@ module Cloudtasker
|
|
121
142
|
end
|
122
143
|
|
123
144
|
#
|
124
|
-
# Return the
|
145
|
+
# Return the prefix used for queues.
|
125
146
|
#
|
126
|
-
# @return [String] The
|
147
|
+
# @return [String] The prefix of the processing queues.
|
127
148
|
#
|
128
|
-
def
|
129
|
-
@
|
149
|
+
def gcp_queue_prefix
|
150
|
+
@gcp_queue_prefix || raise(StandardError, QUEUE_PREFIX_MISSING_ERROR)
|
130
151
|
end
|
131
152
|
|
132
153
|
#
|
data/lib/cloudtasker/cron/job.rb
CHANGED
@@ -105,10 +105,10 @@ module Cloudtasker
|
|
105
105
|
#
|
106
106
|
# Return the cloudtasker redis client
|
107
107
|
#
|
108
|
-
# @return [
|
108
|
+
# @return [Cloudtasker::RedisClient] The cloudtasker redis client..
|
109
109
|
#
|
110
110
|
def redis
|
111
|
-
RedisClient
|
111
|
+
@redis ||= RedisClient.new
|
112
112
|
end
|
113
113
|
|
114
114
|
#
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'fugit'
|
4
|
+
require 'cloudtasker/worker_wrapper'
|
4
5
|
|
5
6
|
module Cloudtasker
|
6
7
|
module Cron
|
7
8
|
# Manage cron schedules
|
8
9
|
class Schedule
|
9
|
-
attr_accessor :id, :cron, :worker, :task_id, :job_id
|
10
|
+
attr_accessor :id, :cron, :worker, :task_id, :job_id, :queue, :args
|
10
11
|
|
11
12
|
# Key Namespace used for object saved under this class
|
12
13
|
SUB_NAMESPACE = 'schedule'
|
@@ -14,10 +15,10 @@ module Cloudtasker
|
|
14
15
|
#
|
15
16
|
# Return the redis client.
|
16
17
|
#
|
17
|
-
# @return [
|
18
|
+
# @return [Cloudtasker::RedisClient] The cloudtasker redis client.
|
18
19
|
#
|
19
20
|
def self.redis
|
20
|
-
RedisClient
|
21
|
+
@redis ||= RedisClient.new
|
21
22
|
end
|
22
23
|
|
23
24
|
#
|
@@ -113,21 +114,25 @@ module Cloudtasker
|
|
113
114
|
# @param [String] id The schedule id.
|
114
115
|
# @param [String] cron The cron expression.
|
115
116
|
# @param [Class] worker The worker class to run.
|
117
|
+
# @param [Array<any>] args The worker arguments.
|
118
|
+
# @param [String] queue The queue to use for the cron job.
|
116
119
|
# @param [String] task_id The ID of the actual backend task.
|
117
120
|
# @param [String] job_id The ID of the Cloudtasker worker.
|
118
121
|
#
|
119
|
-
def initialize(id:, cron:, worker:,
|
122
|
+
def initialize(id:, cron:, worker:, **opts)
|
120
123
|
@id = id
|
121
124
|
@cron = cron
|
122
125
|
@worker = worker
|
123
|
-
@
|
124
|
-
@
|
126
|
+
@args = opts[:args]
|
127
|
+
@queue = opts[:queue]
|
128
|
+
@task_id = opts[:task_id]
|
129
|
+
@job_id = opts[:job_id]
|
125
130
|
end
|
126
131
|
|
127
132
|
#
|
128
133
|
# Return the redis client.
|
129
134
|
#
|
130
|
-
# @return [
|
135
|
+
# @return [Cloudtasker::RedisClient] The cloudtasker redis client.
|
131
136
|
#
|
132
137
|
def redis
|
133
138
|
self.class.redis
|
@@ -191,7 +196,9 @@ module Cloudtasker
|
|
191
196
|
{
|
192
197
|
id: id,
|
193
198
|
cron: cron,
|
194
|
-
worker: worker
|
199
|
+
worker: worker,
|
200
|
+
args: args,
|
201
|
+
queue: queue
|
195
202
|
}
|
196
203
|
end
|
197
204
|
|
@@ -201,13 +208,10 @@ module Cloudtasker
|
|
201
208
|
# @return [Hash] The attributes hash.
|
202
209
|
#
|
203
210
|
def to_h
|
204
|
-
|
205
|
-
id: id,
|
206
|
-
cron: cron,
|
207
|
-
worker: worker,
|
211
|
+
to_config.merge(
|
208
212
|
task_id: task_id,
|
209
213
|
job_id: job_id
|
210
|
-
|
214
|
+
)
|
211
215
|
end
|
212
216
|
|
213
217
|
#
|
@@ -219,6 +223,15 @@ module Cloudtasker
|
|
219
223
|
@cron_schedule ||= Fugit::Cron.parse(cron)
|
220
224
|
end
|
221
225
|
|
226
|
+
#
|
227
|
+
# Return an instance of the underlying worker.
|
228
|
+
#
|
229
|
+
# @return [Cloudtasker::WorkerWrapper] The worker instance
|
230
|
+
#
|
231
|
+
def worker_instance
|
232
|
+
WorkerWrapper.new(worker_name: worker, job_args: args, job_queue: queue)
|
233
|
+
end
|
234
|
+
|
222
235
|
#
|
223
236
|
# Return the next time a job should run.
|
224
237
|
#
|
@@ -279,7 +292,6 @@ module Cloudtasker
|
|
279
292
|
CloudTask.delete(task_id) if task_id
|
280
293
|
|
281
294
|
# Schedule worker
|
282
|
-
worker_instance = Object.const_get(worker).new
|
283
295
|
Job.new(worker_instance).set(schedule_id: id).schedule!
|
284
296
|
end
|
285
297
|
end
|