apisonator 2.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +317 -0
- data/Gemfile +11 -0
- data/Gemfile.base +65 -0
- data/Gemfile.lock +319 -0
- data/Gemfile.on_prem +1 -0
- data/Gemfile.on_prem.lock +297 -0
- data/LICENSE +202 -0
- data/NOTICE +15 -0
- data/README.md +230 -0
- data/Rakefile +287 -0
- data/apisonator.gemspec +47 -0
- data/app/api/api.rb +13 -0
- data/app/api/internal/alert_limits.rb +32 -0
- data/app/api/internal/application_keys.rb +49 -0
- data/app/api/internal/application_referrer_filters.rb +43 -0
- data/app/api/internal/applications.rb +77 -0
- data/app/api/internal/errors.rb +54 -0
- data/app/api/internal/events.rb +42 -0
- data/app/api/internal/internal.rb +104 -0
- data/app/api/internal/metrics.rb +40 -0
- data/app/api/internal/service_tokens.rb +46 -0
- data/app/api/internal/services.rb +58 -0
- data/app/api/internal/stats.rb +42 -0
- data/app/api/internal/usagelimits.rb +62 -0
- data/app/api/internal/utilization.rb +23 -0
- data/bin/3scale_backend +223 -0
- data/bin/3scale_backend_worker +26 -0
- data/config.ru +4 -0
- data/config/puma.rb +192 -0
- data/config/schedule.rb +9 -0
- data/ext/mkrf_conf.rb +64 -0
- data/lib/3scale/backend.rb +67 -0
- data/lib/3scale/backend/alert_limit.rb +56 -0
- data/lib/3scale/backend/alerts.rb +137 -0
- data/lib/3scale/backend/analytics/kinesis.rb +3 -0
- data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
- data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
- data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
- data/lib/3scale/backend/analytics/redshift.rb +3 -0
- data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
- data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
- data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
- data/lib/3scale/backend/application.rb +330 -0
- data/lib/3scale/backend/application_events.rb +76 -0
- data/lib/3scale/backend/background_job.rb +65 -0
- data/lib/3scale/backend/configurable.rb +20 -0
- data/lib/3scale/backend/configuration.rb +151 -0
- data/lib/3scale/backend/configuration/loader.rb +42 -0
- data/lib/3scale/backend/constants.rb +19 -0
- data/lib/3scale/backend/cors.rb +84 -0
- data/lib/3scale/backend/distributed_lock.rb +67 -0
- data/lib/3scale/backend/environment.rb +21 -0
- data/lib/3scale/backend/error_storage.rb +52 -0
- data/lib/3scale/backend/errors.rb +343 -0
- data/lib/3scale/backend/event_storage.rb +120 -0
- data/lib/3scale/backend/experiment.rb +84 -0
- data/lib/3scale/backend/extensions.rb +5 -0
- data/lib/3scale/backend/extensions/array.rb +19 -0
- data/lib/3scale/backend/extensions/hash.rb +26 -0
- data/lib/3scale/backend/extensions/nil_class.rb +13 -0
- data/lib/3scale/backend/extensions/redis.rb +44 -0
- data/lib/3scale/backend/extensions/string.rb +13 -0
- data/lib/3scale/backend/extensions/time.rb +110 -0
- data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
- data/lib/3scale/backend/job_fetcher.rb +122 -0
- data/lib/3scale/backend/listener.rb +728 -0
- data/lib/3scale/backend/listener_metrics.rb +99 -0
- data/lib/3scale/backend/logging.rb +48 -0
- data/lib/3scale/backend/logging/external.rb +44 -0
- data/lib/3scale/backend/logging/external/impl.rb +93 -0
- data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
- data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
- data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
- data/lib/3scale/backend/logging/external/resque.rb +57 -0
- data/lib/3scale/backend/logging/logger.rb +18 -0
- data/lib/3scale/backend/logging/middleware.rb +62 -0
- data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
- data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
- data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
- data/lib/3scale/backend/logging/worker.rb +107 -0
- data/lib/3scale/backend/manifest.rb +80 -0
- data/lib/3scale/backend/memoizer.rb +277 -0
- data/lib/3scale/backend/metric.rb +275 -0
- data/lib/3scale/backend/metric/collection.rb +91 -0
- data/lib/3scale/backend/oauth.rb +4 -0
- data/lib/3scale/backend/oauth/token.rb +26 -0
- data/lib/3scale/backend/oauth/token_key.rb +30 -0
- data/lib/3scale/backend/oauth/token_storage.rb +313 -0
- data/lib/3scale/backend/oauth/token_value.rb +25 -0
- data/lib/3scale/backend/period.rb +3 -0
- data/lib/3scale/backend/period/boundary.rb +107 -0
- data/lib/3scale/backend/period/cache.rb +28 -0
- data/lib/3scale/backend/period/period.rb +402 -0
- data/lib/3scale/backend/queue_storage.rb +16 -0
- data/lib/3scale/backend/rack.rb +49 -0
- data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
- data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
- data/lib/3scale/backend/rack/prometheus.rb +19 -0
- data/lib/3scale/backend/saas.rb +6 -0
- data/lib/3scale/backend/saas_analytics.rb +4 -0
- data/lib/3scale/backend/server.rb +30 -0
- data/lib/3scale/backend/server/falcon.rb +52 -0
- data/lib/3scale/backend/server/puma.rb +71 -0
- data/lib/3scale/backend/service.rb +317 -0
- data/lib/3scale/backend/service_token.rb +97 -0
- data/lib/3scale/backend/stats.rb +8 -0
- data/lib/3scale/backend/stats/aggregator.rb +170 -0
- data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
- data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
- data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
- data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
- data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
- data/lib/3scale/backend/stats/cleaner.rb +195 -0
- data/lib/3scale/backend/stats/codes_commons.rb +14 -0
- data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
- data/lib/3scale/backend/stats/key_generator.rb +73 -0
- data/lib/3scale/backend/stats/keys.rb +104 -0
- data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
- data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
- data/lib/3scale/backend/stats/period_commons.rb +34 -0
- data/lib/3scale/backend/stats/stats_parser.rb +141 -0
- data/lib/3scale/backend/stats/storage.rb +113 -0
- data/lib/3scale/backend/statsd.rb +14 -0
- data/lib/3scale/backend/storable.rb +35 -0
- data/lib/3scale/backend/storage.rb +40 -0
- data/lib/3scale/backend/storage_async.rb +4 -0
- data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
- data/lib/3scale/backend/storage_async/client.rb +205 -0
- data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
- data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
- data/lib/3scale/backend/storage_helpers.rb +278 -0
- data/lib/3scale/backend/storage_key_helpers.rb +9 -0
- data/lib/3scale/backend/storage_sync.rb +43 -0
- data/lib/3scale/backend/transaction.rb +62 -0
- data/lib/3scale/backend/transactor.rb +177 -0
- data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
- data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
- data/lib/3scale/backend/transactor/notify_job.rb +47 -0
- data/lib/3scale/backend/transactor/process_job.rb +33 -0
- data/lib/3scale/backend/transactor/report_job.rb +84 -0
- data/lib/3scale/backend/transactor/status.rb +236 -0
- data/lib/3scale/backend/transactor/usage_report.rb +182 -0
- data/lib/3scale/backend/usage.rb +63 -0
- data/lib/3scale/backend/usage_limit.rb +115 -0
- data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
- data/lib/3scale/backend/util.rb +17 -0
- data/lib/3scale/backend/validators.rb +26 -0
- data/lib/3scale/backend/validators/base.rb +36 -0
- data/lib/3scale/backend/validators/key.rb +17 -0
- data/lib/3scale/backend/validators/limits.rb +57 -0
- data/lib/3scale/backend/validators/oauth_key.rb +15 -0
- data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
- data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
- data/lib/3scale/backend/validators/referrer.rb +60 -0
- data/lib/3scale/backend/validators/service_state.rb +15 -0
- data/lib/3scale/backend/validators/state.rb +15 -0
- data/lib/3scale/backend/version.rb +5 -0
- data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
- data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
- data/lib/3scale/backend/worker.rb +87 -0
- data/lib/3scale/backend/worker_async.rb +88 -0
- data/lib/3scale/backend/worker_metrics.rb +44 -0
- data/lib/3scale/backend/worker_sync.rb +32 -0
- data/lib/3scale/bundler_shim.rb +17 -0
- data/lib/3scale/prometheus_server.rb +10 -0
- data/lib/3scale/tasks/connectivity.rake +41 -0
- data/lib/3scale/tasks/helpers.rb +3 -0
- data/lib/3scale/tasks/helpers/environment.rb +23 -0
- data/lib/3scale/tasks/stats.rake +131 -0
- data/lib/3scale/tasks/swagger.rake +46 -0
- data/licenses.xml +1215 -0
- metadata +227 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
class QueueStorage
|
|
4
|
+
def self.connection(env, cfg)
|
|
5
|
+
init_params = { url: cfg.queues && cfg.queues.master_name }
|
|
6
|
+
if %w(development test).include?(env)
|
|
7
|
+
init_params[:default_url] = '127.0.0.1:6379'
|
|
8
|
+
end
|
|
9
|
+
options = Backend::Storage::Helpers.config_with(cfg.queues,
|
|
10
|
+
options: init_params)
|
|
11
|
+
|
|
12
|
+
Storage.new(options)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require '3scale/backend/configuration'
|
|
2
|
+
require '3scale/backend/logging/middleware'
|
|
3
|
+
require '3scale/backend/util'
|
|
4
|
+
require '3scale/backend/rack/exception_catcher'
|
|
5
|
+
require '3scale/backend/rack/prometheus'
|
|
6
|
+
require '3scale/backend/rack/internal_error_catcher'
|
|
7
|
+
require '3scale/backend'
|
|
8
|
+
|
|
9
|
+
require 'rack'
|
|
10
|
+
|
|
11
|
+
module ThreeScale
|
|
12
|
+
module Backend
|
|
13
|
+
module Rack
|
|
14
|
+
def self.run(rack)
|
|
15
|
+
rack.instance_eval do
|
|
16
|
+
use Rack::InternalErrorCatcher if Backend.production?
|
|
17
|
+
|
|
18
|
+
Backend::Logging::External.setup_rack self
|
|
19
|
+
|
|
20
|
+
if Backend.configuration.listener_prometheus_metrics.enabled
|
|
21
|
+
use Rack::Prometheus
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
loggers = Backend.configuration.request_loggers
|
|
25
|
+
log_writers = Backend::Logging::Middleware.writers loggers
|
|
26
|
+
use Backend::Logging::Middleware, writers: log_writers
|
|
27
|
+
|
|
28
|
+
map "/internal" do
|
|
29
|
+
require_relative "#{Backend::Util.root_dir}/app/api/api"
|
|
30
|
+
|
|
31
|
+
internal_api = Backend::API::Internal.new(
|
|
32
|
+
username: Backend.configuration.internal_api.user,
|
|
33
|
+
password: Backend.configuration.internal_api.password,
|
|
34
|
+
allow_insecure: !Backend.production?
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
use ::Rack::Auth::Basic do |username, password|
|
|
38
|
+
internal_api.helpers.check_password username, password
|
|
39
|
+
end if internal_api.helpers.credentials_set?
|
|
40
|
+
|
|
41
|
+
run internal_api
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
run Backend::Listener.new
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require 'rack'
|
|
2
|
+
require '3scale/backend/cors'
|
|
3
|
+
|
|
4
|
+
module ThreeScale
|
|
5
|
+
module Backend
|
|
6
|
+
module Rack
|
|
7
|
+
class ExceptionCatcher
|
|
8
|
+
# These are the headers responded with when an error happens,
|
|
9
|
+
# and here we include the CORS ones.
|
|
10
|
+
# Note that this way of managing the errors is fundamentally
|
|
11
|
+
# broken, as important information gets lost. That is the case
|
|
12
|
+
# with response headers and other processing that has happened
|
|
13
|
+
# until an exception was raised.
|
|
14
|
+
# A refactoring to use Sinatra's error facilities is in order.
|
|
15
|
+
ERROR_HEADERS = {
|
|
16
|
+
'Content-Type'.freeze => 'application/vnd.3scale-v2.0+xml'.freeze,
|
|
17
|
+
}.merge(Backend::CORS.const_get(:HEADERS)).freeze
|
|
18
|
+
private_constant :ERROR_HEADERS
|
|
19
|
+
|
|
20
|
+
INVALID_BYTE_SEQUENCE_ERR_MSG = 'Invalid query parameters: '\
|
|
21
|
+
'invalid byte sequence in UTF-8'.freeze
|
|
22
|
+
private_constant :INVALID_BYTE_SEQUENCE_ERR_MSG
|
|
23
|
+
|
|
24
|
+
INVALID_PERCENT_ENCODING_ERR_MSG = 'Invalid query parameters: '\
|
|
25
|
+
'invalid %-encoding'.freeze
|
|
26
|
+
private_constant :INVALID_PERCENT_ENCODING_ERR_MSG
|
|
27
|
+
|
|
28
|
+
# Raised with invalid hash params such as:
|
|
29
|
+
# usage[]=1&usage[metric]=1.
|
|
30
|
+
# This is not the whole message. It contains the affected param at the end.
|
|
31
|
+
EXPECTED_HASH_ERR_MSG = 'Invalid query parameters: '\
|
|
32
|
+
'expected Hash (got Array)'.freeze
|
|
33
|
+
private_constant :EXPECTED_HASH_ERR_MSG
|
|
34
|
+
|
|
35
|
+
def initialize(app)
|
|
36
|
+
@app = app
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def call(env)
|
|
40
|
+
resp = @app.call(env)
|
|
41
|
+
filter_encoding_error_response resp, env
|
|
42
|
+
rescue Backend::Error => e
|
|
43
|
+
delete_sinatra_error! env
|
|
44
|
+
respond_with e.http_code, prepare_body(e.to_xml, env)
|
|
45
|
+
rescue Exception => e
|
|
46
|
+
unhandled_exception(e)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# raise up the chain an unhandled exception - but test first for an edge
|
|
52
|
+
# case with Rack key space limit for parsing requests.
|
|
53
|
+
# See https://github.com/rack/rack/blob/2.0.4/lib/rack/query_parser.rb#L166
|
|
54
|
+
def unhandled_exception(e)
|
|
55
|
+
if e.class == RangeError &&
|
|
56
|
+
e.message == 'exceeded available parameter key space'.freeze
|
|
57
|
+
respond_with 400, Backend::NotValidData.new.to_xml
|
|
58
|
+
else
|
|
59
|
+
raise e
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Private: Deletes 'sinatra.error' key in Rack's env hash.
|
|
64
|
+
# Some external services report 'sinatra.error' and we don't want it when
|
|
65
|
+
# the error is rescued and managed by us with the error handler.
|
|
66
|
+
#
|
|
67
|
+
# env - The environment Hash.
|
|
68
|
+
#
|
|
69
|
+
# Returns nothing.
|
|
70
|
+
def delete_sinatra_error!(env)
|
|
71
|
+
env['sinatra.error'.freeze] = nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Private: Prepares the body to include in the reponse.
|
|
75
|
+
#
|
|
76
|
+
# body - Proposed body String.
|
|
77
|
+
# env - The environment Hash.
|
|
78
|
+
#
|
|
79
|
+
# Returns String.
|
|
80
|
+
def prepare_body(body, env)
|
|
81
|
+
Backend::Listener.threescale_extensions(env)[:no_body] ? ''.freeze : body
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns Rack response.
|
|
85
|
+
# Format https://rack.github.io/
|
|
86
|
+
# Array with three elements:
|
|
87
|
+
# *) The HTTP response code
|
|
88
|
+
# *) A Hash of headers
|
|
89
|
+
# *) The response body, which must respond to `each`
|
|
90
|
+
def respond_with(code, body)
|
|
91
|
+
[code, ERROR_HEADERS.dup, [body]]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Private:
|
|
95
|
+
# Filter to transform response under specific conditions:
|
|
96
|
+
# - http_status is 400 and error code refers to encoding issues.
|
|
97
|
+
# - http_status is 400 and error code refers to a malformed hash param.
|
|
98
|
+
# When input request has one of those issues, Sinatra does not raise error
|
|
99
|
+
# and it prepares its own response. For backwards compatibility, we need
|
|
100
|
+
# to capture the error and customize response accordingly.
|
|
101
|
+
# Best and cleanest way to accomplish this is using error handlers. However,
|
|
102
|
+
# Sinatra 2.0.0 has a bug when registering error handlers and request has encoding issues.
|
|
103
|
+
# It has been reported in https://github.com/sinatra/sinatra/issues/1350
|
|
104
|
+
#
|
|
105
|
+
# resp - Rack response. Format https://rack.github.io/
|
|
106
|
+
# Array with three elements:
|
|
107
|
+
# *) The HTTP response code
|
|
108
|
+
# *) A Hash of headers
|
|
109
|
+
# *) The response body, which must respond to `each`
|
|
110
|
+
#
|
|
111
|
+
# env - The environment Hash.
|
|
112
|
+
#
|
|
113
|
+
# Returns Rack response.
|
|
114
|
+
def filter_encoding_error_response(resp, env)
|
|
115
|
+
return resp unless resp.first == 400
|
|
116
|
+
# According to http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body
|
|
117
|
+
# The Body must respond to each and must only yield String values.
|
|
118
|
+
resp_body = resp.last.inject('') { |acc, x| acc << x }
|
|
119
|
+
|
|
120
|
+
if resp_body == INVALID_BYTE_SEQUENCE_ERR_MSG
|
|
121
|
+
delete_sinatra_error! env
|
|
122
|
+
resp = respond_with 400, Backend::NotValidData.new.to_xml
|
|
123
|
+
elsif resp_body.start_with?(EXPECTED_HASH_ERR_MSG)
|
|
124
|
+
delete_sinatra_error! env
|
|
125
|
+
resp = respond_with 400, Backend::BadRequest.new.to_xml
|
|
126
|
+
elsif resp_body.start_with?(INVALID_PERCENT_ENCODING_ERR_MSG)
|
|
127
|
+
delete_sinatra_error! env
|
|
128
|
+
resp = respond_with 400, Backend::BadRequest.new(INVALID_PERCENT_ENCODING_ERR_MSG).to_xml
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
resp
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Rack
|
|
4
|
+
|
|
5
|
+
# This middleware should be the last one to run. If there's an exception,
|
|
6
|
+
# instead of propagating it to the web server, we set our own error
|
|
7
|
+
# message. The reason is that each web server handles this differently.
|
|
8
|
+
# Puma returns a generic error message, while Falcon returns the message
|
|
9
|
+
# of the exception.
|
|
10
|
+
class InternalErrorCatcher
|
|
11
|
+
def initialize(app)
|
|
12
|
+
@app = app
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(env)
|
|
16
|
+
@app.call(env)
|
|
17
|
+
rescue
|
|
18
|
+
[500, {}, ["Internal Server Error\n".freeze]]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Rack
|
|
4
|
+
class Prometheus
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
began_at = Time.now.getutc
|
|
11
|
+
status, header, body = @app.call(env)
|
|
12
|
+
ListenerMetrics.report_resp_code(env['REQUEST_PATH'], status)
|
|
13
|
+
ListenerMetrics.report_response_time(env['REQUEST_PATH'], Time.now - began_at)
|
|
14
|
+
[status, header, body]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require '3scale/backend/util'
|
|
2
|
+
|
|
3
|
+
module ThreeScale
|
|
4
|
+
module Backend
|
|
5
|
+
module Server
|
|
6
|
+
def self.get(server_name)
|
|
7
|
+
server_file = server_name.tr('-', '_')
|
|
8
|
+
require "3scale/backend/server/#{server_file}"
|
|
9
|
+
server_class_name = server_file.tr('_', '').capitalize
|
|
10
|
+
ThreeScale::Backend::Server.const_get server_class_name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.list
|
|
14
|
+
Dir[File.join(ThreeScale::Backend::Util.root_dir, 'lib', '3scale', 'backend', 'server', '*.rb')].map do |s|
|
|
15
|
+
File.basename(s)[0..-4]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Utils
|
|
20
|
+
def argv_add(argv, option, switch, *arguments)
|
|
21
|
+
if option
|
|
22
|
+
argv << switch
|
|
23
|
+
arguments.each { |a| argv << a }
|
|
24
|
+
end
|
|
25
|
+
argv
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Server
|
|
4
|
+
class Falcon
|
|
5
|
+
extend ThreeScale::Backend::Server::Utils
|
|
6
|
+
|
|
7
|
+
def self.start(global_options, options, args)
|
|
8
|
+
# Falcon does not support:
|
|
9
|
+
# - options[:daemonize]
|
|
10
|
+
# - options[:logfile]
|
|
11
|
+
# - options[:errorfile]
|
|
12
|
+
# - options[:pidfile]
|
|
13
|
+
|
|
14
|
+
manifest = global_options[:manifest]
|
|
15
|
+
return unless manifest
|
|
16
|
+
|
|
17
|
+
argv = ['falcon']
|
|
18
|
+
argv_add argv, true, '--bind', 'http://0.0.0.0'
|
|
19
|
+
argv_add argv, options[:port], '--port', options[:port]
|
|
20
|
+
|
|
21
|
+
# Starts the prometheus server if needed. Just once even when spanning
|
|
22
|
+
# multiple workers.
|
|
23
|
+
argv_add argv, true, '--preload', 'lib/3scale/prometheus_server.rb'
|
|
24
|
+
|
|
25
|
+
server_model = manifest[:server_model]
|
|
26
|
+
argv_add argv, true, '--count', server_model[:workers].to_s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.restart(global_options, options, args)
|
|
30
|
+
argv = ['falcon', 'supervisor']
|
|
31
|
+
argv_add argv, true, 'restart'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.stop(global_options, options, args)
|
|
35
|
+
STDERR.puts 'Not implemented'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.status(global_options, options, args)
|
|
39
|
+
STDERR.puts 'Not implemented'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.stats(global_options, options, args)
|
|
43
|
+
STDERR.puts 'Not implemented'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.help(global_options, options, args)
|
|
47
|
+
system('falcon --help')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Server
|
|
4
|
+
class Puma
|
|
5
|
+
extend ThreeScale::Backend::Server::Utils
|
|
6
|
+
|
|
7
|
+
CONFIG = 'config/puma.rb'
|
|
8
|
+
CONTROL_SOCKET = '3scale_backend.sock'
|
|
9
|
+
STATE = '3scale_backend.state'
|
|
10
|
+
|
|
11
|
+
EXPANDED_ROOT_PATH = File.expand_path(File.join(*Array.new(5, '..')),
|
|
12
|
+
__FILE__)
|
|
13
|
+
|
|
14
|
+
def self.socket_state_dir(env, default_dir)
|
|
15
|
+
if ['development', 'test'].include?(env) && File.writable?(default_dir)
|
|
16
|
+
default_dir
|
|
17
|
+
else
|
|
18
|
+
File.join('', 'tmp')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.start(global_options, options, args)
|
|
23
|
+
manifest = global_options[:manifest]
|
|
24
|
+
return unless manifest
|
|
25
|
+
argv = ['puma']
|
|
26
|
+
argv_add argv, options[:daemonize], '-d'
|
|
27
|
+
argv_add argv, options[:port], '-p', options[:port]
|
|
28
|
+
argv_add argv, options[:logfile], '--redirect-stdout', options[:logfile]
|
|
29
|
+
argv_add argv, options[:errorfile], '--redirect-stderr', options[:errorfile]
|
|
30
|
+
argv << '--redirect-append' if [options[:logfile], options[:errorfile]].any?
|
|
31
|
+
argv_add argv, options[:pidfile], '--pidfile', options[:pidfile]
|
|
32
|
+
# workaround Puma bug not phase-restarting correctly if no --dir is specified
|
|
33
|
+
argv_add argv, true, '--dir', global_options[:directory] ? global_options[:directory] : EXPANDED_ROOT_PATH
|
|
34
|
+
argv_add argv, true, '-C', CONFIG
|
|
35
|
+
ss_dir = socket_state_dir(global_options[:environment], global_options[:directory] || EXPANDED_ROOT_PATH)
|
|
36
|
+
argv_add argv, true, '-S', File.join(ss_dir, STATE)
|
|
37
|
+
argv_add argv, true, '--control', "unix://#{File.join(ss_dir, CONTROL_SOCKET)}"
|
|
38
|
+
server_model = manifest[:server_model]
|
|
39
|
+
argv_add argv, true, '-w', server_model[:workers].to_s
|
|
40
|
+
argv_add argv, true, '-t', "#{server_model[:min_threads]}:#{server_model[:max_threads]}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.restart(global_options, options, args)
|
|
44
|
+
build_pumactl_cmdline(options[:'phased-restart'] ? 'phased-restart' : 'restart', global_options, options, args)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
[:stop, :status, :stats].each do |cmd|
|
|
48
|
+
define_singleton_method cmd do |global_options, options, args|
|
|
49
|
+
build_pumactl_cmdline(__method__, global_options, options, args)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.help(global_options, options, args)
|
|
54
|
+
system('puma --help')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.build_pumactl_cmdline(cmd, global_options, options, args)
|
|
58
|
+
argv = ['pumactl']
|
|
59
|
+
ss_dir = socket_state_dir(global_options[:environment], global_options[:directory] || EXPANDED_ROOT_PATH)
|
|
60
|
+
argv_add argv, true, '-S', File.join(ss_dir, STATE)
|
|
61
|
+
argv_add argv, true, '-C', "unix://#{File.join(ss_dir, CONTROL_SOCKET)}"
|
|
62
|
+
argv_add argv, true, '-F', CONFIG
|
|
63
|
+
argv << cmd.to_s
|
|
64
|
+
argv
|
|
65
|
+
end
|
|
66
|
+
private_class_method :build_pumactl_cmdline
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
class Service
|
|
4
|
+
include Storable
|
|
5
|
+
|
|
6
|
+
# list of attributes to be fetched from storage
|
|
7
|
+
ATTRIBUTES = %i[state referrer_filters_required backend_version
|
|
8
|
+
user_registration_required default_user_plan_id
|
|
9
|
+
default_user_plan_name provider_key].freeze
|
|
10
|
+
private_constant :ATTRIBUTES
|
|
11
|
+
|
|
12
|
+
attr_reader :state
|
|
13
|
+
attr_accessor :provider_key, :id, :backend_version,
|
|
14
|
+
:default_user_plan_id, :default_user_plan_name
|
|
15
|
+
attr_writer :referrer_filters_required, :user_registration_required,
|
|
16
|
+
:default_service
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
include Memoizer::Decorator
|
|
20
|
+
|
|
21
|
+
def attribute_names
|
|
22
|
+
(ATTRIBUTES + %i[id default_service].freeze).freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns true if a given service belongs to the provider with
|
|
26
|
+
# that key without loading the whole object.
|
|
27
|
+
#
|
|
28
|
+
def authenticate_service_id(service_id, provider_key)
|
|
29
|
+
provider_key == provider_key_for(service_id)
|
|
30
|
+
end
|
|
31
|
+
memoize :authenticate_service_id
|
|
32
|
+
|
|
33
|
+
def default_id(provider_key)
|
|
34
|
+
storage.get(storage_key_by_provider(provider_key, :id))
|
|
35
|
+
end
|
|
36
|
+
memoize :default_id
|
|
37
|
+
|
|
38
|
+
def default_id!(provider_key)
|
|
39
|
+
default_id(provider_key) or raise ProviderKeyInvalid, provider_key
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_by_id(service_id)
|
|
43
|
+
return if service_id.nil?
|
|
44
|
+
|
|
45
|
+
service_attrs = get_service(id = service_id.to_s)
|
|
46
|
+
massage_service_attrs service_attrs
|
|
47
|
+
|
|
48
|
+
return if service_attrs[:provider_key].nil?
|
|
49
|
+
|
|
50
|
+
new(service_attrs.merge(id: id,
|
|
51
|
+
default_service: default_service?(service_attrs[:provider_key], id)
|
|
52
|
+
))
|
|
53
|
+
end
|
|
54
|
+
memoize :load_by_id
|
|
55
|
+
|
|
56
|
+
def load_by_id!(service_id)
|
|
57
|
+
load_by_id(service_id) or raise ServiceIdInvalid, service_id
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def load_with_provider_key!(id, provider_key)
|
|
61
|
+
id = Service.default_id(provider_key) if id.nil? || id.empty?
|
|
62
|
+
raise ProviderKeyInvalidOrServiceMissing, provider_key if id.nil? || id.empty?
|
|
63
|
+
|
|
64
|
+
service = Service.load_by_id(id.split('-').last) || Service.load_by_id!(id)
|
|
65
|
+
|
|
66
|
+
if service.provider_key != provider_key
|
|
67
|
+
# this is an error; let's raise in default_id! or raise invalid service
|
|
68
|
+
Service.default_id!(provider_key)
|
|
69
|
+
raise ServiceIdInvalid, id
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
service
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def delete_by_id(service_id)
|
|
76
|
+
service = load_by_id!(service_id)
|
|
77
|
+
|
|
78
|
+
if service.default_service? and not service_is_the_only_one_for_provider(service_id)
|
|
79
|
+
raise ServiceIsDefaultService, service.id
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
service.delete_data
|
|
83
|
+
service.clear_cache
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def exists?(service_id)
|
|
87
|
+
storage.exists(storage_key(service_id, 'provider_key'))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def get_service(id)
|
|
91
|
+
keys = ATTRIBUTES.map { |attr| storage_key(id, attr) }
|
|
92
|
+
values = storage.mget(keys)
|
|
93
|
+
|
|
94
|
+
result = {}
|
|
95
|
+
ATTRIBUTES.each_with_index do |key, idx|
|
|
96
|
+
result[key] = values[idx]
|
|
97
|
+
end
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def list(provider_key)
|
|
102
|
+
storage.smembers(storage_key_by_provider(provider_key, :ids)) || []
|
|
103
|
+
end
|
|
104
|
+
memoize :list
|
|
105
|
+
|
|
106
|
+
def save!(attributes = {})
|
|
107
|
+
massage_set_user_registration_required attributes
|
|
108
|
+
|
|
109
|
+
new(attributes).save!
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def storage_key(id, attribute)
|
|
113
|
+
encode_key("service/id:#{id}/#{attribute}")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def storage_key_by_provider(provider_key, attribute)
|
|
117
|
+
encode_key("service/provider_key:#{provider_key}/#{attribute}")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def clear_cache(provider_key, id)
|
|
121
|
+
provider_key_arg = [provider_key]
|
|
122
|
+
keys = Memoizer.build_keys_for_class(self,
|
|
123
|
+
authenticate_service_id: [id, provider_key],
|
|
124
|
+
default_id: provider_key_arg,
|
|
125
|
+
load_by_id: [id],
|
|
126
|
+
list: provider_key_arg,
|
|
127
|
+
provider_key_for: [id])
|
|
128
|
+
Memoizer.clear keys
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Gets the provider key without loading the whole service
|
|
132
|
+
def provider_key_for(service_id)
|
|
133
|
+
storage.get(storage_key(service_id, 'provider_key'.freeze))
|
|
134
|
+
end
|
|
135
|
+
memoize :provider_key_for
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def massage_service_attrs(service_attrs)
|
|
140
|
+
service_attrs[:referrer_filters_required] =
|
|
141
|
+
service_attrs[:referrer_filters_required].to_i > 0
|
|
142
|
+
service_attrs[:user_registration_required] =
|
|
143
|
+
massage_get_user_registration_required(
|
|
144
|
+
service_attrs[:user_registration_required])
|
|
145
|
+
|
|
146
|
+
service_attrs
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# nil => true, 1 => true, '1' => true, 0 => false, '0' => false
|
|
150
|
+
def massage_get_user_registration_required(value)
|
|
151
|
+
value.nil? ? true : value.to_i > 0
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def massage_set_user_registration_required(attributes)
|
|
155
|
+
if attributes[:user_registration_required].nil?
|
|
156
|
+
val = storage.get(storage_key(attributes[:id], :user_registration_required))
|
|
157
|
+
attributes[:user_registration_required] =
|
|
158
|
+
(!val.nil? && val.to_i == 0) ? false : true
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def get_attr(id, attribute)
|
|
163
|
+
storage.get(storage_key(id, attribute))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def default_service?(provider_key, id)
|
|
167
|
+
default_id(provider_key) == id.to_s
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def service_is_the_only_one_for_provider(service_id)
|
|
171
|
+
provider_key = provider_key_for(service_id)
|
|
172
|
+
services = list(provider_key)
|
|
173
|
+
services.size == 1 and services[0] == service_id.to_s
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def initialize(attributes = {})
|
|
178
|
+
# :state is set as active in this method when:
|
|
179
|
+
# - The state key is not present in the attributes hash
|
|
180
|
+
# - The state key is present in the attributes hash but it has
|
|
181
|
+
# the nil value
|
|
182
|
+
# This is done in order to not break compatibility for existing
|
|
183
|
+
# Services saved in the database, that do not contain the state
|
|
184
|
+
# key.
|
|
185
|
+
attributes[:state] ||= :active
|
|
186
|
+
|
|
187
|
+
super(attributes)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def default_service?
|
|
191
|
+
@default_service
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def referrer_filters_required?
|
|
195
|
+
@referrer_filters_required
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def user_registration_required?
|
|
199
|
+
@user_registration_required
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def save!
|
|
203
|
+
set_as_default_if_needed
|
|
204
|
+
persist
|
|
205
|
+
clear_cache
|
|
206
|
+
self
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def clear_cache
|
|
210
|
+
self.class.clear_cache(provider_key, id)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def storage_key(attribute)
|
|
214
|
+
self.class.storage_key id, attribute
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def delete_data
|
|
218
|
+
delete_from_lists
|
|
219
|
+
delete_attributes
|
|
220
|
+
ErrorStorage.delete_all(id)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def to_hash
|
|
224
|
+
{
|
|
225
|
+
id: id,
|
|
226
|
+
state: state,
|
|
227
|
+
provider_key: provider_key,
|
|
228
|
+
backend_version: backend_version,
|
|
229
|
+
referrer_filters_required: referrer_filters_required?,
|
|
230
|
+
user_registration_required: user_registration_required?,
|
|
231
|
+
default_user_plan_id: default_user_plan_id,
|
|
232
|
+
default_user_plan_name: default_user_plan_name,
|
|
233
|
+
default_service: default_service?
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def active?
|
|
238
|
+
state == :active
|
|
239
|
+
end
|
|
240
|
+
alias_method :active, :active?
|
|
241
|
+
|
|
242
|
+
def active=(value)
|
|
243
|
+
self.state = value ? :active : :suspended
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
def state=(value)
|
|
249
|
+
# only :active or nil will be considered as :active
|
|
250
|
+
# we assume nil is active because not having a state in an
|
|
251
|
+
# existing service means that is active in Services created before
|
|
252
|
+
# this change
|
|
253
|
+
@state = value.nil? || value.to_sym == :active ? :active : :suspended
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def delete_attributes
|
|
257
|
+
keys = ATTRIBUTES.map { |attr| storage_key(attr) }
|
|
258
|
+
keys << storage_key_by_provider(:id) if default_service?
|
|
259
|
+
storage.del keys
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def delete_from_lists
|
|
263
|
+
set = storage_key_by_provider :ids
|
|
264
|
+
storage.srem set, id
|
|
265
|
+
storage.srem encode_key('services_set'), id
|
|
266
|
+
storage.del set if default_service?
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def storage_key_by_provider(attribute)
|
|
270
|
+
self.class.storage_key_by_provider provider_key, attribute
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def set_as_default_if_needed
|
|
274
|
+
if @default_service.nil?
|
|
275
|
+
default_service_id = self.class.default_id(provider_key)
|
|
276
|
+
@default_service = default_service_id.nil?
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def persist
|
|
281
|
+
persist_default(self.class.default_id(provider_key)) if default_service?
|
|
282
|
+
persist_attributes
|
|
283
|
+
persist_sets
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def persist_default(old_default_id)
|
|
287
|
+
# we get all sorts of combinations of Strings and Fixnums here. Convert'em.
|
|
288
|
+
if old_default_id.to_i != id.to_i
|
|
289
|
+
storage.set storage_key_by_provider(:id), id
|
|
290
|
+
# we should now clear memoizations of the previous default service
|
|
291
|
+
self.class.clear_cache(provider_key, old_default_id)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def persist_attributes
|
|
296
|
+
persist_attribute :referrer_filters_required, referrer_filters_required? ? 1 : 0
|
|
297
|
+
persist_attribute :user_registration_required, user_registration_required? ? 1 : 0
|
|
298
|
+
persist_attribute :default_user_plan_id, default_user_plan_id, true
|
|
299
|
+
persist_attribute :default_user_plan_name, default_user_plan_name, true
|
|
300
|
+
persist_attribute :backend_version, backend_version, true
|
|
301
|
+
persist_attribute :provider_key, provider_key
|
|
302
|
+
persist_attribute :state, state.to_s if state
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def persist_attribute(attribute, value, ignore_nils = false)
|
|
306
|
+
storage.set storage_key(attribute), value unless ignore_nils && value.nil?
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def persist_sets
|
|
310
|
+
storage.sadd storage_key_by_provider(:ids), id
|
|
311
|
+
storage.sadd encode_key("services_set"), id
|
|
312
|
+
storage.sadd encode_key("provider_keys_set"), provider_key
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|