cloudtasker 0.7.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +5 -0
  4. data/.travis.yml +3 -3
  5. data/CHANGELOG.md +41 -0
  6. data/README.md +145 -25
  7. data/_config.yml +1 -0
  8. data/app/controllers/cloudtasker/worker_controller.rb +21 -5
  9. data/cloudtasker.gemspec +2 -2
  10. data/docs/BATCH_JOBS.md +28 -3
  11. data/docs/CRON_JOBS.md +3 -1
  12. data/exe/cloudtasker +13 -1
  13. data/gemfiles/google_cloud_tasks_1.0.gemfile.lock +26 -9
  14. data/gemfiles/google_cloud_tasks_1.1.gemfile.lock +26 -9
  15. data/gemfiles/google_cloud_tasks_1.2.gemfile.lock +27 -10
  16. data/gemfiles/google_cloud_tasks_1.3.gemfile.lock +26 -9
  17. data/gemfiles/rails_5.2.gemfile.lock +28 -11
  18. data/gemfiles/rails_6.0.gemfile.lock +29 -12
  19. data/lib/cloudtasker.rb +1 -1
  20. data/lib/cloudtasker/backend/google_cloud_task.rb +65 -12
  21. data/lib/cloudtasker/backend/memory_task.rb +5 -3
  22. data/lib/cloudtasker/backend/redis_task.rb +24 -13
  23. data/lib/cloudtasker/batch/batch_progress.rb +11 -2
  24. data/lib/cloudtasker/batch/job.rb +18 -4
  25. data/lib/cloudtasker/cli.rb +6 -5
  26. data/lib/cloudtasker/cloud_task.rb +6 -2
  27. data/lib/cloudtasker/config.rb +33 -9
  28. data/lib/cloudtasker/cron/job.rb +2 -2
  29. data/lib/cloudtasker/cron/schedule.rb +26 -14
  30. data/lib/cloudtasker/local_server.rb +44 -22
  31. data/lib/cloudtasker/max_task_size_exceeded_error.rb +14 -0
  32. data/lib/cloudtasker/redis_client.rb +10 -7
  33. data/lib/cloudtasker/unique_job/job.rb +2 -2
  34. data/lib/cloudtasker/version.rb +1 -1
  35. data/lib/cloudtasker/worker.rb +45 -10
  36. data/lib/cloudtasker/worker_handler.rb +7 -5
  37. data/lib/cloudtasker/worker_logger.rb +1 -1
  38. data/lib/cloudtasker/worker_wrapper.rb +52 -0
  39. data/lib/tasks/setup_queue.rake +12 -2
  40. metadata +7 -6
  41. data/Gemfile.lock +0 -280
  42. data/lib/cloudtasker/railtie.rb +0 -10
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- cloudtasker (0.2.0)
4
+ cloudtasker (0.9.1)
5
5
  activesupport
6
6
  fugit
7
7
  google-cloud-tasks
@@ -69,14 +69,24 @@ GEM
69
69
  erubi (1.9.0)
70
70
  et-orbi (1.2.2)
71
71
  tzinfo
72
- faraday (0.17.0)
72
+ faraday (0.17.1)
73
73
  multipart-post (>= 1.2, < 3)
74
+ faraday-http-cache (2.0.0)
75
+ faraday (~> 0.8)
74
76
  fugit (1.3.3)
75
77
  et-orbi (~> 1.1, >= 1.1.8)
76
78
  raabro (~> 1.1)
79
+ github_changelog_generator (1.15.0)
80
+ activesupport
81
+ faraday-http-cache
82
+ multi_json
83
+ octokit (~> 4.6)
84
+ rainbow (>= 2.2.1)
85
+ rake (>= 10.0)
86
+ retriable (~> 3.0)
77
87
  globalid (0.4.2)
78
88
  activesupport (>= 4.2.0)
79
- google-cloud-tasks (1.3.1)
89
+ google-cloud-tasks (1.3.3)
80
90
  google-gax (~> 1.8)
81
91
  googleapis-common-protos (>= 1.3.9, < 2.0)
