apisonator 2.100.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +317 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.base +65 -0
  5. data/Gemfile.lock +319 -0
  6. data/Gemfile.on_prem +1 -0
  7. data/Gemfile.on_prem.lock +297 -0
  8. data/LICENSE +202 -0
  9. data/NOTICE +15 -0
  10. data/README.md +230 -0
  11. data/Rakefile +287 -0
  12. data/apisonator.gemspec +47 -0
  13. data/app/api/api.rb +13 -0
  14. data/app/api/internal/alert_limits.rb +32 -0
  15. data/app/api/internal/application_keys.rb +49 -0
  16. data/app/api/internal/application_referrer_filters.rb +43 -0
  17. data/app/api/internal/applications.rb +77 -0
  18. data/app/api/internal/errors.rb +54 -0
  19. data/app/api/internal/events.rb +42 -0
  20. data/app/api/internal/internal.rb +104 -0
  21. data/app/api/internal/metrics.rb +40 -0
  22. data/app/api/internal/service_tokens.rb +46 -0
  23. data/app/api/internal/services.rb +58 -0
  24. data/app/api/internal/stats.rb +42 -0
  25. data/app/api/internal/usagelimits.rb +62 -0
  26. data/app/api/internal/utilization.rb +23 -0
  27. data/bin/3scale_backend +223 -0
  28. data/bin/3scale_backend_worker +26 -0
  29. data/config.ru +4 -0
  30. data/config/puma.rb +192 -0
  31. data/config/schedule.rb +9 -0
  32. data/ext/mkrf_conf.rb +64 -0
  33. data/lib/3scale/backend.rb +67 -0
  34. data/lib/3scale/backend/alert_limit.rb +56 -0
  35. data/lib/3scale/backend/alerts.rb +137 -0
  36. data/lib/3scale/backend/analytics/kinesis.rb +3 -0
  37. data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
  38. data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
  39. data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
  40. data/lib/3scale/backend/analytics/redshift.rb +3 -0
  41. data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
  42. data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
  43. data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
  44. data/lib/3scale/backend/application.rb +330 -0
  45. data/lib/3scale/backend/application_events.rb +76 -0
  46. data/lib/3scale/backend/background_job.rb +65 -0
  47. data/lib/3scale/backend/configurable.rb +20 -0
  48. data/lib/3scale/backend/configuration.rb +151 -0
  49. data/lib/3scale/backend/configuration/loader.rb +42 -0
  50. data/lib/3scale/backend/constants.rb +19 -0
  51. data/lib/3scale/backend/cors.rb +84 -0
  52. data/lib/3scale/backend/distributed_lock.rb +67 -0
  53. data/lib/3scale/backend/environment.rb +21 -0
  54. data/lib/3scale/backend/error_storage.rb +52 -0
  55. data/lib/3scale/backend/errors.rb +343 -0
  56. data/lib/3scale/backend/event_storage.rb +120 -0
  57. data/lib/3scale/backend/experiment.rb +84 -0
  58. data/lib/3scale/backend/extensions.rb +5 -0
  59. data/lib/3scale/backend/extensions/array.rb +19 -0
  60. data/lib/3scale/backend/extensions/hash.rb +26 -0
  61. data/lib/3scale/backend/extensions/nil_class.rb +13 -0
  62. data/lib/3scale/backend/extensions/redis.rb +44 -0
  63. data/lib/3scale/backend/extensions/string.rb +13 -0
  64. data/lib/3scale/backend/extensions/time.rb +110 -0
  65. data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
  66. data/lib/3scale/backend/job_fetcher.rb +122 -0
  67. data/lib/3scale/backend/listener.rb +728 -0
  68. data/lib/3scale/backend/listener_metrics.rb +99 -0
  69. data/lib/3scale/backend/logging.rb +48 -0
  70. data/lib/3scale/backend/logging/external.rb +44 -0
  71. data/lib/3scale/backend/logging/external/impl.rb +93 -0
  72. data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
  73. data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
  74. data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
  75. data/lib/3scale/backend/logging/external/resque.rb +57 -0
  76. data/lib/3scale/backend/logging/logger.rb +18 -0
  77. data/lib/3scale/backend/logging/middleware.rb +62 -0
  78. data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
  79. data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
  80. data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
  81. data/lib/3scale/backend/logging/worker.rb +107 -0
  82. data/lib/3scale/backend/manifest.rb +80 -0
  83. data/lib/3scale/backend/memoizer.rb +277 -0
  84. data/lib/3scale/backend/metric.rb +275 -0
  85. data/lib/3scale/backend/metric/collection.rb +91 -0
  86. data/lib/3scale/backend/oauth.rb +4 -0
  87. data/lib/3scale/backend/oauth/token.rb +26 -0
  88. data/lib/3scale/backend/oauth/token_key.rb +30 -0
  89. data/lib/3scale/backend/oauth/token_storage.rb +313 -0
  90. data/lib/3scale/backend/oauth/token_value.rb +25 -0
  91. data/lib/3scale/backend/period.rb +3 -0
  92. data/lib/3scale/backend/period/boundary.rb +107 -0
  93. data/lib/3scale/backend/period/cache.rb +28 -0
  94. data/lib/3scale/backend/period/period.rb +402 -0
  95. data/lib/3scale/backend/queue_storage.rb +16 -0
  96. data/lib/3scale/backend/rack.rb +49 -0
  97. data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
  98. data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
  99. data/lib/3scale/backend/rack/prometheus.rb +19 -0
  100. data/lib/3scale/backend/saas.rb +6 -0
  101. data/lib/3scale/backend/saas_analytics.rb +4 -0
  102. data/lib/3scale/backend/server.rb +30 -0
  103. data/lib/3scale/backend/server/falcon.rb +52 -0
  104. data/lib/3scale/backend/server/puma.rb +71 -0
  105. data/lib/3scale/backend/service.rb +317 -0
  106. data/lib/3scale/backend/service_token.rb +97 -0
  107. data/lib/3scale/backend/stats.rb +8 -0
  108. data/lib/3scale/backend/stats/aggregator.rb +170 -0
  109. data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
  110. data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
  111. data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
  112. data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
  113. data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
  114. data/lib/3scale/backend/stats/cleaner.rb +195 -0
  115. data/lib/3scale/backend/stats/codes_commons.rb +14 -0
  116. data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
  117. data/lib/3scale/backend/stats/key_generator.rb +73 -0
  118. data/lib/3scale/backend/stats/keys.rb +104 -0
  119. data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
  120. data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
  121. data/lib/3scale/backend/stats/period_commons.rb +34 -0
  122. data/lib/3scale/backend/stats/stats_parser.rb +141 -0
  123. data/lib/3scale/backend/stats/storage.rb +113 -0
  124. data/lib/3scale/backend/statsd.rb +14 -0
  125. data/lib/3scale/backend/storable.rb +35 -0
  126. data/lib/3scale/backend/storage.rb +40 -0
  127. data/lib/3scale/backend/storage_async.rb +4 -0
  128. data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
  129. data/lib/3scale/backend/storage_async/client.rb +205 -0
  130. data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
  131. data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
  132. data/lib/3scale/backend/storage_helpers.rb +278 -0
  133. data/lib/3scale/backend/storage_key_helpers.rb +9 -0
  134. data/lib/3scale/backend/storage_sync.rb +43 -0
  135. data/lib/3scale/backend/transaction.rb +62 -0
  136. data/lib/3scale/backend/transactor.rb +177 -0
  137. data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
  138. data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
  139. data/lib/3scale/backend/transactor/notify_job.rb +47 -0
  140. data/lib/3scale/backend/transactor/process_job.rb +33 -0
  141. data/lib/3scale/backend/transactor/report_job.rb +84 -0
  142. data/lib/3scale/backend/transactor/status.rb +236 -0
  143. data/lib/3scale/backend/transactor/usage_report.rb +182 -0
  144. data/lib/3scale/backend/usage.rb +63 -0
  145. data/lib/3scale/backend/usage_limit.rb +115 -0
  146. data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
  147. data/lib/3scale/backend/util.rb +17 -0
  148. data/lib/3scale/backend/validators.rb +26 -0
  149. data/lib/3scale/backend/validators/base.rb +36 -0
  150. data/lib/3scale/backend/validators/key.rb +17 -0
  151. data/lib/3scale/backend/validators/limits.rb +57 -0
  152. data/lib/3scale/backend/validators/oauth_key.rb +15 -0
  153. data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
  154. data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
  155. data/lib/3scale/backend/validators/referrer.rb +60 -0
  156. data/lib/3scale/backend/validators/service_state.rb +15 -0
  157. data/lib/3scale/backend/validators/state.rb +15 -0
  158. data/lib/3scale/backend/version.rb +5 -0
  159. data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
  160. data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
  161. data/lib/3scale/backend/worker.rb +87 -0
  162. data/lib/3scale/backend/worker_async.rb +88 -0
  163. data/lib/3scale/backend/worker_metrics.rb +44 -0
  164. data/lib/3scale/backend/worker_sync.rb +32 -0
  165. data/lib/3scale/bundler_shim.rb +17 -0
  166. data/lib/3scale/prometheus_server.rb +10 -0
  167. data/lib/3scale/tasks/connectivity.rake +41 -0
  168. data/lib/3scale/tasks/helpers.rb +3 -0
  169. data/lib/3scale/tasks/helpers/environment.rb +23 -0
  170. data/lib/3scale/tasks/stats.rake +131 -0
  171. data/lib/3scale/tasks/swagger.rake +46 -0
  172. data/licenses.xml +1215 -0
  173. 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,6 @@
1
+ if ThreeScale::Backend.configuration.saas
2
+ # SaaS-specific dependencies
3
+ require '3scale/backend/statsd'
4
+ require '3scale/backend/experiment'
5
+ require '3scale/backend/saas_analytics'
6
+ end
@@ -0,0 +1,4 @@
1
+ # Requires needed for the SaaS analytics system based on Kinesis + Redshift.
2
+ require 'aws-sdk'
3
+ require '3scale/backend/analytics/kinesis'
4
+ require '3scale/backend/analytics/redshift'
@@ -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