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.
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