82
92
  googleapis-common-protos-types (>= 1.0.4, < 2.0)
@@ -87,7 +97,7 @@ GEM
87
97
  googleauth (~> 0.9)
88
98
  grpc (~> 1.24)
89
99
  rly (~> 0.2.3)
90
- google-protobuf (3.10.1)
100
+ google-protobuf (3.11.3-universal-darwin)
91
101
  googleapis-common-protos (1.3.9)
92
102
  google-protobuf (~> 3.0)
93
103
  googleapis-common-protos-types (~> 1.0)
@@ -101,8 +111,8 @@ GEM
101
111
  multi_json (~> 1.11)
102
112
  os (>= 0.9, < 2.0)
103
113
  signet (~> 0.12)
104
- grpc (1.25.0)
105
- google-protobuf (~> 3.8)
114
+ grpc (1.27.0-universal-darwin)
115
+ google-protobuf (~> 3.11)
106
116
  googleapis-common-protos-types (~> 1.0)
107
117
  grpc-google-iam-v1 (0.6.9)
108
118
  googleapis-common-protos (>= 1.3.1, < 2.0)
@@ -112,14 +122,14 @@ GEM
112
122
  concurrent-ruby (~> 1.0)
113
123
  jaro_winkler (1.5.4)
114
124
  jwt (2.2.1)
115
- loofah (2.3.1)
125
+ loofah (2.4.0)
116
126
  crass (~> 1.0.2)
117
127
  nokogiri (>= 1.5.9)
118
128
  mail (2.7.1)
119
129
  mini_mime (>= 0.1.1)
120
130
  marcel (0.3.3)
121
131
  mimemagic (~> 0.3.2)
122
- memoist (0.16.1)
132
+ memoist (0.16.2)
123
133
  method_source (0.9.2)
124
134
  mimemagic (0.3.3)
125
135
  mini_mime (1.0.2)
@@ -128,10 +138,12 @@ GEM
128
138
  multi_json (1.14.1)
129
139
  multipart-post (2.1.1)
130
140
  nio4r (2.5.2)
131
- nokogiri (1.10.5)
141
+ nokogiri (1.10.7)
132
142
  mini_portile2 (~> 2.4.0)
143
+ octokit (4.14.0)
144
+ sawyer (~> 0.8.0, >= 0.5.3)
133
145
  os (1.0.1)
134
- parallel (1.19.0)
146
+ parallel (1.19.1)
135
147
  parser (2.6.5.0)
136
148
  ast (~> 2.4.0)
137
149
  public_suffix (4.0.1)
@@ -166,6 +178,7 @@ GEM
166
178
  rainbow (3.0.0)
167
179
  rake (10.5.0)
168
180
  redis (4.1.3)
181
+ retriable (3.1.2)
169
182
  rly (0.2.3)
170
183
  rspec (3.9.0)
171
184
  rspec-core (~> 3.9.0)
@@ -195,10 +208,13 @@ GEM
195
208
  rainbow (>= 2.2.2, < 4.0)
196
209
  ruby-progressbar (~> 1.7)
197
210
  unicode-display_width (>= 1.4.0, < 1.7)
198
- rubocop-rspec (1.36.0)
211
+ rubocop-rspec (1.37.0)
199
212
  rubocop (>= 0.68.1)
200
213
  ruby-progressbar (1.10.1)
201
214
  safe_yaml (1.0.5)
215
+ sawyer (0.8.2)
216
+ addressable (>= 2.3.5)
217
+ faraday (> 0.8, < 2.0)
202
218
  signet (0.12.0)
203
219
  addressable (~> 2.3)
204
220
  faraday (~> 0.9)
@@ -233,6 +249,7 @@ DEPENDENCIES
233
249
  appraisal
234
250
  bundler (~> 2.0)
235
251
  cloudtasker!
252
+ github_changelog_generator
236
253
  rails (= 5.2)
237
254
  rake (~> 10.0)
238
255
  rspec (~> 3.0)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- cloudtasker (0.2.0)
