apisonator 3.1.0 → 3.3.1.1
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/CHANGELOG.md +61 -0
- data/Gemfile.base +8 -3
- data/Gemfile.lock +12 -10
- data/Gemfile.on_prem.lock +12 -10
- data/Rakefile +33 -11
- data/app/api/internal/service_tokens.rb +8 -0
- data/app/api/internal/stats.rb +6 -25
- data/lib/3scale/backend/application_events.rb +2 -4
- data/lib/3scale/backend/configuration.rb +1 -6
- data/lib/3scale/backend/errors.rb +0 -6
- data/lib/3scale/backend/job_fetcher.rb +28 -22
- data/lib/3scale/backend/listener_metrics.rb +14 -12
- data/lib/3scale/backend/stats.rb +0 -4
- data/lib/3scale/backend/stats/aggregators/base.rb +8 -1
- data/lib/3scale/backend/stats/cleaner.rb +109 -28
- data/lib/3scale/backend/stats/keys.rb +6 -0
- data/lib/3scale/backend/stats/period_commons.rb +0 -3
- data/lib/3scale/backend/transactor.rb +31 -4
- data/lib/3scale/backend/version.rb +1 -1
- data/lib/3scale/backend/worker_async.rb +22 -1
- data/licenses.xml +6 -6
- metadata +2 -6
- data/lib/3scale/backend/stats/delete_job_def.rb +0 -60
- data/lib/3scale/backend/stats/key_generator.rb +0 -73
- data/lib/3scale/backend/stats/partition_eraser_job.rb +0 -58
- data/lib/3scale/backend/stats/partition_generator_job.rb +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '09ccae8a2719bc3717d22dcdd78c5c996cbd4774f2528b91d77c74fbbe2e51b7'
|
|
4
|
+
data.tar.gz: 4a9f237a6d1b25f9cc763817030cbe562536a6d5ed89374326db2f99ea82971c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2818f8b3a97b701218c28ae93339650db57a0229181ee3573a24ffe215611747c1381e9f0e643eb9dc85b81d13d43336a08252a88cd63cd7fc78ed9a182c5c83
|
|
7
|
+
data.tar.gz: 7a9c2e389c239094e2c6f303749056d5a8ea2d216f5097e98e9a87ef5c3bcd4ea2d190c4b8277d659608f7c80f57d5d727bf65eb9330f4c08fb8777ab5a1ae81
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to Apisonator will be tracked in this document.
|
|
4
4
|
|
|
5
|
+
## 3.3.1.1 - 2021-02-12
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Updated our Puma fork to v4.3.7
|
|
10
|
+
([#261](https://github.com/3scale/apisonator/pull/261)).
|
|
11
|
+
|
|
12
|
+
## 3.3.1 - 2021-02-11
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Usages with `#0` (set to 0) no longer generate unnecessary stats keys in Redis
|
|
17
|
+
([#258](https://github.com/3scale/apisonator/pull/258)).
|
|
18
|
+
|
|
19
|
+
## 3.3.0 - 2021-02-09
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Rake task to delete stats keys set to 0 in the DB left there because of [this
|
|
24
|
+
issue](https://github.com/3scale/apisonator/pull/247)
|
|
25
|
+
([#250](https://github.com/3scale/apisonator/pull/250)).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Made the worker more reliable when configured in async mode. Now it handles
|
|
30
|
+
connection errors better
|
|
31
|
+
([#253](https://github.com/3scale/apisonator/pull/253)),
|
|
32
|
+
([#254](https://github.com/3scale/apisonator/pull/254)), and
|
|
33
|
+
([#255](https://github.com/3scale/apisonator/pull/255)).
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Updated async-redis to v0.5.1
|
|
38
|
+
([#251](https://github.com/3scale/apisonator/pull/251)).
|
|
39
|
+
|
|
40
|
+
## 3.2.1 - 2021-01-22
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- Reports of 0 hits no longer generate unnecessary stats keys in Redis
|
|
45
|
+
([#247](https://github.com/3scale/apisonator/pull/247)).
|
|
46
|
+
|
|
47
|
+
## 3.2.0 - 2021-01-19
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- New endpoint in the internal API to get the provider key for a given (token,
|
|
52
|
+
service_id) pair ([#243](https://github.com/3scale/apisonator/pull/243)).
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- The config file used when running in a Docker image now parses "1" and "true"
|
|
57
|
+
(case-insensitive) as true
|
|
58
|
+
([#245](https://github.com/3scale/apisonator/pull/245)).
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- Fixed some metrics of the internal API that were not being counted
|
|
63
|
+
correctly([#244](https://github.com/3scale/apisonator/pull/244)).
|
|
64
|
+
|
|
65
|
+
|
|
5
66
|
## 3.1.0 - 2020-10-14
|
|
6
67
|
|
|
7
68
|
### Added
|
data/Gemfile.base
CHANGED
|
@@ -15,11 +15,15 @@ platform :ruby do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
group :test do
|
|
18
|
+
# Newer versions of rack-test don't work well with rspec-api-documentation.
|
|
19
|
+
# See https://github.com/rack/rack-test/pull/223 &
|
|
20
|
+
# https://github.com/zipmark/rspec_api_documentation/issues/342
|
|
21
|
+
gem 'rack-test', '= 0.8.2'
|
|
22
|
+
|
|
18
23
|
gem 'benchmark-ips', '~> 2.7.2'
|
|
19
24
|
gem 'mocha', '~> 1.3'
|
|
20
25
|
gem 'nokogiri', '~> 1.10.8'
|
|
21
26
|
gem 'pkg-config', '~> 1.1.7'
|
|
22
|
-
gem 'rack-test', '~> 0.8.2'
|
|
23
27
|
gem 'resque_unit', '~> 0.4.4', source: 'https://rubygems.org'
|
|
24
28
|
gem 'test-unit', '~> 3.2.6'
|
|
25
29
|
gem 'resque_spec', '~> 0.17.0'
|
|
@@ -42,7 +46,7 @@ group :development, :test do
|
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
# Default server by platform
|
|
45
|
-
gem 'puma', git: 'https://github.com/3scale/puma',
|
|
49
|
+
gem 'puma', git: 'https://github.com/3scale/puma', branch: '3scale-4.3.7'
|
|
46
50
|
# gems required by the runner
|
|
47
51
|
gem 'gli', '~> 2.16.1', require: nil
|
|
48
52
|
# Workers
|
|
@@ -53,13 +57,14 @@ gem 'rake', '~> 13.0'
|
|
|
53
57
|
gem 'builder', '= 3.2.3'
|
|
54
58
|
# Use a patched resque to allow reusing their Airbrake Failure class
|
|
55
59
|
gem 'resque', git: 'https://github.com/3scale/resque', branch: '3scale'
|
|
60
|
+
gem 'redis-namespace', '~>1.8.0'
|
|
56
61
|
gem 'rack', '~> 2.1.4'
|
|
57
62
|
gem 'sinatra', '~> 2.0.3'
|
|
58
63
|
gem 'sinatra-contrib', '~> 2.0.3'
|
|
59
64
|
# Optional external error logging services
|
|
60
65
|
gem 'bugsnag', '~> 6', require: nil
|
|
61
66
|
gem 'yabeda-prometheus', '~> 0.5.0'
|
|
62
|
-
gem 'async-redis', '~> 0.5'
|
|
67
|
+
gem 'async-redis', '~> 0.5.1'
|
|
63
68
|
gem 'falcon', '~> 0.35'
|
|
64
69
|
|
|
65
70
|
# Use a patched redis-rb that fixes an issue when trying to connect with
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
GIT
|
|
2
2
|
remote: https://github.com/3scale/puma
|
|
3
|
-
revision:
|
|
4
|
-
|
|
3
|
+
revision: c0601d08695839b8ffd0f380e91c3b91c1e8b754
|
|
4
|
+
branch: 3scale-4.3.7
|
|
5
5
|
specs:
|
|
6
|
-
puma (
|
|
6
|
+
puma (4.3.7)
|
|
7
|
+
nio4r (~> 2.0)
|
|
7
8
|
|
|
8
9
|
GIT
|
|
9
10
|
remote: https://github.com/3scale/redis-rb
|
|
@@ -35,7 +36,7 @@ GIT
|
|
|
35
36
|
PATH
|
|
36
37
|
remote: .
|
|
37
38
|
specs:
|
|
38
|
-
apisonator (3.1.
|
|
39
|
+
apisonator (3.3.1.1)
|
|
39
40
|
|
|
40
41
|
GEM
|
|
41
42
|
remote: https://rubygems.org/
|
|
@@ -70,7 +71,7 @@ GEM
|
|
|
70
71
|
async (~> 1.14)
|
|
71
72
|
async-pool (0.2.0)
|
|
72
73
|
async (~> 1.8)
|
|
73
|
-
async-redis (0.5.
|
|
74
|
+
async-redis (0.5.1)
|
|
74
75
|
async (~> 1.8)
|
|
75
76
|
async-io (~> 1.10)
|
|
76
77
|
async-pool (~> 0.2)
|
|
@@ -142,7 +143,7 @@ GEM
|
|
|
142
143
|
net-scp (1.2.1)
|
|
143
144
|
net-ssh (>= 2.6.5)
|
|
144
145
|
net-ssh (4.2.0)
|
|
145
|
-
nio4r (2.5.
|
|
146
|
+
nio4r (2.5.4)
|
|
146
147
|
nokogiri (1.10.9)
|
|
147
148
|
mini_portile2 (~> 2.4.0)
|
|
148
149
|
parslet (1.8.2)
|
|
@@ -178,7 +179,7 @@ GEM
|
|
|
178
179
|
rack-test (0.8.2)
|
|
179
180
|
rack (>= 1.0, < 3)
|
|
180
181
|
rake (13.0.1)
|
|
181
|
-
redis-namespace (1.
|
|
182
|
+
redis-namespace (1.8.0)
|
|
182
183
|
redis (>= 3.0.4)
|
|
183
184
|
resque_spec (0.17.0)
|
|
184
185
|
resque (>= 1.19.0)
|
|
@@ -241,7 +242,7 @@ GEM
|
|
|
241
242
|
thread_safe (0.3.6)
|
|
242
243
|
tilt (2.0.8)
|
|
243
244
|
timecop (0.9.1)
|
|
244
|
-
timers (4.3.
|
|
245
|
+
timers (4.3.2)
|
|
245
246
|
toml (0.2.0)
|
|
246
247
|
parslet (~> 1.8.0)
|
|
247
248
|
tzinfo (1.2.7)
|
|
@@ -267,7 +268,7 @@ PLATFORMS
|
|
|
267
268
|
DEPENDENCIES
|
|
268
269
|
airbrake (= 4.3.1)
|
|
269
270
|
apisonator!
|
|
270
|
-
async-redis (~> 0.5)
|
|
271
|
+
async-redis (~> 0.5.1)
|
|
271
272
|
async-rspec
|
|
272
273
|
aws-sdk (= 2.4.2)
|
|
273
274
|
benchmark-ips (~> 2.7.2)
|
|
@@ -288,9 +289,10 @@ DEPENDENCIES
|
|
|
288
289
|
pry-doc (~> 0.11.1)
|
|
289
290
|
puma!
|
|
290
291
|
rack (~> 2.1.4)
|
|
291
|
-
rack-test (
|
|
292
|
+
rack-test (= 0.8.2)
|
|
292
293
|
rake (~> 13.0)
|
|
293
294
|
redis!
|
|
295
|
+
redis-namespace (~> 1.8.0)
|
|
294
296
|
resque!
|
|
295
297
|
resque_spec (~> 0.17.0)
|
|
296
298
|
resque_unit (~> 0.4.4)!
|
data/Gemfile.on_prem.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
GIT
|
|
2
2
|
remote: https://github.com/3scale/puma
|
|
3
|
-
revision:
|
|
4
|
-
|
|
3
|
+
revision: c0601d08695839b8ffd0f380e91c3b91c1e8b754
|
|
4
|
+
branch: 3scale-4.3.7
|
|
5
5
|
specs:
|
|
6
|
-
puma (
|
|
6
|
+
puma (4.3.7)
|
|
7
|
+
nio4r (~> 2.0)
|
|
7
8
|
|
|
8
9
|
GIT
|
|
9
10
|
remote: https://github.com/3scale/redis-rb
|
|
@@ -35,7 +36,7 @@ GIT
|
|
|
35
36
|
PATH
|
|
36
37
|
remote: .
|
|
37
38
|
specs:
|
|
38
|
-
apisonator (3.1.
|
|
39
|
+
apisonator (3.3.1.1)
|
|
39
40
|
|
|
40
41
|
GEM
|
|
41
42
|
remote: https://rubygems.org/
|
|
@@ -67,7 +68,7 @@ GEM
|
|
|
67
68
|
async (~> 1.14)
|
|
68
69
|
async-pool (0.2.0)
|
|
69
70
|
async (~> 1.8)
|
|
70
|
-
async-redis (0.5.
|
|
71
|
+
async-redis (0.5.1)
|
|
71
72
|
async (~> 1.8)
|
|
72
73
|
async-io (~> 1.10)
|
|
73
74
|
async-pool (~> 0.2)
|
|
@@ -131,7 +132,7 @@ GEM
|
|
|
131
132
|
net-scp (1.2.1)
|
|
132
133
|
net-ssh (>= 2.6.5)
|
|
133
134
|
net-ssh (4.2.0)
|
|
134
|
-
nio4r (2.5.
|
|
135
|
+
nio4r (2.5.4)
|
|
135
136
|
nokogiri (1.10.9)
|
|
136
137
|
mini_portile2 (~> 2.4.0)
|
|
137
138
|
parslet (1.8.2)
|
|
@@ -166,7 +167,7 @@ GEM
|
|
|
166
167
|
rack-test (0.8.2)
|
|
167
168
|
rack (>= 1.0, < 3)
|
|
168
169
|
rake (13.0.1)
|
|
169
|
-
redis-namespace (1.
|
|
170
|
+
redis-namespace (1.8.0)
|
|
170
171
|
redis (>= 3.0.4)
|
|
171
172
|
resque_spec (0.17.0)
|
|
172
173
|
resque (>= 1.19.0)
|
|
@@ -227,7 +228,7 @@ GEM
|
|
|
227
228
|
thread_safe (0.3.6)
|
|
228
229
|
tilt (2.0.8)
|
|
229
230
|
timecop (0.9.1)
|
|
230
|
-
timers (4.3.
|
|
231
|
+
timers (4.3.2)
|
|
231
232
|
toml (0.2.0)
|
|
232
233
|
parslet (~> 1.8.0)
|
|
233
234
|
tzinfo (1.2.7)
|
|
@@ -250,7 +251,7 @@ PLATFORMS
|
|
|
250
251
|
|
|
251
252
|
DEPENDENCIES
|
|
252
253
|
apisonator!
|
|
253
|
-
async-redis (~> 0.5)
|
|
254
|
+
async-redis (~> 0.5.1)
|
|
254
255
|
async-rspec
|
|
255
256
|
benchmark-ips (~> 2.7.2)
|
|
256
257
|
bugsnag (~> 6)
|
|
@@ -269,9 +270,10 @@ DEPENDENCIES
|
|
|
269
270
|
pry-doc (~> 0.11.1)
|
|
270
271
|
puma!
|
|
271
272
|
rack (~> 2.1.4)
|
|
272
|
-
rack-test (
|
|
273
|
+
rack-test (= 0.8.2)
|
|
273
274
|
rake (~> 13.0)
|
|
274
275
|
redis!
|
|
276
|
+
redis-namespace (~> 1.8.0)
|
|
275
277
|
resque!
|
|
276
278
|
resque_spec (~> 0.17.0)
|
|
277
279
|
resque_unit (~> 0.4.4)!
|
data/Rakefile
CHANGED
|
@@ -261,27 +261,49 @@ task :reschedule_failed_jobs do
|
|
|
261
261
|
"Pending failed jobs: #{result[:failed_current]}."
|
|
262
262
|
end
|
|
263
263
|
|
|
264
|
-
desc 'Delete stats of services marked for deletion'
|
|
265
264
|
namespace :stats do
|
|
265
|
+
desc 'Delete stats of services marked for deletion'
|
|
266
266
|
task :cleanup, [:redis_urls, :log_deleted_keys] do |_, args|
|
|
267
|
-
|
|
267
|
+
redis_conns = redis_conns(args[:redis_urls])
|
|
268
268
|
|
|
269
|
-
if
|
|
269
|
+
if redis_conns.empty?
|
|
270
270
|
puts 'No Redis URLs specified'
|
|
271
271
|
exit(false)
|
|
272
272
|
end
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
ThreeScale::Backend::Stats::Cleaner.delete!(
|
|
275
|
+
redis_conns, log_deleted_keys: logger_for_deleted_keys(args[:log_deleted_keys])
|
|
276
|
+
)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
desc 'Delete stats keys set to 0'
|
|
280
|
+
task :delete_stats_keys_set_to_0, [:redis_urls, :log_deleted_keys] do |_, args|
|
|
281
|
+
redis_conns = redis_conns(args[:redis_urls])
|
|
282
|
+
|
|
283
|
+
if redis_conns.empty?
|
|
284
|
+
puts 'No Redis URLs specified'
|
|
285
|
+
exit(false)
|
|
279
286
|
end
|
|
280
287
|
|
|
281
|
-
|
|
288
|
+
ThreeScale::Backend::Stats::Cleaner.delete_stats_keys_set_to_0(
|
|
289
|
+
redis_conns, log_deleted_keys: logger_for_deleted_keys(args[:log_deleted_keys])
|
|
290
|
+
)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
282
293
|
|
|
283
|
-
|
|
284
|
-
|
|
294
|
+
def redis_conns(urls)
|
|
295
|
+
redis_urls = urls && urls.split(' ')
|
|
296
|
+
|
|
297
|
+
return [] if redis_urls.nil? || redis_urls.empty?
|
|
298
|
+
|
|
299
|
+
redis_urls.map do |redis_url|
|
|
300
|
+
parsed_uri = URI.parse(ThreeScale::Backend::Storage::Helpers.send(
|
|
301
|
+
:to_redis_uri, redis_url)
|
|
285
302
|
)
|
|
303
|
+
Redis.new(host: parsed_uri.host, port: parsed_uri.port)
|
|
286
304
|
end
|
|
287
305
|
end
|
|
306
|
+
|
|
307
|
+
def logger_for_deleted_keys(arg_log_deleted_keys)
|
|
308
|
+
arg_log_deleted_keys == 'true' ? STDOUT : nil
|
|
309
|
+
end
|
|
@@ -7,6 +7,14 @@ module ThreeScale
|
|
|
7
7
|
ServiceToken.exists?(token, service_id) ? 200 : 404
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
get '/:token/:service_id/provider_key' do |token, service_id|
|
|
11
|
+
if ServiceToken.exists?(token, service_id)
|
|
12
|
+
{ status: :found, provider_key: Service.provider_key_for(service_id) }.to_json
|
|
13
|
+
else
|
|
14
|
+
respond_with_404('token/service combination not found'.freeze)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
10
18
|
post '/' do
|
|
11
19
|
check_tokens_param!
|
|
12
20
|
|
data/app/api/internal/stats.rb
CHANGED
|
@@ -6,32 +6,13 @@ module ThreeScale
|
|
|
6
6
|
respond_with_404('service not found') unless Service.exists?(params[:service_id])
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
# This
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
delete '' do |service_id|
|
|
13
|
-
delete_stats_job_attrs = api_params Stats::DeleteJobDef
|
|
14
|
-
delete_stats_job_attrs[:service_id] = service_id
|
|
15
|
-
delete_stats_job_attrs[:from] = delete_stats_job_attrs[:from].to_i
|
|
16
|
-
delete_stats_job_attrs[:to] = delete_stats_job_attrs[:to].to_i
|
|
17
|
-
begin
|
|
18
|
-
Stats::DeleteJobDef.new(delete_stats_job_attrs).run_async
|
|
19
|
-
rescue DeleteServiceStatsValidationError => e
|
|
20
|
-
[400, headers, { status: :error, error: e.message }.to_json]
|
|
21
|
-
else
|
|
22
|
-
{ status: :to_be_deleted }.to_json
|
|
23
|
-
end
|
|
24
|
-
=end
|
|
25
|
-
|
|
26
|
-
# This is an alternative to the above. It just adds the service to a
|
|
27
|
-
# Redis set to marked is as "to be deleted".
|
|
28
|
-
# Later a script can read that set and actually delete the keys.
|
|
29
|
-
# Read the docs of the Stats::Cleaner class for more details.
|
|
9
|
+
# This adds the service to a Redis set to mark is as "to be deleted".
|
|
10
|
+
# Later a script can read that set and actually delete the keys. Read
|
|
11
|
+
# the docs of the Stats::Cleaner class for more details.
|
|
30
12
|
#
|
|
31
|
-
# Notice that this method ignores the "from" and "to" parameters
|
|
32
|
-
# system calls this method, they're always
|
|
33
|
-
#
|
|
34
|
-
# implementation of the option above easier.
|
|
13
|
+
# Notice that this method ignores the "from" and "to" parameters used in
|
|
14
|
+
# previous versions. When system calls this method, they're always
|
|
15
|
+
# interested in deleting all the keys.
|
|
35
16
|
delete '' do |service_id|
|
|
36
17
|
Stats::Cleaner.mark_service_to_be_deleted(service_id)
|
|
37
18
|
{ status: :to_be_deleted }.to_json
|
|
@@ -40,10 +40,8 @@ module ThreeScale
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
42
|
def self.first_traffic(service_id, application_id)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
if storage.sadd(key, encode_key(application_id))
|
|
43
|
+
if storage.sadd(Stats::Keys.set_of_apps_with_traffic(service_id),
|
|
44
|
+
encode_key(application_id))
|
|
47
45
|
EventStorage.store(:first_traffic,
|
|
48
46
|
{ service_id: service_id,
|
|
49
47
|
application_id: application_id,
|
|
@@ -32,8 +32,6 @@ module ThreeScale
|
|
|
32
32
|
|
|
33
33
|
CONFIG_DELETE_STATS_BATCH_SIZE = 50
|
|
34
34
|
private_constant :CONFIG_DELETE_STATS_BATCH_SIZE
|
|
35
|
-
CONFIG_DELETE_STATS_PARTITION_BATCH_SIZE = 1000
|
|
36
|
-
private_constant :CONFIG_DELETE_STATS_PARTITION_BATCH_SIZE
|
|
37
35
|
|
|
38
36
|
@configuration = Configuration::Loader.new
|
|
39
37
|
|
|
@@ -54,7 +52,7 @@ module ThreeScale
|
|
|
54
52
|
config.add_section(:analytics_redis, :server,
|
|
55
53
|
:connect_timeout, :read_timeout, :write_timeout)
|
|
56
54
|
config.add_section(:hoptoad, :service, :api_key)
|
|
57
|
-
config.add_section(:stats, :bucket_size, :delete_batch_size
|
|
55
|
+
config.add_section(:stats, :bucket_size, :delete_batch_size)
|
|
58
56
|
config.add_section(:redshift, :host, :port, :dbname, :user, :password)
|
|
59
57
|
config.add_section(:statsd, :host, :port)
|
|
60
58
|
config.add_section(:internal_api, :user, :password)
|
|
@@ -125,9 +123,6 @@ module ThreeScale
|
|
|
125
123
|
config.stats.delete_batch_size = parse_int(config.stats.delete_batch_size,
|
|
126
124
|
CONFIG_DELETE_STATS_BATCH_SIZE)
|
|
127
125
|
|
|
128
|
-
config.stats.delete_partition_batch_size = parse_int(config.stats.delete_partition_batch_size,
|
|
129
|
-
CONFIG_DELETE_STATS_PARTITION_BATCH_SIZE)
|
|
130
|
-
|
|
131
126
|
# often we don't have a log_file setting - generate it here from
|
|
132
127
|
# the log_path setting.
|
|
133
128
|
log_file = config.log_file
|
|
@@ -292,12 +292,6 @@ module ThreeScale
|
|
|
292
292
|
end
|
|
293
293
|
end
|
|
294
294
|
|
|
295
|
-
class DeleteServiceStatsValidationError < Error
|
|
296
|
-
def initialize(service_id, msg)
|
|
297
|
-
super "Delete stats job context validation error. Service: #{service_id}. Error: #{msg}"
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
295
|
class EndUsersNoLongerSupported < BadRequest
|
|
302
296
|
def initialize
|
|
303
297
|
super 'End-users are no longer supported, do not specify the user_id parameter'.freeze
|
|
@@ -32,25 +32,6 @@ module ThreeScale
|
|
|
32
32
|
DEFAULT_WAIT_BEFORE_FETCHING_MORE_JOBS
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def pop_from_queue
|
|
36
|
-
begin
|
|
37
|
-
encoded_job = @redis.blpop(*@queues, timeout: @fetch_timeout)
|
|
38
|
-
rescue Redis::BaseConnectionError, Errno::ECONNREFUSED, Errno::EPIPE => e
|
|
39
|
-
raise RedisConnectionError.new(e.message)
|
|
40
|
-
rescue Redis::CommandError => e
|
|
41
|
-
# Redis::CommandError from redis-rb can be raised for multiple
|
|
42
|
-
# reasons, so we need to check the error message to distinguish
|
|
43
|
-
# connection errors from the rest.
|
|
44
|
-
if e.message == 'ERR Connection timed out'.freeze
|
|
45
|
-
raise RedisConnectionError.new(e.message)
|
|
46
|
-
else
|
|
47
|
-
raise e
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
encoded_job
|
|
52
|
-
end
|
|
53
|
-
|
|
54
35
|
def fetch
|
|
55
36
|
encoded_job = pop_from_queue
|
|
56
37
|
return nil if encoded_job.nil? || encoded_job.empty?
|
|
@@ -99,10 +80,11 @@ module ThreeScale
|
|
|
99
80
|
|
|
100
81
|
# Re-instantiate Redis instance. This is needed to recover from
|
|
101
82
|
# Errno::EPIPE, not sure if there are others.
|
|
102
|
-
@redis =
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
@redis = Redis::Namespace.new(
|
|
84
|
+
WorkerAsync.const_get(:RESQUE_REDIS_NAMESPACE),
|
|
85
|
+
redis: QueueStorage.connection(Backend.environment, Backend.configuration)
|
|
105
86
|
)
|
|
87
|
+
|
|
106
88
|
# If there is a different kind of error, it's probably a
|
|
107
89
|
# programming error. Like sending an invalid blpop command to
|
|
108
90
|
# Redis. In that case, let the worker crash.
|
|
@@ -111,12 +93,36 @@ module ThreeScale
|
|
|
111
93
|
end
|
|
112
94
|
end
|
|
113
95
|
|
|
96
|
+
rescue Exception => e
|
|
97
|
+
Worker.logger.notify(e)
|
|
98
|
+
ensure
|
|
114
99
|
job_queue.close
|
|
115
100
|
end
|
|
116
101
|
|
|
117
102
|
def shutdown
|
|
118
103
|
@shutdown = true
|
|
119
104
|
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def pop_from_queue
|
|
109
|
+
begin
|
|
110
|
+
encoded_job = @redis.blpop(*@queues, timeout: @fetch_timeout)
|
|
111
|
+
rescue Redis::BaseConnectionError, Errno::ECONNREFUSED, Errno::EPIPE => e
|
|
112
|
+
raise RedisConnectionError.new(e.message)
|
|
113
|
+
rescue Redis::CommandError => e
|
|
114
|
+
# Redis::CommandError from redis-rb can be raised for multiple
|
|
115
|
+
# reasons, so we need to check the error message to distinguish
|
|
116
|
+
# connection errors from the rest.
|
|
117
|
+
if e.message == 'ERR Connection timed out'.freeze
|
|
118
|
+
raise RedisConnectionError.new(e.message)
|
|
119
|
+
else
|
|
120
|
+
raise e
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
encoded_job
|
|
125
|
+
end
|
|
120
126
|
end
|
|
121
127
|
end
|
|
122
128
|
end
|
|
@@ -13,19 +13,21 @@ module ThreeScale
|
|
|
13
13
|
}
|
|
14
14
|
private_constant :AUTH_AND_REPORT_REQUEST_TYPES
|
|
15
15
|
|
|
16
|
+
# Only the first match is taken into account, that's why for example,
|
|
17
|
+
# "/\/services\/.*\/stats/" needs to appear before "/\/services/"
|
|
16
18
|
INTERNAL_API_PATHS = [
|
|
17
|
-
[/\/services\/.*\/alert_limits/, 'alerts'],
|
|
18
|
-
[/\/services\/.*\/applications\/.*\/keys/, 'application_keys'],
|
|
19
|
-
[/\/services\/.*\/applications\/.*\/referrer_filters/, 'application_referrer_filters'],
|
|
20
|
-
[/\/services\/.*\/applications/, '
|
|
21
|
-
[/\/services\/.*\/
|
|
22
|
-
[/\/
|
|
23
|
-
[/\/
|
|
24
|
-
[/\/
|
|
25
|
-
[/\/
|
|
26
|
-
[/\/services\/.*\/stats/, 'stats'],
|
|
27
|
-
[/\/services\/.*\/plans\/.*\/usagelimits/, 'usage_limits'],
|
|
28
|
-
[/\/services
|
|
19
|
+
[/\/services\/.*\/alert_limits/, 'alerts'.freeze],
|
|
20
|
+
[/\/services\/.*\/applications\/.*\/keys/, 'application_keys'.freeze],
|
|
21
|
+
[/\/services\/.*\/applications\/.*\/referrer_filters/, 'application_referrer_filters'.freeze],
|
|
22
|
+
[/\/services\/.*\/applications\/.*\/utilization/, 'utilization'.freeze],
|
|
23
|
+
[/\/services\/.*\/applications/, 'applications'.freeze],
|
|
24
|
+
[/\/services\/.*\/errors/, 'errors'.freeze],
|
|
25
|
+
[/\/events/, 'events'.freeze],
|
|
26
|
+
[/\/services\/.*\/metrics/, 'metrics'.freeze],
|
|
27
|
+
[/\/service_tokens/, 'service_tokens'.freeze],
|
|
28
|
+
[/\/services\/.*\/stats/, 'stats'.freeze],
|
|
29
|
+
[/\/services\/.*\/plans\/.*\/usagelimits/, 'usage_limits'.freeze],
|
|
30
|
+
[/\/services/, 'services'.freeze],
|
|
29
31
|
].freeze
|
|
30
32
|
private_constant :INTERNAL_API_PATHS
|
|
31
33
|
|
data/lib/3scale/backend/stats.rb
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
require '3scale/backend/stats/codes_commons'
|
|
2
2
|
require '3scale/backend/stats/period_commons'
|
|
3
3
|
require '3scale/backend/stats/aggregator'
|
|
4
|
-
require '3scale/backend/stats/delete_job_def'
|
|
5
|
-
require '3scale/backend/stats/key_generator'
|
|
6
|
-
require '3scale/backend/stats/partition_generator_job'
|
|
7
|
-
require '3scale/backend/stats/partition_eraser_job'
|
|
8
4
|
require '3scale/backend/stats/cleaner'
|
|
@@ -20,7 +20,14 @@ module ThreeScale
|
|
|
20
20
|
key = counter_key(prefix_key, granularity.new(timestamp))
|
|
21
21
|
expire_time = Stats::PeriodCommons.expire_time_for_granularity(granularity)
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
# We don't need to store stats keys set to 0. It wastes Redis
|
|
24
|
+
# memory because for rate-limiting and stats, a key of set to 0
|
|
25
|
+
# is equivalent to a key that does not exist.
|
|
26
|
+
if cmd == :set && value == 0
|
|
27
|
+
storage.del(key)
|
|
28
|
+
else
|
|
29
|
+
store_key(cmd, key, value, expire_time)
|
|
30
|
+
end
|
|
24
31
|
|
|
25
32
|
unless Stats::PeriodCommons::EXCLUDED_FOR_BUCKETS.include?(granularity)
|
|
26
33
|
keys_for_bucket << key
|
|
@@ -45,6 +45,12 @@ module ThreeScale
|
|
|
45
45
|
STATS_KEY_PREFIX = 'stats/'.freeze
|
|
46
46
|
private_constant :STATS_KEY_PREFIX
|
|
47
47
|
|
|
48
|
+
REDIS_CONN_ERRORS = [Redis::BaseConnectionError, Errno::ECONNREFUSED, Errno::EPIPE].freeze
|
|
49
|
+
private_constant :REDIS_CONN_ERRORS
|
|
50
|
+
|
|
51
|
+
MAX_RETRIES_REDIS_ERRORS = 3
|
|
52
|
+
private_constant :MAX_RETRIES_REDIS_ERRORS
|
|
53
|
+
|
|
48
54
|
class << self
|
|
49
55
|
include Logging
|
|
50
56
|
def mark_service_to_be_deleted(service_id)
|
|
@@ -77,37 +83,73 @@ module ThreeScale
|
|
|
77
83
|
logger.info("Going to delete the stats keys for these services: #{services.to_a}")
|
|
78
84
|
|
|
79
85
|
unless services.empty?
|
|
80
|
-
|
|
81
|
-
redis_conns.each do |redis_conn|
|
|
86
|
+
_ok, failed = redis_conns.partition do |redis_conn|
|
|
82
87
|
begin
|
|
83
88
|
delete_keys(redis_conn, services, log_deleted_keys)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
logger.error("Error while deleting stats of server #{redis_conn}: #{e}")
|
|
89
|
-
delete_successful = false
|
|
90
|
-
rescue Redis::CommandError => e
|
|
91
|
-
# Redis::CommandError from redis-rb can be raised for multiple
|
|
92
|
-
# reasons, so we need to check the error message to distinguish
|
|
93
|
-
# connection errors from the rest.
|
|
94
|
-
if e.message == 'ERR Connection timed out'.freeze
|
|
95
|
-
logger.error("Error while deleting stats of server #{redis_conn}: #{e}")
|
|
96
|
-
delete_successful = false
|
|
97
|
-
else
|
|
98
|
-
raise e
|
|
99
|
-
end
|
|
89
|
+
true
|
|
90
|
+
rescue => e
|
|
91
|
+
handle_redis_exception(e, redis_conn)
|
|
92
|
+
false
|
|
100
93
|
end
|
|
101
94
|
end
|
|
102
95
|
|
|
103
|
-
remove_services_from_delete_set(services) if
|
|
96
|
+
with_retries { remove_services_from_delete_set(services) } if failed.empty?
|
|
97
|
+
|
|
98
|
+
failed.each do |failed_conn|
|
|
99
|
+
logger.error("Error while deleting stats of server #{failed_conn}")
|
|
100
|
+
end
|
|
104
101
|
end
|
|
105
102
|
|
|
106
103
|
logger.info("Finished deleting the stats keys for these services: #{services.to_a}")
|
|
107
104
|
end
|
|
108
105
|
|
|
106
|
+
# Deletes all the stats keys set to 0.
|
|
107
|
+
#
|
|
108
|
+
# Stats keys set to 0 are useless and occupy Redis memory
|
|
109
|
+
# unnecessarily. They were generated due to a bug in previous versions
|
|
110
|
+
# of Apisonator.
|
|
111
|
+
# Ref: https://github.com/3scale/apisonator/pull/247
|
|
112
|
+
#
|
|
113
|
+
# As the .delete function, this one also receives a collection of
|
|
114
|
+
# instantiated Redis clients and those need to connect to Redis
|
|
115
|
+
# servers directly.
|
|
116
|
+
#
|
|
117
|
+
# @param [Array] redis_conns Instantiated Redis clients.
|
|
118
|
+
# @param [IO] log_deleted_keys IO where to write the logs. Defaults to
|
|
119
|
+
# nil (logs nothing).
|
|
120
|
+
def delete_stats_keys_set_to_0(redis_conns, log_deleted_keys: nil)
|
|
121
|
+
_ok, failed = redis_conns.partition do |redis_conn|
|
|
122
|
+
begin
|
|
123
|
+
delete_stats_keys_with_val_0(redis_conn, log_deleted_keys)
|
|
124
|
+
true
|
|
125
|
+
rescue => e
|
|
126
|
+
handle_redis_exception(e, redis_conn)
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
failed.each do |failed_conn|
|
|
132
|
+
logger.error("Error while deleting stats of server #{failed_conn}")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
109
136
|
private
|
|
110
137
|
|
|
138
|
+
def handle_redis_exception(exception, redis_conn)
|
|
139
|
+
# If it's a connection error, do nothing so we can continue with
|
|
140
|
+
# other shards. If it's another kind of error, it could be caused by
|
|
141
|
+
# a bug, so better re-raise.
|
|
142
|
+
|
|
143
|
+
case exception
|
|
144
|
+
when *REDIS_CONN_ERRORS
|
|
145
|
+
# Do nothing.
|
|
146
|
+
when Redis::CommandError
|
|
147
|
+
raise exception if exception.message != 'ERR Connection timed out'.freeze
|
|
148
|
+
else
|
|
149
|
+
raise exception
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
111
153
|
# Returns a set with the services included in the
|
|
112
154
|
# SET_WITH_SERVICES_MARKED_FOR_DELETION Redis set.
|
|
113
155
|
def services_to_delete
|
|
@@ -133,19 +175,21 @@ module ThreeScale
|
|
|
133
175
|
cursor = 0
|
|
134
176
|
|
|
135
177
|
loop do
|
|
136
|
-
|
|
178
|
+
with_retries do
|
|
179
|
+
cursor, keys = redis_conn.scan(cursor, count: SCAN_SLICE)
|
|
137
180
|
|
|
138
|
-
|
|
181
|
+
to_delete = keys.select { |key| delete_key?(key, services) }
|
|
139
182
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
183
|
+
unless to_delete.empty?
|
|
184
|
+
if log_deleted_keys
|
|
185
|
+
values = redis_conn.mget(*(to_delete.to_a))
|
|
186
|
+
to_delete.each_with_index do |k, i|
|
|
187
|
+
log_deleted_keys.puts "#{k} #{values[i]}"
|
|
188
|
+
end
|
|
145
189
|
end
|
|
146
|
-
end
|
|
147
190
|
|
|
148
|
-
|
|
191
|
+
redis_conn.del(to_delete)
|
|
192
|
+
end
|
|
149
193
|
end
|
|
150
194
|
|
|
151
195
|
break if cursor.to_i == 0
|
|
@@ -188,6 +232,43 @@ module ThreeScale
|
|
|
188
232
|
# simply ignore those keys.
|
|
189
233
|
nil
|
|
190
234
|
end
|
|
235
|
+
|
|
236
|
+
def delete_stats_keys_with_val_0(redis_conn, log_deleted_keys)
|
|
237
|
+
cursor = 0
|
|
238
|
+
|
|
239
|
+
loop do
|
|
240
|
+
with_retries do
|
|
241
|
+
cursor, keys = redis_conn.scan(cursor, count: SCAN_SLICE)
|
|
242
|
+
|
|
243
|
+
stats_keys = keys.select { |k| is_stats_key?(k) }
|
|
244
|
+
|
|
245
|
+
unless stats_keys.empty?
|
|
246
|
+
values = redis_conn.mget(*stats_keys)
|
|
247
|
+
to_delete = stats_keys.zip(values).select { |_, v| v == '0'.freeze }.map(&:first)
|
|
248
|
+
|
|
249
|
+
unless to_delete.empty?
|
|
250
|
+
redis_conn.del(to_delete)
|
|
251
|
+
to_delete.each { |k| log_deleted_keys.puts k } if log_deleted_keys
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
break if cursor.to_i == 0
|
|
257
|
+
|
|
258
|
+
sleep(SLEEP_BETWEEN_SCANS)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def with_retries(max = MAX_RETRIES_REDIS_ERRORS)
|
|
263
|
+
retries = 0
|
|
264
|
+
begin
|
|
265
|
+
yield
|
|
266
|
+
rescue Exception => e
|
|
267
|
+
retries += 1
|
|
268
|
+
retry if retries < max
|
|
269
|
+
raise e
|
|
270
|
+
end
|
|
271
|
+
end
|
|
191
272
|
end
|
|
192
273
|
end
|
|
193
274
|
end
|
|
@@ -70,6 +70,12 @@ module ThreeScale
|
|
|
70
70
|
key
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
def set_of_apps_with_traffic(service_id)
|
|
74
|
+
Stats::Keys.applications_key_prefix(
|
|
75
|
+
Stats::Keys.service_key_prefix(service_id)
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
73
79
|
# We want all the buckets to go to the same Redis shard.
|
|
74
80
|
# The reason is that SUNION support in Twemproxy requires that the
|
|
75
81
|
# supplied keys hash to the same server.
|
|
@@ -12,9 +12,6 @@ module ThreeScale
|
|
|
12
12
|
GRANULARITY_EXPIRATION_TIME = { Period[:minute] => 180 }.freeze
|
|
13
13
|
private_constant :GRANULARITY_EXPIRATION_TIME
|
|
14
14
|
|
|
15
|
-
PERMANENT_SERVICE_GRANULARITIES = (SERVICE_GRANULARITIES - GRANULARITY_EXPIRATION_TIME.keys).freeze
|
|
16
|
-
PERMANENT_EXPANDED_GRANULARITIES = (EXPANDED_GRANULARITIES - GRANULARITY_EXPIRATION_TIME.keys).freeze
|
|
17
|
-
|
|
18
15
|
# We are not going to send metrics with granularity 'eternity' or
|
|
19
16
|
# 'week' to Kinesis, so there is no point in storing them in Redis
|
|
20
17
|
# buckets.
|
|
@@ -20,8 +20,14 @@ module ThreeScale
|
|
|
20
20
|
def report(provider_key, service_id, transactions, context_info = {})
|
|
21
21
|
service = Service.load_with_provider_key!(service_id, provider_key)
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
# A usage of 0 does not affect rate-limits or stats, so we do not need
|
|
24
|
+
# to report it.
|
|
25
|
+
filtered_transactions = filter_usages_with_0(transactions.clone)
|
|
26
|
+
|
|
27
|
+
return if filtered_transactions.empty?
|
|
28
|
+
|
|
29
|
+
report_enqueue(service.id, filtered_transactions, context_info)
|
|
30
|
+
notify_report(provider_key, filtered_transactions.size)
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
def authorize(provider_key, params, context_info = {})
|
|
@@ -137,9 +143,17 @@ module ThreeScale
|
|
|
137
143
|
|
|
138
144
|
usage = params[:usage]
|
|
139
145
|
|
|
140
|
-
|
|
146
|
+
filtered_usage = filter_metrics_without_inc(usage.clone) if usage
|
|
147
|
+
|
|
148
|
+
if ((filtered_usage && !filtered_usage.empty?) || params[:log]) && status.authorized?
|
|
141
149
|
application_id = status.application.id
|
|
142
|
-
|
|
150
|
+
|
|
151
|
+
report_enqueue(
|
|
152
|
+
status.service_id,
|
|
153
|
+
{ 0 => {"app_id" => application_id, "usage" => filtered_usage, "log" => params[:log] } },
|
|
154
|
+
request: { extensions: request_info[:extensions] }
|
|
155
|
+
)
|
|
156
|
+
|
|
143
157
|
notify_authrep(provider_key, usage ? 1 : 0)
|
|
144
158
|
else
|
|
145
159
|
notify_authorize(provider_key)
|
|
@@ -182,6 +196,19 @@ module ThreeScale
|
|
|
182
196
|
end
|
|
183
197
|
end
|
|
184
198
|
|
|
199
|
+
def filter_usages_with_0(transactions)
|
|
200
|
+
# There are plenty of existing tests using both a string and a symbol
|
|
201
|
+
# when accessing the usage.
|
|
202
|
+
transactions.delete_if do |_idx, tx|
|
|
203
|
+
(usage = tx['usage'.freeze] || tx[:usage]) or next
|
|
204
|
+
filter_metrics_without_inc(usage).empty?
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def filter_metrics_without_inc(usage)
|
|
209
|
+
usage.delete_if { |_metric, delta| delta.to_s == '0'.freeze }
|
|
210
|
+
end
|
|
211
|
+
|
|
185
212
|
def storage
|
|
186
213
|
Storage.instance
|
|
187
214
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'async'
|
|
2
|
+
require 'redis-namespace'
|
|
2
3
|
require '3scale/backend/job_fetcher'
|
|
3
4
|
|
|
4
5
|
module ThreeScale
|
|
@@ -10,6 +11,9 @@ module ThreeScale
|
|
|
10
11
|
DEFAULT_MAX_CONCURRENT_JOBS = 20
|
|
11
12
|
private_constant :DEFAULT_MAX_CONCURRENT_JOBS
|
|
12
13
|
|
|
14
|
+
RESQUE_REDIS_NAMESPACE = :resque
|
|
15
|
+
private_constant :RESQUE_REDIS_NAMESPACE
|
|
16
|
+
|
|
13
17
|
def initialize(options = {})
|
|
14
18
|
trap('TERM') { shutdown }
|
|
15
19
|
trap('INT') { shutdown }
|
|
@@ -17,7 +21,7 @@ module ThreeScale
|
|
|
17
21
|
@one_off = options[:one_off]
|
|
18
22
|
@jobs = Queue.new # Thread-safe queue
|
|
19
23
|
|
|
20
|
-
@job_fetcher = options[:job_fetcher] || JobFetcher.new
|
|
24
|
+
@job_fetcher = options[:job_fetcher] || JobFetcher.new(redis_client: redis_client)
|
|
21
25
|
|
|
22
26
|
@max_concurrent_jobs = configuration.async_worker.max_concurrent_jobs ||
|
|
23
27
|
DEFAULT_MAX_CONCURRENT_JOBS
|
|
@@ -64,6 +68,10 @@ module ThreeScale
|
|
|
64
68
|
# unblocks when there are new jobs or when .close() is called
|
|
65
69
|
job = @jobs.pop
|
|
66
70
|
|
|
71
|
+
# If job is nil, it means that the queue is closed. No more jobs are
|
|
72
|
+
# going to be pushed, so shutdown.
|
|
73
|
+
shutdown unless job
|
|
74
|
+
|
|
67
75
|
break if @shutdown
|
|
68
76
|
|
|
69
77
|
@reactor.async { perform(job) }
|
|
@@ -83,6 +91,19 @@ module ThreeScale
|
|
|
83
91
|
Async { @job_fetcher.start(@jobs) }
|
|
84
92
|
end
|
|
85
93
|
end
|
|
94
|
+
|
|
95
|
+
# Returns a new Redis client with namespace "resque".
|
|
96
|
+
# In the async worker, the job fetcher runs in a separate thread, and we
|
|
97
|
+
# need to avoid sharing an already instantiated client like the one in
|
|
98
|
+
# Resque::Helpers initialized in lib/3scale/backend.rb (Resque.redis).
|
|
99
|
+
# Failing to do so, will raise errors because of fibers shared across
|
|
100
|
+
# threads.
|
|
101
|
+
def redis_client
|
|
102
|
+
Redis::Namespace.new(
|
|
103
|
+
RESQUE_REDIS_NAMESPACE,
|
|
104
|
+
redis: QueueStorage.connection(Backend.environment, Backend.configuration)
|
|
105
|
+
)
|
|
106
|
+
end
|
|
86
107
|
end
|
|
87
108
|
end
|
|
88
109
|
end
|
data/licenses.xml
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</dependency>
|
|
24
24
|
<dependency>
|
|
25
25
|
<packageName>apisonator</packageName>
|
|
26
|
-
<version>3.1.
|
|
26
|
+
<version>3.3.1.1</version>
|
|
27
27
|
<licenses>
|
|
28
28
|
<license>
|
|
29
29
|
<name>Apache 2.0</name>
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
</dependency>
|
|
94
94
|
<dependency>
|
|
95
95
|
<packageName>async-redis</packageName>
|
|
96
|
-
<version>0.5.
|
|
96
|
+
<version>0.5.1</version>
|
|
97
97
|
<licenses>
|
|
98
98
|
<license>
|
|
99
99
|
<name>MIT</name>
|
|
@@ -525,7 +525,7 @@
|
|
|
525
525
|
</dependency>
|
|
526
526
|
<dependency>
|
|
527
527
|
<packageName>nio4r</packageName>
|
|
528
|
-
<version>2.5.
|
|
528
|
+
<version>2.5.4</version>
|
|
529
529
|
<licenses>
|
|
530
530
|
<license>
|
|
531
531
|
<name>MIT</name>
|
|
@@ -709,7 +709,7 @@
|
|
|
709
709
|
</dependency>
|
|
710
710
|
<dependency>
|
|
711
711
|
<packageName>puma</packageName>
|
|
712
|
-
<version>
|
|
712
|
+
<version>4.3.7</version>
|
|
713
713
|
<licenses>
|
|
714
714
|
<license>
|
|
715
715
|
<name>New BSD</name>
|
|
@@ -769,7 +769,7 @@
|
|
|
769
769
|
</dependency>
|
|
770
770
|
<dependency>
|
|
771
771
|
<packageName>redis-namespace</packageName>
|
|
772
|
-
<version>1.
|
|
772
|
+
<version>1.8.0</version>
|
|
773
773
|
<licenses>
|
|
774
774
|
<license>
|
|
775
775
|
<name>MIT</name>
|
|
@@ -1043,7 +1043,7 @@
|
|
|
1043
1043
|
</dependency>
|
|
1044
1044
|
<dependency>
|
|
1045
1045
|
<packageName>timers</packageName>
|
|
1046
|
-
<version>4.3.
|
|
1046
|
+
<version>4.3.2</version>
|
|
1047
1047
|
<licenses>
|
|
1048
1048
|
<license>
|
|
1049
1049
|
<name>MIT</name>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: apisonator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.1.
|
|
4
|
+
version: 3.3.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Ciganek
|
|
@@ -16,7 +16,7 @@ authors:
|
|
|
16
16
|
autorequire:
|
|
17
17
|
bindir: bin
|
|
18
18
|
cert_chain: []
|
|
19
|
-
date:
|
|
19
|
+
date: 2021-02-12 00:00:00.000000000 Z
|
|
20
20
|
dependencies: []
|
|
21
21
|
description: This gem provides a daemon that handles authorization and reporting of
|
|
22
22
|
web services managed by 3scale.
|
|
@@ -136,11 +136,7 @@ files:
|
|
|
136
136
|
- lib/3scale/backend/stats/bucket_storage.rb
|
|
137
137
|
- lib/3scale/backend/stats/cleaner.rb
|
|
138
138
|
- lib/3scale/backend/stats/codes_commons.rb
|
|
139
|
-
- lib/3scale/backend/stats/delete_job_def.rb
|
|
140
|
-
- lib/3scale/backend/stats/key_generator.rb
|
|
141
139
|
- lib/3scale/backend/stats/keys.rb
|
|
142
|
-
- lib/3scale/backend/stats/partition_eraser_job.rb
|
|
143
|
-
- lib/3scale/backend/stats/partition_generator_job.rb
|
|
144
140
|
- lib/3scale/backend/stats/period_commons.rb
|
|
145
141
|
- lib/3scale/backend/stats/stats_parser.rb
|
|
146
142
|
- lib/3scale/backend/stats/storage.rb
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
module ThreeScale
|
|
2
|
-
module Backend
|
|
3
|
-
module Stats
|
|
4
|
-
class DeleteJobDef
|
|
5
|
-
ATTRIBUTES = %i[service_id applications metrics from to context_info].freeze
|
|
6
|
-
private_constant :ATTRIBUTES
|
|
7
|
-
attr_reader(*ATTRIBUTES)
|
|
8
|
-
|
|
9
|
-
def self.attribute_names
|
|
10
|
-
ATTRIBUTES
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def initialize(params = {})
|
|
14
|
-
ATTRIBUTES.each do |key|
|
|
15
|
-
instance_variable_set("@#{key}".to_sym, params[key]) unless params[key].nil?
|
|
16
|
-
end
|
|
17
|
-
validate
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def run_async
|
|
21
|
-
Resque.enqueue(PartitionGeneratorJob, Time.now.getutc.to_f, service_id, applications,
|
|
22
|
-
metrics, from, to, context_info)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def to_json
|
|
26
|
-
to_hash.to_json
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def to_hash
|
|
30
|
-
Hash[ATTRIBUTES.collect { |key| [key, send(key)] }]
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def validate
|
|
36
|
-
# from and to valid epoch times
|
|
37
|
-
raise_validation_error('from field not integer') unless from.is_a? Integer
|
|
38
|
-
raise_validation_error('from field is zero') if from.zero?
|
|
39
|
-
raise_validation_error('to field not integer') unless to.is_a? Integer
|
|
40
|
-
raise_validation_error('to field is zero') if to.zero?
|
|
41
|
-
raise_validation_error('from < to fields') if Time.at(to) < Time.at(from)
|
|
42
|
-
# application is array
|
|
43
|
-
raise_validation_error('applications field') unless applications.is_a? Array
|
|
44
|
-
raise_validation_error('applications values') unless applications.all? do |x|
|
|
45
|
-
x.is_a?(String) || x.is_a?(Integer)
|
|
46
|
-
end
|
|
47
|
-
# metrics is array
|
|
48
|
-
raise_validation_error('metrics field') unless metrics.is_a? Array
|
|
49
|
-
raise_validation_error('metrics values') unless metrics.all? do |x|
|
|
50
|
-
x.is_a?(String) || x.is_a?(Integer)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def raise_validation_error(msg)
|
|
55
|
-
raise DeleteServiceStatsValidationError.new(service_id, msg)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
module ThreeScale
|
|
2
|
-
module Backend
|
|
3
|
-
module Stats
|
|
4
|
-
class KeyGenerator
|
|
5
|
-
attr_reader :service_id, :applications, :metrics, :from, :to
|
|
6
|
-
|
|
7
|
-
def initialize(service_id:, applications: [], metrics: [], from:, to:, **)
|
|
8
|
-
@service_id = service_id
|
|
9
|
-
@applications = applications
|
|
10
|
-
@metrics = metrics
|
|
11
|
-
@from = from
|
|
12
|
-
@to = to
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def keys
|
|
16
|
-
response_code_service_keys +
|
|
17
|
-
response_code_application_keys +
|
|
18
|
-
usage_service_keys +
|
|
19
|
-
usage_application_keys
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def periods(granularities)
|
|
25
|
-
granularities.flat_map do |granularity|
|
|
26
|
-
(Period[granularity].new(Time.at(from))..Period[granularity].new(Time.at(to))).to_a
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def response_codes
|
|
31
|
-
CodesCommons::TRACKED_CODES + CodesCommons::TRACKED_CODE_GROUPS
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def response_code_service_keys
|
|
35
|
-
periods(PeriodCommons::PERMANENT_SERVICE_GRANULARITIES).flat_map do |period|
|
|
36
|
-
response_codes.flat_map do |response_code|
|
|
37
|
-
Keys.service_response_code_value_key(service_id, response_code, period)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def response_code_application_keys
|
|
43
|
-
periods(PeriodCommons::PERMANENT_EXPANDED_GRANULARITIES).flat_map do |period|
|
|
44
|
-
response_codes.flat_map do |response_code|
|
|
45
|
-
applications.flat_map do |application|
|
|
46
|
-
Keys.application_response_code_value_key(service_id, application,
|
|
47
|
-
response_code, period)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def usage_service_keys
|
|
54
|
-
periods(PeriodCommons::PERMANENT_SERVICE_GRANULARITIES).flat_map do |period|
|
|
55
|
-
metrics.flat_map do |metric|
|
|
56
|
-
Keys.service_usage_value_key(service_id, metric, period)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def usage_application_keys
|
|
62
|
-
periods(PeriodCommons::PERMANENT_EXPANDED_GRANULARITIES).flat_map do |period|
|
|
63
|
-
metrics.flat_map do |metric|
|
|
64
|
-
applications.flat_map do |application|
|
|
65
|
-
Keys.application_usage_value_key(service_id, application, metric, period)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
module ThreeScale
|
|
2
|
-
module Backend
|
|
3
|
-
module Stats
|
|
4
|
-
# Job for deleting service stats
|
|
5
|
-
# Perform actual key deletion from a key partition definition
|
|
6
|
-
class PartitionEraserJob < BackgroundJob
|
|
7
|
-
# low priority queue
|
|
8
|
-
@queue = :stats
|
|
9
|
-
|
|
10
|
-
class << self
|
|
11
|
-
include StorageHelpers
|
|
12
|
-
include Configurable
|
|
13
|
-
|
|
14
|
-
def perform_logged(_enqueue_time, service_id, applications, metrics,
|
|
15
|
-
from, to, offset, length, context_info = {})
|
|
16
|
-
job = DeleteJobDef.new(
|
|
17
|
-
service_id: service_id,
|
|
18
|
-
applications: applications,
|
|
19
|
-
metrics: metrics,
|
|
20
|
-
from: from,
|
|
21
|
-
to: to
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
validate_job(job, offset, length)
|
|
25
|
-
|
|
26
|
-
stats_key_gen = KeyGenerator.new(job.to_hash)
|
|
27
|
-
|
|
28
|
-
stats_key_gen.keys.drop(offset).take(length).each_slice(configuration.stats.delete_batch_size) do |slice|
|
|
29
|
-
storage.del(slice)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
[true, { job: job.to_hash, offset: offset, lenght: length }.to_json]
|
|
33
|
-
rescue Backend::Error => error
|
|
34
|
-
[false, "#{service_id} #{error}"]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def validate_job(job, offset, length)
|
|
40
|
-
unless offset.is_a? Integer
|
|
41
|
-
raise DeleteServiceStatsValidationError.new(job.service_id, 'offset field value ' \
|
|
42
|
-
"[#{offset}] validation error")
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
unless length.is_a? Integer
|
|
46
|
-
raise DeleteServiceStatsValidationError.new(job.service_id, 'length field value ' \
|
|
47
|
-
"[#{length}] validation error")
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def enqueue_time(args)
|
|
52
|
-
args[0]
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
module ThreeScale
|
|
2
|
-
module Backend
|
|
3
|
-
module Stats
|
|
4
|
-
# Job for deleting service stats
|
|
5
|
-
# Maps delete job definition to a set of non overlapping key set partitions
|
|
6
|
-
class PartitionGeneratorJob < BackgroundJob
|
|
7
|
-
# low priority queue
|
|
8
|
-
@queue = :stats
|
|
9
|
-
|
|
10
|
-
class << self
|
|
11
|
-
include Configurable
|
|
12
|
-
|
|
13
|
-
def perform_logged(_enqueue_time, service_id, applications, metrics,
|
|
14
|
-
from, to, context_info = {})
|
|
15
|
-
job = DeleteJobDef.new(
|
|
16
|
-
service_id: service_id,
|
|
17
|
-
applications: applications,
|
|
18
|
-
metrics: metrics,
|
|
19
|
-
from: from,
|
|
20
|
-
to: to
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
stats_key_gen = KeyGenerator.new(job.to_hash)
|
|
24
|
-
|
|
25
|
-
# Generate partitions
|
|
26
|
-
0.step(stats_key_gen.keys.count, configuration.stats.delete_partition_batch_size).each do |idx|
|
|
27
|
-
Resque.enqueue(PartitionEraserJob, Time.now.getutc.to_f, service_id, applications,
|
|
28
|
-
metrics, from, to, idx,
|
|
29
|
-
configuration.stats.delete_partition_batch_size, context_info)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
[true, job.to_json]
|
|
33
|
-
rescue Backend::Error => error
|
|
34
|
-
[false, "#{service_id} #{error}"]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def enqueue_time(args)
|
|
40
|
-
args[0]
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|