apisonator 2.100.0 → 2.101.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/Gemfile.base +6 -3
- data/Gemfile.lock +17 -25
- data/Gemfile.on_prem.lock +17 -25
- data/lib/3scale/backend.rb +0 -1
- data/lib/3scale/backend/application.rb +18 -0
- data/lib/3scale/backend/configuration.rb +2 -2
- data/lib/3scale/backend/listener.rb +43 -43
- data/lib/3scale/backend/metric/collection.rb +1 -4
- data/lib/3scale/backend/storage_async/client.rb +3 -1
- data/lib/3scale/backend/storage_helpers.rb +14 -5
- data/lib/3scale/backend/transactor.rb +26 -1
- data/lib/3scale/backend/transactor/status.rb +0 -5
- data/lib/3scale/backend/usage_limit.rb +20 -11
- data/lib/3scale/backend/version.rb +1 -1
- data/lib/3scale/backend/worker_metrics.rb +2 -1
- data/lib/3scale/tasks/swagger.rake +11 -29
- data/licenses.xml +8 -68
- metadata +2 -3
- data/lib/3scale/backend/extensions/redis.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4740b1613c8bd2182adf9587db2a2ae6667a50ee140c99c679c232b6da3b7193
|
4
|
+
data.tar.gz: 0e34376e2e788194b44eeb5aedf7d8654110c8baa4a0885ed2c55a3a28fcb0c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6431c06f07433710308ef8b9121d2975415cbec27d2c82db3c8f13c4a0dab364b6a9085a550a530bd000a18234bfd63d67ee555ea69299f8745734725f9f436
|
7
|
+
data.tar.gz: 90618d349784bfe5ff9d78b940f93d7b63013fb9f1ec1b9c68f03881ff9e13a165d0532a98d50c26218402cc197c3f9f730f7b34ca26589cb8f4b91fa37e6cab
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,46 @@
|
|
2
2
|
|
3
3
|
Notable changes to Apisonator will be tracked in this document.
|
4
4
|
|
5
|
+
## 2.101.0 - 2020-06-04
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Introduced the `CONFIG_REDIS_MAX_CONNS` and `CONFIG_QUEUES_MAX_CONNS` ENVs to
|
10
|
+
configure the max number of Redis connections when using the async mode
|
11
|
+
([#214](https://github.com/3scale/apisonator/pull/214)).
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
|
15
|
+
- Perf optimization: loading the usage limits is now done more efficiently.
|
16
|
+
There is a noticeable improvement in requests with `no_body` enabled for
|
17
|
+
services with many metrics defined
|
18
|
+
([#221](https://github.com/3scale/apisonator/pull/221)).
|
19
|
+
- Updated activesupport to 5.2.4.3
|
20
|
+
([#217](https://github.com/3scale/apisonator/pull/217)).
|
21
|
+
|
22
|
+
|
23
|
+
## 2.100.2 - 2020-05-08
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
|
27
|
+
- The Prometheus histogram buckets of the workers have been adjusted to be more
|
28
|
+
informative ([#208](https://github.com/3scale/apisonator/pull/208)).
|
29
|
+
|
30
|
+
### Removed
|
31
|
+
|
32
|
+
- The deprecated endpoints to create, delete, and list oauth tokens have been
|
33
|
+
disabled ([#212](https://github.com/3scale/apisonator/pull/212)).
|
34
|
+
|
35
|
+
|
36
|
+
## 2.100.1 - 2020-04-22
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
|
40
|
+
- Now we are using our own redis-rb fork. It includes a fix that should reduce
|
41
|
+
the number of 5xx errors caused by Redis connection errors
|
42
|
+
([#205](https://github.com/3scale/apisonator/pull/205)).
|
43
|
+
- Updated hiredis to v0.6.3 ([#204](https://github.com/3scale/apisonator/pull/204)).
|
44
|
+
|
5
45
|
## 2.100.0 - 2020-04-21
|
6
46
|
|
7
47
|
### Added
|
data/Gemfile.base
CHANGED
@@ -9,7 +9,7 @@ gemspec
|
|
9
9
|
# implementations (ie. pure Ruby, java, etc).
|
10
10
|
#
|
11
11
|
platform :ruby do
|
12
|
-
gem 'hiredis', '
|
12
|
+
gem 'hiredis', '~> 0.6.1'
|
13
13
|
gem 'yajl-ruby', '~> 1.3.1', require: 'yajl'
|
14
14
|
gem 'pry-byebug', '~> 3.5.1', groups: [:development]
|
15
15
|
end
|
@@ -25,7 +25,6 @@ group :test do
|
|
25
25
|
gem 'resque_spec', '~> 0.17.0'
|
26
26
|
gem 'timecop', '~> 0.9.1'
|
27
27
|
gem 'rspec', '~> 3.7.0', require: nil
|
28
|
-
gem 'geminabox', '~> 0.13.11', require: false
|
29
28
|
gem 'codeclimate-test-reporter', '~> 0.6.0', require: nil
|
30
29
|
gem 'async-rspec'
|
31
30
|
end
|
@@ -52,7 +51,6 @@ gem 'daemons', '= 1.2.4'
|
|
52
51
|
# Production gems
|
53
52
|
gem 'rake', '~> 13.0'
|
54
53
|
gem 'builder', '= 3.2.3'
|
55
|
-
gem 'redis', '= 4.1.1'
|
56
54
|
# Use a patched resque to allow reusing their Airbrake Failure class
|
57
55
|
gem 'resque', git: 'https://github.com/3scale/resque', branch: '3scale'
|
58
56
|
gem 'rack', '~> 2.0.8'
|
@@ -63,3 +61,8 @@ gem 'bugsnag', '~> 6', require: nil
|
|
63
61
|
gem 'yabeda-prometheus', '~> 0.5.0'
|
64
62
|
gem 'async-redis', '~> 0.4.1'
|
65
63
|
gem 'falcon', '~> 0.35'
|
64
|
+
|
65
|
+
# Use a patched redis-rb that fixes an issue when trying to connect with
|
66
|
+
# sentinels and avoids retrying calls when there's a timeout to prevent
|
67
|
+
# duplicated commands. It's based on version 4.1.3.
|
68
|
+
gem 'redis', git: 'https://github.com/3scale/redis-rb', branch: 'apisonator'
|
data/Gemfile.lock
CHANGED
@@ -5,6 +5,13 @@ GIT
|
|
5
5
|
specs:
|
6
6
|
puma (2.15.3)
|
7
7
|
|
8
|
+
GIT
|
9
|
+
remote: https://github.com/3scale/redis-rb
|
10
|
+
revision: 35301b3d952975300a4cb30d408ae3b5969d0440
|
11
|
+
branch: apisonator
|
12
|
+
specs:
|
13
|
+
redis (4.1.3)
|
14
|
+
|
8
15
|
GIT
|
9
16
|
remote: https://github.com/3scale/resque
|
10
17
|
revision: 88839e71756ea9b6edfc9426a0af71e94109c135
|
@@ -28,14 +35,14 @@ GIT
|
|
28
35
|
PATH
|
29
36
|
remote: .
|
30
37
|
specs:
|
31
|
-
apisonator (2.
|
38
|
+
apisonator (2.101.0)
|
32
39
|
|
33
40
|
GEM
|
34
41
|
remote: https://rubygems.org/
|
35
42
|
specs:
|
36
|
-
activesupport (5.
|
43
|
+
activesupport (5.2.4.3)
|
37
44
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
38
|
-
i18n (
|
45
|
+
i18n (>= 0.7, < 2)
|
39
46
|
minitest (~> 5.1)
|
40
47
|
tzinfo (~> 1.1)
|
41
48
|
airbrake (4.3.1)
|
@@ -88,7 +95,7 @@ GEM
|
|
88
95
|
codeclimate-test-reporter (0.6.0)
|
89
96
|
simplecov (>= 0.7.1, < 1.0.0)
|
90
97
|
coderay (1.1.2)
|
91
|
-
concurrent-ruby (1.
|
98
|
+
concurrent-ruby (1.1.6)
|
92
99
|
console (1.8.2)
|
93
100
|
daemons (1.2.4)
|
94
101
|
diff-lcs (1.3)
|
@@ -105,20 +112,10 @@ GEM
|
|
105
112
|
process-metrics (~> 0.1.0)
|
106
113
|
rack (>= 1.0)
|
107
114
|
samovar (~> 2.1)
|
108
|
-
faraday (0.13.1)
|
109
|
-
multipart-post (>= 1.2, < 3)
|
110
115
|
ffi (1.12.2)
|
111
|
-
geminabox (0.13.11)
|
112
|
-
builder
|
113
|
-
faraday
|
114
|
-
httpclient (>= 2.2.7)
|
115
|
-
nesty
|
116
|
-
reentrant_flock
|
117
|
-
sinatra (>= 1.2.7)
|
118
116
|
gli (2.16.1)
|
119
|
-
hiredis (0.6.
|
120
|
-
|
121
|
-
i18n (0.9.1)
|
117
|
+
hiredis (0.6.3)
|
118
|
+
i18n (1.8.2)
|
122
119
|
concurrent-ruby (~> 1.0)
|
123
120
|
jmespath (1.3.1)
|
124
121
|
json (2.1.0)
|
@@ -134,15 +131,13 @@ GEM
|
|
134
131
|
metaclass (0.0.4)
|
135
132
|
method_source (0.9.0)
|
136
133
|
mini_portile2 (2.4.0)
|
137
|
-
minitest (5.
|
134
|
+
minitest (5.14.1)
|
138
135
|
mocha (1.3.0)
|
139
136
|
metaclass (~> 0.0.1)
|
140
137
|
mono_logger (1.1.0)
|
141
138
|
multi_json (1.13.1)
|
142
|
-
multipart-post (2.0.0)
|
143
139
|
mustache (1.0.5)
|
144
140
|
mustermann (1.0.2)
|
145
|
-
nesty (1.0.2)
|
146
141
|
net-scp (1.2.1)
|
147
142
|
net-ssh (>= 2.6.5)
|
148
143
|
net-ssh (4.2.0)
|
@@ -182,10 +177,8 @@ GEM
|
|
182
177
|
rack-test (0.8.2)
|
183
178
|
rack (>= 1.0, < 3)
|
184
179
|
rake (13.0.1)
|
185
|
-
redis (4.1.1)
|
186
180
|
redis-namespace (1.6.0)
|
187
181
|
redis (>= 3.0.4)
|
188
|
-
reentrant_flock (0.1.1)
|
189
182
|
resque_spec (0.17.0)
|
190
183
|
resque (>= 1.19.0)
|
191
184
|
rspec-core (>= 3.0.0)
|
@@ -250,7 +243,7 @@ GEM
|
|
250
243
|
timers (4.3.0)
|
251
244
|
toml (0.2.0)
|
252
245
|
parslet (~> 1.8.0)
|
253
|
-
tzinfo (1.2.
|
246
|
+
tzinfo (1.2.7)
|
254
247
|
thread_safe (~> 0.1)
|
255
248
|
vegas (0.1.11)
|
256
249
|
rack (>= 1.0.0)
|
@@ -282,9 +275,8 @@ DEPENDENCIES
|
|
282
275
|
codeclimate-test-reporter (~> 0.6.0)
|
283
276
|
daemons (= 1.2.4)
|
284
277
|
falcon (~> 0.35)
|
285
|
-
geminabox (~> 0.13.11)
|
286
278
|
gli (~> 2.16.1)
|
287
|
-
hiredis (
|
279
|
+
hiredis (~> 0.6.1)
|
288
280
|
license_finder (~> 5)
|
289
281
|
mocha (~> 1.3)
|
290
282
|
nokogiri (~> 1.10.8)
|
@@ -297,7 +289,7 @@ DEPENDENCIES
|
|
297
289
|
rack (~> 2.0.8)
|
298
290
|
rack-test (~> 0.8.2)
|
299
291
|
rake (~> 13.0)
|
300
|
-
redis
|
292
|
+
redis!
|
301
293
|
resque!
|
302
294
|
resque_spec (~> 0.17.0)
|
303
295
|
resque_unit (~> 0.4.4)!
|
data/Gemfile.on_prem.lock
CHANGED
@@ -5,6 +5,13 @@ GIT
|
|
5
5
|
specs:
|
6
6
|
puma (2.15.3)
|
7
7
|
|
8
|
+
GIT
|
9
|
+
remote: https://github.com/3scale/redis-rb
|
10
|
+
revision: 35301b3d952975300a4cb30d408ae3b5969d0440
|
11
|
+
branch: apisonator
|
12
|
+
specs:
|
13
|
+
redis (4.1.3)
|
14
|
+
|
8
15
|
GIT
|
9
16
|
remote: https://github.com/3scale/resque
|
10
17
|
revision: 88839e71756ea9b6edfc9426a0af71e94109c135
|
@@ -28,14 +35,14 @@ GIT
|
|
28
35
|
PATH
|
29
36
|
remote: .
|
30
37
|
specs:
|
31
|
-
apisonator (2.
|
38
|
+
apisonator (2.101.0)
|
32
39
|
|
33
40
|
GEM
|
34
41
|
remote: https://rubygems.org/
|
35
42
|
specs:
|
36
|
-
activesupport (5.
|
43
|
+
activesupport (5.2.4.3)
|
37
44
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
38
|
-
i18n (
|
45
|
+
i18n (>= 0.7, < 2)
|
39
46
|
minitest (~> 5.1)
|
40
47
|
tzinfo (~> 1.1)
|
41
48
|
async (1.24.2)
|
@@ -78,7 +85,7 @@ GEM
|
|
78
85
|
codeclimate-test-reporter (0.6.0)
|
79
86
|
simplecov (>= 0.7.1, < 1.0.0)
|
80
87
|
coderay (1.1.2)
|
81
|
-
concurrent-ruby (1.
|
88
|
+
concurrent-ruby (1.1.6)
|
82
89
|
console (1.8.2)
|
83
90
|
daemons (1.2.4)
|
84
91
|
diff-lcs (1.3)
|
@@ -95,20 +102,10 @@ GEM
|
|
95
102
|
process-metrics (~> 0.1.0)
|
96
103
|
rack (>= 1.0)
|
97
104
|
samovar (~> 2.1)
|
98
|
-
faraday (0.13.1)
|
99
|
-
multipart-post (>= 1.2, < 3)
|
100
105
|
ffi (1.12.2)
|
101
|
-
geminabox (0.13.11)
|
102
|
-
builder
|
103
|
-
faraday
|
104
|
-
httpclient (>= 2.2.7)
|
105
|
-
nesty
|
106
|
-
reentrant_flock
|
107
|
-
sinatra (>= 1.2.7)
|
108
106
|
gli (2.16.1)
|
109
|
-
hiredis (0.6.
|
110
|
-
|
111
|
-
i18n (0.9.1)
|
107
|
+
hiredis (0.6.3)
|
108
|
+
i18n (1.8.2)
|
112
109
|
concurrent-ruby (~> 1.0)
|
113
110
|
json (2.1.0)
|
114
111
|
license_finder (5.9.2)
|
@@ -123,15 +120,13 @@ GEM
|
|
123
120
|
metaclass (0.0.4)
|
124
121
|
method_source (0.9.0)
|
125
122
|
mini_portile2 (2.4.0)
|
126
|
-
minitest (5.
|
123
|
+
minitest (5.14.1)
|
127
124
|
mocha (1.3.0)
|
128
125
|
metaclass (~> 0.0.1)
|
129
126
|
mono_logger (1.1.0)
|
130
127
|
multi_json (1.13.1)
|
131
|
-
multipart-post (2.0.0)
|
132
128
|
mustache (1.0.5)
|
133
129
|
mustermann (1.0.2)
|
134
|
-
nesty (1.0.2)
|
135
130
|
net-scp (1.2.1)
|
136
131
|
net-ssh (>= 2.6.5)
|
137
132
|
net-ssh (4.2.0)
|
@@ -170,10 +165,8 @@ GEM
|
|
170
165
|
rack-test (0.8.2)
|
171
166
|
rack (>= 1.0, < 3)
|
172
167
|
rake (13.0.1)
|
173
|
-
redis (4.1.1)
|
174
168
|
redis-namespace (1.6.0)
|
175
169
|
redis (>= 3.0.4)
|
176
|
-
reentrant_flock (0.1.1)
|
177
170
|
resque_spec (0.17.0)
|
178
171
|
resque (>= 1.19.0)
|
179
172
|
rspec-core (>= 3.0.0)
|
@@ -236,7 +229,7 @@ GEM
|
|
236
229
|
timers (4.3.0)
|
237
230
|
toml (0.2.0)
|
238
231
|
parslet (~> 1.8.0)
|
239
|
-
tzinfo (1.2.
|
232
|
+
tzinfo (1.2.7)
|
240
233
|
thread_safe (~> 0.1)
|
241
234
|
vegas (0.1.11)
|
242
235
|
rack (>= 1.0.0)
|
@@ -264,9 +257,8 @@ DEPENDENCIES
|
|
264
257
|
codeclimate-test-reporter (~> 0.6.0)
|
265
258
|
daemons (= 1.2.4)
|
266
259
|
falcon (~> 0.35)
|
267
|
-
geminabox (~> 0.13.11)
|
268
260
|
gli (~> 2.16.1)
|
269
|
-
hiredis (
|
261
|
+
hiredis (~> 0.6.1)
|
270
262
|
license_finder (~> 5)
|
271
263
|
mocha (~> 1.3)
|
272
264
|
nokogiri (~> 1.10.8)
|
@@ -278,7 +270,7 @@ DEPENDENCIES
|
|
278
270
|
rack (~> 2.0.8)
|
279
271
|
rack-test (~> 0.8.2)
|
280
272
|
rake (~> 13.0)
|
281
|
-
redis
|
273
|
+
redis!
|
282
274
|
resque!
|
283
275
|
resque_spec (~> 0.17.0)
|
284
276
|
resque_unit (~> 0.4.4)!
|
data/lib/3scale/backend.rb
CHANGED
@@ -223,6 +223,24 @@ module ThreeScale
|
|
223
223
|
@usage_limits ||= UsageLimit.load_all(service_id, plan_id)
|
224
224
|
end
|
225
225
|
|
226
|
+
def load_all_usage_limits
|
227
|
+
@usage_limits = UsageLimit.load_all(service_id, plan_id)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Loads the usage limits affected by the metrics received, that is, the
|
231
|
+
# limits that are defined for those metrics plus all their ancestors in
|
232
|
+
# the metrics hierarchy.
|
233
|
+
def load_usage_limits_affected_by(metric_names)
|
234
|
+
metric_ids = metric_names.flat_map do |name|
|
235
|
+
[name] + Metric.ascendants(service_id, name)
|
236
|
+
end.uniq.map do |name|
|
237
|
+
Metric.load_id(service_id, name)
|
238
|
+
end
|
239
|
+
|
240
|
+
# IDs are sorted to be able to use the memoizer
|
241
|
+
@usage_limits = UsageLimit.load_for_affecting_metrics(service_id, plan_id, metric_ids.sort)
|
242
|
+
end
|
243
|
+
|
226
244
|
def active?
|
227
245
|
state == :active
|
228
246
|
end
|
@@ -46,9 +46,9 @@ module ThreeScale
|
|
46
46
|
|
47
47
|
# Add configuration sections
|
48
48
|
config.add_section(:queues, :master_name, :sentinels, :role,
|
49
|
-
:connect_timeout, :read_timeout, :write_timeout)
|
49
|
+
:connect_timeout, :read_timeout, :write_timeout, :max_connections)
|
50
50
|
config.add_section(:redis, :url, :proxy, :sentinels, :role,
|
51
|
-
:connect_timeout, :read_timeout, :write_timeout,
|
51
|
+
:connect_timeout, :read_timeout, :write_timeout, :max_connections,
|
52
52
|
:async)
|
53
53
|
config.add_section(:analytics_redis, :server,
|
54
54
|
:connect_timeout, :read_timeout, :write_timeout)
|
@@ -12,9 +12,9 @@ module ThreeScale
|
|
12
12
|
include Logging
|
13
13
|
|
14
14
|
## ------------ DOCS --------------
|
15
|
-
##~ namespace =
|
15
|
+
##~ namespace = "Service Management API"
|
16
16
|
##~ sapi = source2swagger.namespace(namespace)
|
17
|
-
##~ sapi.basePath = "
|
17
|
+
##~ sapi.basePath = ""
|
18
18
|
##~ sapi.swaggerVersion = "0.1a"
|
19
19
|
##~ sapi.apiVersion = "1.0"
|
20
20
|
##
|
@@ -190,7 +190,7 @@ module ThreeScale
|
|
190
190
|
private :do_api_method
|
191
191
|
|
192
192
|
## ------------ DOCS --------------
|
193
|
-
##~ namespace =
|
193
|
+
##~ namespace = "Service Management API"
|
194
194
|
##~ sapi = source2swagger.namespace(namespace)
|
195
195
|
##~ a = sapi.apis.add
|
196
196
|
##~ a.set "path" => "/transactions/authorize.xml", "format" => "xml"
|
@@ -243,7 +243,7 @@ module ThreeScale
|
|
243
243
|
end
|
244
244
|
|
245
245
|
## ------------ DOCS --------------
|
246
|
-
##~ namespace =
|
246
|
+
##~ namespace = "Service Management API"
|
247
247
|
##~ sapi = source2swagger.namespace(namespace)
|
248
248
|
##~ a = sapi.apis.add
|
249
249
|
##~ a.set "path" => "/transactions/oauth_authorize.xml", "format" => "xml"
|
@@ -276,7 +276,7 @@ module ThreeScale
|
|
276
276
|
end
|
277
277
|
|
278
278
|
## ------------ DOCS --------------
|
279
|
-
##~ namespace =
|
279
|
+
##~ namespace = "Service Management API"
|
280
280
|
##~ sapi = source2swagger.namespace(namespace)
|
281
281
|
##~ a = sapi.apis.add
|
282
282
|
##~ a.set "path" => "/transactions/authrep.xml", "format" => "xml"
|
@@ -323,7 +323,7 @@ module ThreeScale
|
|
323
323
|
end
|
324
324
|
|
325
325
|
## ------------ DOCS --------------
|
326
|
-
##~ namespace =
|
326
|
+
##~ namespace = "Service Management API"
|
327
327
|
##~ sapi = source2swagger.namespace(namespace)
|
328
328
|
##~ a = sapi.apis.add
|
329
329
|
##~ a.set "path" => "/transactions/oauth_authrep.xml", "format" => "xml"
|
@@ -351,7 +351,7 @@ module ThreeScale
|
|
351
351
|
end
|
352
352
|
|
353
353
|
## ------------ DOCS --------------
|
354
|
-
##~ namespace =
|
354
|
+
##~ namespace = "Service Management API"
|
355
355
|
##~ sapi = source2swagger.namespace(namespace)
|
356
356
|
##~ a = sapi.apis.add
|
357
357
|
##~ a.set "path" => "/transactions.xml", "format" => "xml"
|
@@ -432,54 +432,58 @@ module ThreeScale
|
|
432
432
|
|
433
433
|
## OAUTH ACCESS TOKENS
|
434
434
|
|
435
|
-
|
436
|
-
|
437
|
-
|
435
|
+
# These endpoints are deprecated and are going to be removed. For now,
|
436
|
+
# let's disable them.
|
437
|
+
if Backend.test?
|
438
|
+
post '/services/:service_id/oauth_access_tokens.xml' do
|
439
|
+
check_post_content_type!
|
440
|
+
require_params! :service_id, :token
|
438
441
|
|
439
|
-
|
440
|
-
|
442
|
+
service_id = params[:service_id]
|
443
|
+
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
441
444
|
|
442
|
-
|
443
|
-
|
445
|
+
app_id = params[:app_id]
|
446
|
+
raise ApplicationNotFound, app_id unless Application.exists?(service_id, app_id)
|
444
447
|
|
445
|
-
|
446
|
-
|
448
|
+
OAuth::Token::Storage.create(params[:token], service_id, app_id, params[:ttl])
|
449
|
+
end
|
447
450
|
|
448
|
-
|
449
|
-
|
451
|
+
delete '/services/:service_id/oauth_access_tokens/:token.xml' do
|
452
|
+
require_params! :service_id, :token
|
450
453
|
|
451
|
-
|
452
|
-
|
454
|
+
service_id = params[:service_id]
|
455
|
+
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
453
456
|
|
454
|
-
|
457
|
+
token = params[:token]
|
455
458
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
+
# TODO: perhaps improve this to list the deleted tokens?
|
460
|
+
raise AccessTokenInvalid, token unless OAuth::Token::Storage.delete(token, service_id)
|
461
|
+
end
|
459
462
|
|
460
|
-
|
461
|
-
|
463
|
+
get '/services/:service_id/applications/:app_id/oauth_access_tokens.xml' do
|
464
|
+
require_params! :service_id, :app_id
|
462
465
|
|
463
|
-
|
464
|
-
|
466
|
+
service_id = params[:service_id]
|
467
|
+
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
465
468
|
|
466
|
-
|
469
|
+
app_id = params[:app_id]
|
467
470
|
|
468
|
-
|
471
|
+
raise ApplicationNotFound, app_id unless Application.exists?(service_id, app_id)
|
469
472
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
+
@tokens = OAuth::Token::Storage.all_by_service_and_app service_id, app_id
|
474
|
+
builder :oauth_access_tokens
|
475
|
+
end
|
473
476
|
|
474
|
-
|
475
|
-
|
477
|
+
get '/services/:service_id/oauth_access_tokens/:token.xml' do
|
478
|
+
require_params! :service_id, :token
|
476
479
|
|
477
|
-
|
478
|
-
|
480
|
+
service_id = params[:service_id]
|
481
|
+
ensure_authenticated!(params[:provider_key], params[:service_token], service_id)
|
479
482
|
|
480
|
-
|
483
|
+
@token_to_app_id = OAuth::Token::Storage.get_credentials(params[:token], service_id)
|
481
484
|
|
482
|
-
|
485
|
+
builder :oauth_app_id_by_token
|
486
|
+
end
|
483
487
|
end
|
484
488
|
|
485
489
|
get '/check.txt' do
|
@@ -600,10 +604,6 @@ module ThreeScale
|
|
600
604
|
end
|
601
605
|
end
|
602
606
|
|
603
|
-
def application
|
604
|
-
@application ||= Application.load_by_id_or_user_key!(service_id, params[:app_id], params[:user_key])
|
605
|
-
end
|
606
|
-
|
607
607
|
def service_id
|
608
608
|
if params[:service_id].nil? || params[:service_id].empty?
|
609
609
|
@service_id ||= Service.default_id!(params[:provider_key])
|
@@ -75,10 +75,7 @@ module ThreeScale
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def load_metric_id(name)
|
78
|
-
|
79
|
-
:load_metric_id, @service_id, name)) do
|
80
|
-
storage.get(encode_key("metric/service_id:#{@service_id}/name:#{name}/id"))
|
81
|
-
end || raise(MetricInvalid.new(name))
|
78
|
+
Metric.load_id(@service_id, name) || raise(MetricInvalid.new(name))
|
82
79
|
end
|
83
80
|
|
84
81
|
## accepts postive integers or positive integers preffixed with # (for sets)
|
@@ -46,7 +46,9 @@ module ThreeScale
|
|
46
46
|
port ||= DEFAULT_PORT
|
47
47
|
|
48
48
|
endpoint = Async::IO::Endpoint.tcp(host, port)
|
49
|
-
@redis_async = Async::Redis::Client.new(
|
49
|
+
@redis_async = Async::Redis::Client.new(
|
50
|
+
endpoint, connection_limit: opts[:max_connections]
|
51
|
+
)
|
50
52
|
@building_pipeline = false
|
51
53
|
end
|
52
54
|
|
@@ -44,17 +44,26 @@ module ThreeScale
|
|
44
44
|
connect_timeout: 5,
|
45
45
|
read_timeout: 3,
|
46
46
|
write_timeout: 3,
|
47
|
-
#
|
48
|
-
#
|
49
|
-
|
47
|
+
# Note that we can set reconnect_attempts to >= 0 because we use
|
48
|
+
# our redis-rb fork which implements a workaround for this issue
|
49
|
+
# that shows that when there might be duplicated transactions when
|
50
|
+
# there's a timeout: https://github.com/redis/redis-rb/issues/668
|
51
|
+
# We should investigate if there are edge cases that can lead to
|
52
|
+
# duplicated commands because of this setting.
|
53
|
+
reconnect_attempts: 1,
|
50
54
|
# use by default the C extension client
|
51
|
-
driver: :hiredis
|
55
|
+
driver: :hiredis,
|
56
|
+
# applies only to async mode. The sync library opens 1 connection
|
57
|
+
# per process.
|
58
|
+
max_connections: 10,
|
52
59
|
}.freeze
|
53
60
|
private_constant :CONN_OPTIONS
|
54
61
|
|
55
62
|
# CONN_WHITELIST - Connection options that can be specified in config
|
56
63
|
# Note: we don't expose reconnect_attempts until the bug above is fixed
|
57
|
-
CONN_WHITELIST = [
|
64
|
+
CONN_WHITELIST = [
|
65
|
+
:connect_timeout, :read_timeout, :write_timeout, :max_connections
|
66
|
+
].freeze
|
58
67
|
private_constant :CONN_WHITELIST
|
59
68
|
|
60
69
|
# Parameters regarding target server we will take from a config object
|
@@ -87,9 +87,13 @@ module ThreeScale
|
|
87
87
|
application = Application.load_by_id_or_user_key!(service_id,
|
88
88
|
app_id,
|
89
89
|
params[:user_key])
|
90
|
+
|
91
|
+
extensions = request_info && request_info[:extensions] || {}
|
92
|
+
|
93
|
+
preload_usage_limits(application, extensions[:no_body], params[:usage])
|
94
|
+
|
90
95
|
now = Time.now.getutc
|
91
96
|
usage_values = Usage.application_usage(application, now)
|
92
|
-
extensions = request_info && request_info[:extensions] || {}
|
93
97
|
status_attrs = {
|
94
98
|
service_id: service_id,
|
95
99
|
application: application,
|
@@ -169,6 +173,27 @@ module ThreeScale
|
|
169
173
|
Resque.enqueue(ReportJob, service_id, data, Time.now.getutc.to_f, context_info)
|
170
174
|
end
|
171
175
|
|
176
|
+
# Loads the usage limits that are needed to authorize the current request.
|
177
|
+
# These are the cases:
|
178
|
+
# - When no_body is enabled, we only need to load the limits related with
|
179
|
+
# the metrics included in the request, that is, the ones included plus all
|
180
|
+
# their ancestors in the hierarchy. That's all we need to verify if the
|
181
|
+
# request is within the limits defined.
|
182
|
+
# - When no_body is disabled, we need to load all the limits because they
|
183
|
+
# are needed to generate the XML response.
|
184
|
+
# - When the usage reported in the request is empty, apisonator returns
|
185
|
+
# "limits_exceeded" if any of the limits defined has been violated. That's
|
186
|
+
# why in that scenario we need to load all the limits.
|
187
|
+
def preload_usage_limits(application, no_body_enabled, usage_in_params)
|
188
|
+
metric_names_in_usage = usage_in_params&.keys || {}
|
189
|
+
|
190
|
+
if no_body_enabled && !metric_names_in_usage.empty?
|
191
|
+
application.load_usage_limits_affected_by(metric_names_in_usage)
|
192
|
+
else
|
193
|
+
application.load_all_usage_limits
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
172
197
|
def storage
|
173
198
|
Storage.instance
|
174
199
|
end
|
@@ -80,11 +80,6 @@ module ThreeScale
|
|
80
80
|
values && values[usage_limit.metric_id] || 0
|
81
81
|
end
|
82
82
|
|
83
|
-
def value_for_application_usage_limit(usage_limit)
|
84
|
-
values = @values[usage_limit.period]
|
85
|
-
values && values[usage_limit.metric_id] || 0
|
86
|
-
end
|
87
|
-
|
88
83
|
# provides a hierarchy hash with metrics as symbolic names
|
89
84
|
def hierarchy
|
90
85
|
@hierarchy ||= Metric.hierarchy service_id
|
@@ -22,20 +22,15 @@ module ThreeScale
|
|
22
22
|
|
23
23
|
def load_all(service_id, plan_id)
|
24
24
|
metric_ids = Metric.load_all_ids(service_id)
|
25
|
-
|
26
|
-
|
27
|
-
results = []
|
28
|
-
with_pairs_and_values service_id, plan_id, metric_ids do |pair, value|
|
29
|
-
value and results << new(service_id: service_id,
|
30
|
-
plan_id: plan_id,
|
31
|
-
metric_id: pair[0],
|
32
|
-
period: pair[1],
|
33
|
-
value: value.to_i)
|
34
|
-
end
|
35
|
-
results
|
25
|
+
generate_for_metrics(service_id, plan_id, metric_ids)
|
36
26
|
end
|
37
27
|
memoize :load_all
|
38
28
|
|
29
|
+
def load_for_affecting_metrics(service_id, plan_id, metric_ids)
|
30
|
+
generate_for_metrics(service_id, plan_id, metric_ids)
|
31
|
+
end
|
32
|
+
memoize :load_for_affecting_metrics
|
33
|
+
|
39
34
|
def load_value(service_id, plan_id, metric_id, period)
|
40
35
|
raw_value = storage.get(key(service_id, plan_id, metric_id, period))
|
41
36
|
raw_value and raw_value.to_i
|
@@ -80,6 +75,20 @@ module ThreeScale
|
|
80
75
|
encode_key(key_pre + period.to_s)
|
81
76
|
end
|
82
77
|
|
78
|
+
def generate_for_metrics(service_id, plan_id, metric_ids)
|
79
|
+
return metric_ids if metric_ids.empty?
|
80
|
+
|
81
|
+
results = []
|
82
|
+
with_pairs_and_values service_id, plan_id, metric_ids do |pair, value|
|
83
|
+
value and results << new(service_id: service_id,
|
84
|
+
plan_id: plan_id,
|
85
|
+
metric_id: pair[0],
|
86
|
+
period: pair[1],
|
87
|
+
value: value.to_i)
|
88
|
+
end
|
89
|
+
results
|
90
|
+
end
|
91
|
+
|
83
92
|
# yields [pair(metric_id, period), value]
|
84
93
|
def with_pairs_and_values(service_id, plan_id, metric_ids, &blk)
|
85
94
|
pairs, values = get_pairs_and_values_for service_id, plan_id, metric_ids
|
@@ -11,7 +11,8 @@ Yabeda.configure do
|
|
11
11
|
comment "How long jobs take to run"
|
12
12
|
unit :seconds
|
13
13
|
tags %i[type]
|
14
|
-
|
14
|
+
# Most requests will be under 100ms, so use a higher granularity from there
|
15
|
+
buckets [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.25, 0.5, 0.75, 1]
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -1,46 +1,28 @@
|
|
1
|
+
SPEC_FILE = 'docs/active_docs/Service Management API.json'
|
2
|
+
|
1
3
|
def run_command cmd
|
2
4
|
puts "--> executing: #{cmd}"
|
3
5
|
puts "--> Remember to commit the resulting specs to the Porta repo."
|
4
6
|
system cmd
|
5
7
|
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
res = 'docs/active_docs/Service Management API'
|
10
|
-
res << ' (on-premises)' if type == :on_prem
|
11
|
-
"#{res}.json"
|
12
|
-
end
|
13
|
-
|
14
|
-
def generate_docs(type)
|
15
|
-
spec = spec_file(type)
|
16
|
-
cmd = type == :saas ? 'SAAS_SWAGGER=1' : 'SAAS_SWAGGER=0'
|
17
|
-
cmd << ' bundle exec source2swagger -f lib/3scale/backend/listener.rb -c "##~" -o docs/active_docs/.'
|
9
|
+
def generate_docs
|
10
|
+
cmd = 'bundle exec source2swagger -f lib/3scale/backend/listener.rb -c "##~" -o docs/active_docs/.'
|
18
11
|
run_command cmd
|
19
12
|
# source2swagger is really bad at generating readable JSON
|
20
13
|
require 'json'
|
21
|
-
File.write(
|
14
|
+
File.write(SPEC_FILE, JSON.pretty_generate(JSON.parse(File.read(SPEC_FILE))) << "\n")
|
22
15
|
end
|
23
16
|
|
24
17
|
namespace :docs do
|
25
18
|
namespace :swagger do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# (Service Management API (on-premises).json and Service Management
|
30
|
-
# API.json)
|
31
|
-
|
32
|
-
desc "Generates all swagger docs"
|
33
|
-
task all: [:saas, :on_prem]
|
34
|
-
|
35
|
-
desc "Generates swagger docs for SaaS"
|
36
|
-
task :saas do
|
37
|
-
generate_docs(:saas)
|
38
|
-
end
|
19
|
+
# The swagger specs generated by these tasks need to be committed to the
|
20
|
+
# Porta repo: https://github.com/3scale/porta/tree/master/doc/active_docs
|
21
|
+
# (Service Management API.json)
|
39
22
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
23
|
+
desc "Generates swagger docs"
|
24
|
+
task :generate do
|
25
|
+
generate_docs
|
44
26
|
end
|
45
27
|
end
|
46
28
|
end
|
data/licenses.xml
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
<dependencies>
|
4
4
|
<dependency>
|
5
5
|
<packageName>activesupport</packageName>
|
6
|
-
<version>5.
|
6
|
+
<version>5.2.4.3</version>
|
7
7
|
<licenses>
|
8
8
|
<license>
|
9
9
|
<name>MIT</name>
|
@@ -23,7 +23,7 @@
|
|
23
23
|
</dependency>
|
24
24
|
<dependency>
|
25
25
|
<packageName>apisonator</packageName>
|
26
|
-
<version>2.
|
26
|
+
<version>2.101.0</version>
|
27
27
|
<licenses>
|
28
28
|
<license>
|
29
29
|
<name>Apache 2.0</name>
|
@@ -243,7 +243,7 @@
|
|
243
243
|
</dependency>
|
244
244
|
<dependency>
|
245
245
|
<packageName>concurrent-ruby</packageName>
|
246
|
-
<version>1.
|
246
|
+
<version>1.1.6</version>
|
247
247
|
<licenses>
|
248
248
|
<license>
|
249
249
|
<name>MIT</name>
|
@@ -318,16 +318,6 @@
|
|
318
318
|
<url>http://opensource.org/licenses/mit-license</url>
|
319
319
|
</license>
|
320
320
|
</licenses>
|
321
|
-
</dependency>
|
322
|
-
<dependency>
|
323
|
-
<packageName>faraday</packageName>
|
324
|
-
<version>0.13.1</version>
|
325
|
-
<licenses>
|
326
|
-
<license>
|
327
|
-
<name>MIT</name>
|
328
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
329
|
-
</license>
|
330
|
-
</licenses>
|
331
321
|
</dependency>
|
332
322
|
<dependency>
|
333
323
|
<packageName>ffi</packageName>
|
@@ -338,16 +328,6 @@
|
|
338
328
|
<url>http://opensource.org/licenses/BSD-3-Clause</url>
|
339
329
|
</license>
|
340
330
|
</licenses>
|
341
|
-
</dependency>
|
342
|
-
<dependency>
|
343
|
-
<packageName>geminabox</packageName>
|
344
|
-
<version>0.13.11</version>
|
345
|
-
<licenses>
|
346
|
-
<license>
|
347
|
-
<name>MIT-LICENSE</name>
|
348
|
-
<url></url>
|
349
|
-
</license>
|
350
|
-
</licenses>
|
351
331
|
</dependency>
|
352
332
|
<dependency>
|
353
333
|
<packageName>gli</packageName>
|
@@ -361,27 +341,17 @@
|
|
361
341
|
</dependency>
|
362
342
|
<dependency>
|
363
343
|
<packageName>hiredis</packageName>
|
364
|
-
<version>0.6.
|
344
|
+
<version>0.6.3</version>
|
365
345
|
<licenses>
|
366
346
|
<license>
|
367
347
|
<name>New BSD</name>
|
368
348
|
<url>http://opensource.org/licenses/BSD-3-Clause</url>
|
369
349
|
</license>
|
370
350
|
</licenses>
|
371
|
-
</dependency>
|
372
|
-
<dependency>
|
373
|
-
<packageName>httpclient</packageName>
|
374
|
-
<version>2.8.3</version>
|
375
|
-
<licenses>
|
376
|
-
<license>
|
377
|
-
<name>ruby</name>
|
378
|
-
<url>http://www.ruby-lang.org/en/LICENSE.txt</url>
|
379
|
-
</license>
|
380
|
-
</licenses>
|
381
351
|
</dependency>
|
382
352
|
<dependency>
|
383
353
|
<packageName>i18n</packageName>
|
384
|
-
<version>
|
354
|
+
<version>1.8.2</version>
|
385
355
|
<licenses>
|
386
356
|
<license>
|
387
357
|
<name>MIT</name>
|
@@ -471,7 +441,7 @@
|
|
471
441
|
</dependency>
|
472
442
|
<dependency>
|
473
443
|
<packageName>minitest</packageName>
|
474
|
-
<version>5.
|
444
|
+
<version>5.14.1</version>
|
475
445
|
<licenses>
|
476
446
|
<license>
|
477
447
|
<name>MIT</name>
|
@@ -512,16 +482,6 @@
|
|
512
482
|
<url>http://opensource.org/licenses/mit-license</url>
|
513
483
|
</license>
|
514
484
|
</licenses>
|
515
|
-
</dependency>
|
516
|
-
<dependency>
|
517
|
-
<packageName>multipart-post</packageName>
|
518
|
-
<version>2.0.0</version>
|
519
|
-
<licenses>
|
520
|
-
<license>
|
521
|
-
<name>MIT</name>
|
522
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
523
|
-
</license>
|
524
|
-
</licenses>
|
525
485
|
</dependency>
|
526
486
|
<dependency>
|
527
487
|
<packageName>mustache</packageName>
|
@@ -542,16 +502,6 @@
|
|
542
502
|
<url>http://opensource.org/licenses/mit-license</url>
|
543
503
|
</license>
|
544
504
|
</licenses>
|
545
|
-
</dependency>
|
546
|
-
<dependency>
|
547
|
-
<packageName>nesty</packageName>
|
548
|
-
<version>1.0.2</version>
|
549
|
-
<licenses>
|
550
|
-
<license>
|
551
|
-
<name>MIT</name>
|
552
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
553
|
-
</license>
|
554
|
-
</licenses>
|
555
505
|
</dependency>
|
556
506
|
<dependency>
|
557
507
|
<packageName>net-scp</packageName>
|
@@ -809,7 +759,7 @@
|
|
809
759
|
</dependency>
|
810
760
|
<dependency>
|
811
761
|
<packageName>redis</packageName>
|
812
|
-
<version>4.1.
|
762
|
+
<version>4.1.3</version>
|
813
763
|
<licenses>
|
814
764
|
<license>
|
815
765
|
<name>MIT</name>
|
@@ -826,16 +776,6 @@
|
|
826
776
|
<url>http://opensource.org/licenses/mit-license</url>
|
827
777
|
</license>
|
828
778
|
</licenses>
|
829
|
-
</dependency>
|
830
|
-
<dependency>
|
831
|
-
<packageName>reentrant_flock</packageName>
|
832
|
-
<version>0.1.1</version>
|
833
|
-
<licenses>
|
834
|
-
<license>
|
835
|
-
<name>MIT</name>
|
836
|
-
<url>http://opensource.org/licenses/mit-license</url>
|
837
|
-
</license>
|
838
|
-
</licenses>
|
839
779
|
</dependency>
|
840
780
|
<dependency>
|
841
781
|
<packageName>resque</packageName>
|
@@ -1123,7 +1063,7 @@
|
|
1123
1063
|
</dependency>
|
1124
1064
|
<dependency>
|
1125
1065
|
<packageName>tzinfo</packageName>
|
1126
|
-
<version>1.2.
|
1066
|
+
<version>1.2.7</version>
|
1127
1067
|
<licenses>
|
1128
1068
|
<license>
|
1129
1069
|
<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: 2.
|
4
|
+
version: 2.101.0
|
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: 2020-04
|
19
|
+
date: 2020-06-04 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.
|
@@ -88,7 +88,6 @@ files:
|
|
88
88
|
- lib/3scale/backend/extensions/array.rb
|
89
89
|
- lib/3scale/backend/extensions/hash.rb
|
90
90
|
- lib/3scale/backend/extensions/nil_class.rb
|
91
|
-
- lib/3scale/backend/extensions/redis.rb
|
92
91
|
- lib/3scale/backend/extensions/string.rb
|
93
92
|
- lib/3scale/backend/extensions/time.rb
|
94
93
|
- lib/3scale/backend/failed_jobs_scheduler.rb
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# Monkey-patches a method in Redis::Client::Connector::Sentinel to fix a bug
|
2
|
-
# with sentinel passwords. It applies the fix in
|
3
|
-
# https://github.com/redis/redis-rb/pull/856.
|
4
|
-
#
|
5
|
-
# The fix was included in 4.1.2, but we cannot upgrade because that version
|
6
|
-
# drops support for ruby < 2.3.0 which we still need to support.
|
7
|
-
#
|
8
|
-
# This should only be temporary. It should be deleted when updating the gem.
|
9
|
-
class Redis
|
10
|
-
class Client
|
11
|
-
class Connector
|
12
|
-
class Sentinel
|
13
|
-
def sentinel_detect
|
14
|
-
@sentinels.each do |sentinel|
|
15
|
-
client = Redis::Client.new(@options.merge({:host => sentinel[:host],
|
16
|
-
:port => sentinel[:port],
|
17
|
-
password: sentinel[:password],
|
18
|
-
:reconnect_attempts => 0,
|
19
|
-
}))
|
20
|
-
|
21
|
-
begin
|
22
|
-
if result = yield(client)
|
23
|
-
# This sentinel responded. Make sure we ask it first next time.
|
24
|
-
@sentinels.delete(sentinel)
|
25
|
-
@sentinels.unshift(sentinel)
|
26
|
-
|
27
|
-
return result
|
28
|
-
end
|
29
|
-
rescue BaseConnectionError
|
30
|
-
rescue RuntimeError => exception
|
31
|
-
# Needed because when the sentinel address cannot be resolved it
|
32
|
-
# raises this instead of "BaseConnectionError"
|
33
|
-
raise unless exception.message =~ /Name or service not known/
|
34
|
-
ensure
|
35
|
-
client.disconnect
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
raise CannotConnectError, "No sentinels available."
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|