4
+ cloudtasker (0.9.1)
5
5
  activesupport
6
6
  fugit
7
7
  google-cloud-tasks
@@ -82,14 +82,24 @@ GEM
82
82
  erubi (1.9.0)
83
83
  et-orbi (1.2.2)
84
84
  tzinfo
85
- faraday (0.17.0)
85
+ faraday (0.17.1)
86
86
  multipart-post (>= 1.2, < 3)
87
+ faraday-http-cache (2.0.0)
88
+ faraday (~> 0.8)
87
89
  fugit (1.3.3)
88
90
  et-orbi (~> 1.1, >= 1.1.8)
89
91
  raabro (~> 1.1)
92
+ github_changelog_generator (1.15.0)
93
+ activesupport
94
+ faraday-http-cache
95
+ multi_json
96
+ octokit (~> 4.6)
97
+ rainbow (>= 2.2.1)
98
+ rake (>= 10.0)
99
+ retriable (~> 3.0)
90
100
  globalid (0.4.2)
91
101
  activesupport (>= 4.2.0)
92
- google-cloud-tasks (1.3.1)
102
+ google-cloud-tasks (1.3.3)
93
103
  google-gax (~> 1.8)
94
104
  googleapis-common-protos (>= 1.3.9, < 2.0)
95
105
  googleapis-common-protos-types (>= 1.0.4, < 2.0)
@@ -100,7 +110,7 @@ GEM
100
110
  googleauth (~> 0.9)
101
111
  grpc (~> 1.24)
102
112
  rly (~> 0.2.3)
103
- google-protobuf (3.10.1-universal-darwin)
113
+ google-protobuf (3.11.3-universal-darwin)
104
114
  googleapis-common-protos (1.3.9)
105
115
  google-protobuf (~> 3.0)
106
116
  googleapis-common-protos-types (~> 1.0)
@@ -114,8 +124,8 @@ GEM
114
124
  multi_json (~> 1.11)
115
125
  os (>= 0.9, < 2.0)
116
126
  signet (~> 0.12)
117
- grpc (1.25.0-universal-darwin)
118
- google-protobuf (~> 3.8)
127
+ grpc (1.27.0-universal-darwin)
128
+ google-protobuf (~> 3.11)
119
129
  googleapis-common-protos-types (~> 1.0)
120
130
  grpc-google-iam-v1 (0.6.9)
121
131
  googleapis-common-protos (>= 1.3.1, < 2.0)
@@ -125,14 +135,14 @@ GEM
125
135
  concurrent-ruby (~> 1.0)
126
136
  jaro_winkler (1.5.4)
127
137
  jwt (2.2.1)
128
- loofah (2.3.1)
138
+ loofah (2.4.0)
129
139
  crass (~> 1.0.2)
130
140
  nokogiri (>= 1.5.9)
131
141
  mail (2.7.1)
132
142
  mini_mime (>= 0.1.1)
133
143
  marcel (0.3.3)
134
144
  mimemagic (~> 0.3.2)
135
- memoist (0.16.1)
145
+ memoist (0.16.2)
136
146
  method_source (0.9.2)
137
147
  mimemagic (0.3.3)
138
148
  mini_mime (1.0.2)
@@ -141,10 +151,12 @@ GEM
141
151
  multi_json (1.14.1)
142
152
  multipart-post (2.1.1)
143
153
  nio4r (2.5.2)
144
- nokogiri (1.10.5)
154
+ nokogiri (1.10.7)
145
155
  mini_portile2 (~> 2.4.0)
156
+ octokit (4.14.0)
157
+ sawyer (~> 0.8.0, >= 0.5.3)
146
158
  os (1.0.1)
147
- parallel (1.19.0)
159
+ parallel (1.19.1)
148
160
  parser (2.6.5.0)
149
161
  ast (~> 2.4.0)
150
162
  public_suffix (4.0.1)
@@ -181,6 +193,7 @@ GEM
181
193
  rainbow (3.0.0)
182
194
  rake (10.5.0)
183
195
  redis (4.1.3)
