apisonator 2.100.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|