196
+ retriable (3.1.2)
184
197
  rly (0.2.3)
185
198
  rspec (3.9.0)
186
199
  rspec-core (~> 3.9.0)
@@ -210,10 +223,13 @@ GEM
210
223
  rainbow (>= 2.2.2, < 4.0)
211
224
  ruby-progressbar (~> 1.7)
212
225
  unicode-display_width (>= 1.4.0, < 1.7)
213
- rubocop-rspec (1.36.0)
226
+ rubocop-rspec (1.37.0)
214
227
  rubocop (>= 0.68.1)
215
228
  ruby-progressbar (1.10.1)
216
229
  safe_yaml (1.0.5)
230
+ sawyer (0.8.2)
231
+ addressable (>= 2.3.5)
232
+ faraday (> 0.8, < 2.0)
217
233
  signet (0.12.0)
218
234
  addressable (~> 2.3)
219
235
  faraday (~> 0.9)
@@ -240,7 +256,7 @@ GEM
240
256
  websocket-driver (0.7.1)
241
257
  websocket-extensions (>= 0.1.0)
242
258
  websocket-extensions (0.1.4)
243
- zeitwerk (2.2.1)
259
+ zeitwerk (2.2.2)
244
260
 
245
261
  PLATFORMS
246
262
  ruby
@@ -249,6 +265,7 @@ DEPENDENCIES
249
265
  appraisal
250
266
  bundler (~> 2.0)
251
267
  cloudtasker!
268
+ github_changelog_generator
252
269
  rails (= 6.0)
253
270
  rake (~> 10.0)
254
271
  rspec (~> 3.0)
@@ -8,6 +8,7 @@ require 'cloudtasker/config'
8
8
  require 'cloudtasker/authentication_error'
9
9
  require 'cloudtasker/dead_worker_error'
10
10
  require 'cloudtasker/invalid_worker_error'
11
+ require 'cloudtasker/max_task_size_exceeded_error'
11
12
 
12
13
  require 'cloudtasker/middleware/chain'
13
14
  require 'cloudtasker/authenticator'
@@ -47,5 +48,4 @@ module Cloudtasker
47
48
  end
48
49
  end
49
50
 
50
- require 'cloudtasker/railtie' if defined?(Rails)
51
51
  require 'cloudtasker/engine' if defined?(::Rails::Engine)
@@ -9,15 +9,28 @@ module Cloudtasker
9
9
  #
10
10
  # Create the queue configured in Cloudtasker if it does not already exist.
11
11
  #
12
+ # @param [String] queue_name The relative name of the queue.
13
+ #
12
14
  # @return [Google::Cloud::Tasks::V2beta3::Queue] The queue
13
15
  #
14
- def self.setup_queue
15
- client.get_queue(queue_path)
16
+ def self.setup_queue(**opts)
17
+ # Build full queue path
18
+ queue_name = opts[:name] || Cloudtasker::Config::DEFAULT_JOB_QUEUE
19
+ full_queue_name = queue_path(queue_name)
20
+
21
+ # Try to get existing queue
22
+ client.get_queue(full_queue_name)
16
23
  rescue Google::Gax::RetryError
24
+ # Extract options
25
+ concurrency = (opts[:concurrency] || Cloudtasker::Config::DEFAULT_QUEUE_CONCURRENCY).to_i
26
+ retries = (opts[:retries] || Cloudtasker::Config::DEFAULT_QUEUE_RETRIES).to_i
27
+
28
+ # Create queue on 'not found' error
17
29
  client.create_queue(
18
30
  client.location_path(config.gcp_project_id, config.gcp_location_id),
19
- name: queue_path,
20
- retry_config: { max_attempts: -1 }
31
+ name: full_queue_name,
32
+ retry_config: { max_attempts: retries },
33
+ rate_limits: { max_concurrent_dispatches: concurrency }
21
34
  )
22
35
  end
23
36
 
@@ -42,13 +55,15 @@ module Cloudtasker
42
55
  #
43
56
  # Return the fully qualified path for the Cloud Task queue.
44
57
  #
58
+ # @param [String] queue_name The relative name of the queue.
59
+ #
45
60
  # @return [String] The queue path.
46
61
  #
47
- def self.queue_path
62
+ def self.queue_path(queue_name)
48
63
  client.queue_path(
49
64
  config.gcp_project_id,
50
65
  config.gcp_location_id,
51
- config.gcp_queue_id
66
+ [config.gcp_queue_prefix, queue_name].join('-')
52
67
  )
53
68
  end
54
69
 
@@ -67,6 +82,29 @@ module Cloudtasker
67
82
  Google::Protobuf::Timestamp.new.tap { |e| e.seconds = schedule_time.to_i }
68
83
  end
69
84
 
85
+ #
86
+ # Format the job payload sent to Cloud Tasks.
87
+ #
88
+ # @param [Hash] hash The worker payload.
89
+ #
90
+ # @return [Hash] The Cloud Task payloadd.
91
+ #
92
+ def self.format_task_payload(payload)
93
+ payload = JSON.parse(payload.to_json, symbolize_names: true) # deep dup
94
+
95
+ # Format schedule time to Google Protobuf timestamp
96
+ payload[:schedule_time] = format_schedule_time(payload[:schedule_time])
97
+
98
+ # Encode job content to support UTF-8. Google Cloud Task
99
+ # expect content to be ASCII-8BIT compatible (binary)
100
+ payload[:http_request][:headers] ||= {}
101
+ payload[:http_request][:headers][Cloudtasker::Config::CONTENT_TYPE_HEADER] = 'text/json'
102
+ payload[:http_request][:headers][Cloudtasker::Config::ENCODING_HEADER] = 'Base64'
103
+ payload[:http_request][:body] = Base64.encode64(payload[:http_request][:body])
104
+
105
+ payload
106
+ end
107
+
70
108
  #
71
109
  # Find a task by id.
72
110
  #
@@ -89,13 +127,13 @@ module Cloudtasker
89
127
  # @return [Cloudtasker::Backend::GoogleCloudTask, nil] The created task.
90
128
  #
91
129
  def self.create(payload)
92
- # Format payload
93
- payload = payload.merge(
94
- schedule_time: format_schedule_time(payload[:schedule_time])
95
- ).compact
130
+ payload = format_task_payload(payload)
131
+
132
+ # Extract relative queue name
133
+ relative_queue = payload.delete(:queue)
96
134
 
97
135
  # Create task
98
- resp = client.create_task(queue_path, payload)
136
+ resp = client.create_task(queue_path(relative_queue), payload)
99
137
  resp ? new(resp) : nil
100
138
  rescue Google::Gax::RetryError
101
139
  nil
@@ -121,6 +159,20 @@ module Cloudtasker
121
159
  @gcp_task = gcp_task
122
160
  end
123
161
 
162
+ #
163
+ # Return the relative queue (queue name minus prefix) the task is in.
164
+ #
165
+ # @return [String] The relative queue name
166
+ #
167
+ def relative_queue
168
+ gcp_task
169
+ .name
170
+ .match(%r{/queues/([^/]+)})
171
+ &.captures
172
+ &.first
173
+ &.sub("#{self.class.config.gcp_queue_prefix}-", '')
174
+ end
175
+
124
176
  #
125
177
  # Return a hash description of the task.
126
178
  #
@@ -131,7 +183,8 @@ module Cloudtasker
131
183
  id: gcp_task.name,
132
184
  http_request: gcp_task.to_h[:http_request],
133
185
  schedule_time: gcp_task.to_h.dig(:schedule_time, :seconds).to_i,
134
- retries: gcp_task.to_h[:response_count]
186
+ retries: gcp_task.to_h[:response_count],
187
+ queue: relative_queue
135
188
  }
136
189
  end
137
190
  end
@@ -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 [Class] The redis client.
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(gid,
159
- retries: is_error ? retries + 1 : retries,
160
- http_request: http_request,
161
- schedule_time: (Time.now + interval).to_i)
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
  #