zuora_connect 0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +38 -0
- data/app/assets/javascripts/hallway_wrapper/after.js +15 -0
- data/app/assets/javascripts/hallway_wrapper/before.js +2 -0
- data/app/assets/javascripts/zuora_connect/api/v1/app_instance.js +2 -0
- data/app/assets/javascripts/zuora_connect/application.js +13 -0
- data/app/assets/stylesheets/zuora_connect/api/v1/app_instance.css +4 -0
- data/app/assets/stylesheets/zuora_connect/application.css +15 -0
- data/app/controllers/zuora_connect/admin/tenant_controller.rb +11 -0
- data/app/controllers/zuora_connect/api/v1/app_instance_controller.rb +58 -0
- data/app/controllers/zuora_connect/application_controller.rb +8 -0
- data/app/controllers/zuora_connect/static_controller.rb +58 -0
- data/app/helpers/zuora_connect/api/v1/app_instance_helper.rb +4 -0
- data/app/helpers/zuora_connect/application_helper.rb +5 -0
- data/app/models/zuora_connect/app_instance.rb +5 -0
- data/app/models/zuora_connect/app_instance_base.rb +952 -0
- data/app/models/zuora_connect/login.rb +36 -0
- data/app/models/zuora_connect/telegraf.rb +93 -0
- data/app/views/layouts/zuora_connect/application.html.erb +14 -0
- data/app/views/sql/refresh_aggregate_table.txt +85 -0
- data/app/views/zuora_connect/static/invalid_app_instance_error.html.erb +65 -0
- data/app/views/zuora_connect/static/invalid_launch_request.html +65 -0
- data/app/views/zuora_connect/static/launch.html.erb +80 -0
- data/app/views/zuora_connect/static/session_error.html.erb +63 -0
- data/config/initializers/apartment.rb +95 -0
- data/config/initializers/aws.rb +2 -0
- data/config/initializers/elastic_apm.rb +25 -0
- data/config/initializers/object_method_hooks.rb +27 -0
- data/config/initializers/postgresql_adapter.rb +32 -0
- data/config/initializers/prometheus.rb +40 -0
- data/config/initializers/redis.rb +13 -0
- data/config/initializers/resque.rb +22 -0
- data/config/initializers/to_bool.rb +24 -0
- data/config/initializers/unicorn.rb +9 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20100718151733_create_connect_app_instances.rb +9 -0
- data/db/migrate/20101024162319_add_tokens_to_app_instance.rb +6 -0
- data/db/migrate/20101024220705_add_token_to_app_instance.rb +5 -0
- data/db/migrate/20110131211919_add_sessions_table.rb +13 -0
- data/db/migrate/20110411200303_add_expiration_to_app_instance.rb +5 -0
- data/db/migrate/20110413191512_add_new_api_token.rb +5 -0
- data/db/migrate/20110503003602_add_catalog_data_to_app_instance.rb +6 -0
- data/db/migrate/20110503003603_add_catalog_mappings_to_app_instance.rb +5 -0
- data/db/migrate/20110503003604_catalog_default.rb +5 -0
- data/db/migrate/20180301052853_add_catalog_attempted_at.rb +5 -0
- data/db/migrate/20181206162339_add_fields_to_instance.rb +5 -0
- data/lib/metrics/influx/point_value.rb +79 -0
- data/lib/metrics/net.rb +218 -0
- data/lib/middleware/metrics_middleware.rb +134 -0
- data/lib/resque/additions.rb +53 -0
- data/lib/resque/dynamic_queues.rb +222 -0
- data/lib/resque/plugins/custom_logger.rb +46 -0
- data/lib/resque/self_lookup.rb +19 -0
- data/lib/resque/silence_done.rb +71 -0
- data/lib/tasks/zuora_connect_tasks.rake +24 -0
- data/lib/zuora_connect.rb +42 -0
- data/lib/zuora_connect/configuration.rb +53 -0
- data/lib/zuora_connect/controllers/helpers.rb +261 -0
- data/lib/zuora_connect/engine.rb +34 -0
- data/lib/zuora_connect/exceptions.rb +67 -0
- data/lib/zuora_connect/railtie.rb +63 -0
- data/lib/zuora_connect/version.rb +3 -0
- data/lib/zuora_connect/views/helpers.rb +9 -0
- data/test/controllers/zuora_connect/api/v1/app_instance_controller_test.rb +13 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/fixtures/zuora_connect/app_instances.yml +11 -0
- data/test/integration/navigation_test.rb +8 -0
- data/test/lib/generators/zuora_connect/datatable_generator_test.rb +16 -0
- data/test/models/zuora_connect/app_instance_test.rb +9 -0
- data/test/test_helper.rb +21 -0
- data/test/zuora_connect_test.rb +7 -0
- metadata +443 -0
data/lib/metrics/net.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# require 'http_logger'
|
8
|
+
#
|
9
|
+
# == Setup logger
|
10
|
+
#
|
11
|
+
# HttpLogger.logger = Logger.new('/tmp/all.log')
|
12
|
+
# HttpLogger.log_headers = true
|
13
|
+
#
|
14
|
+
# == Do request
|
15
|
+
#
|
16
|
+
# res = Net::HTTP.start(url.host, url.port) { |http|
|
17
|
+
# http.request(req)
|
18
|
+
# }
|
19
|
+
# ...
|
20
|
+
#
|
21
|
+
# == View the log
|
22
|
+
#
|
23
|
+
# cat /tmp/all.log
|
24
|
+
class HttpLogger
|
25
|
+
class << self
|
26
|
+
attr_accessor :collapse_body_limit
|
27
|
+
attr_accessor :log_headers
|
28
|
+
attr_accessor :log_request_body
|
29
|
+
attr_accessor :log_response_body
|
30
|
+
attr_accessor :logger
|
31
|
+
attr_accessor :colorize
|
32
|
+
attr_accessor :ignore
|
33
|
+
attr_accessor :level
|
34
|
+
end
|
35
|
+
|
36
|
+
self.log_headers = false
|
37
|
+
self.log_request_body = true
|
38
|
+
self.log_response_body = true
|
39
|
+
self.colorize = true
|
40
|
+
self.collapse_body_limit = 5000
|
41
|
+
self.ignore = []
|
42
|
+
self.level = :debug
|
43
|
+
|
44
|
+
def self.perform(*args, &block)
|
45
|
+
instance.perform(*args, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.instance
|
49
|
+
@instance ||= HttpLogger.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.deprecate_config(option)
|
53
|
+
warn "Net::HTTP.#{option} is deprecated. Use HttpLogger.#{option} instead."
|
54
|
+
end
|
55
|
+
|
56
|
+
def perform(http, request, request_body)
|
57
|
+
start_time = Time.now
|
58
|
+
tags = {}
|
59
|
+
response = yield
|
60
|
+
rescue => ex
|
61
|
+
tags = tags.merge({error_type: ex.class})
|
62
|
+
raise
|
63
|
+
ensure
|
64
|
+
values = {response_time: ((Time.now - start_time)*1000).round(2)}
|
65
|
+
if require_logging?(http, request)
|
66
|
+
tags = tags.merge({endpoint: http.address, method: request.method})
|
67
|
+
log_request_url(http, request, start_time)
|
68
|
+
log_request_body(request)
|
69
|
+
log_request_headers(request)
|
70
|
+
if defined?(response) && response
|
71
|
+
tags = tags.merge({status: response.code.to_i})
|
72
|
+
log_response_code(response)
|
73
|
+
log_response_headers(response)
|
74
|
+
log_response_body(response.body)
|
75
|
+
end
|
76
|
+
ZuoraConnect::AppInstance.write_to_telegraf(direction: :outbound, tags: tags, values: values)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def log_request_url(http, request, start_time)
|
83
|
+
ofset = Time.now - start_time
|
84
|
+
log("HTTP #{request.method} (%0.2fms)" % (ofset * 1000), request_url(http, request))
|
85
|
+
end
|
86
|
+
|
87
|
+
def request_url(http, request)
|
88
|
+
URI.decode("http#{"s" if http.use_ssl?}://#{http.address}:#{http.port}#{request.path}")
|
89
|
+
end
|
90
|
+
|
91
|
+
def log_request_headers(request)
|
92
|
+
if self.class.log_headers
|
93
|
+
request.each_capitalized { |k,v| log("HTTP request header", "#{k}: #{v}") }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
HTTP_METHODS_WITH_BODY = Set.new(%w(POST PUT GET PATCH))
|
98
|
+
|
99
|
+
def log_request_body(request)
|
100
|
+
if self.class.log_request_body
|
101
|
+
if HTTP_METHODS_WITH_BODY.include?(request.method)
|
102
|
+
if (body = request.body) && !body.empty?
|
103
|
+
log("Request body", truncate_body(body))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def log_response_code(response)
|
110
|
+
log("Response status", "#{response.class} (#{response.code})")
|
111
|
+
end
|
112
|
+
|
113
|
+
def log_response_headers(response)
|
114
|
+
if self.class.log_headers
|
115
|
+
response.each_capitalized { |k,v| log("HTTP response header", "#{k}: #{v}") }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def log_response_body(body)
|
120
|
+
if self.class.log_response_body
|
121
|
+
if body.is_a?(Net::ReadAdapter)
|
122
|
+
log("Response body", "<impossible to log>")
|
123
|
+
else
|
124
|
+
if body && !body.empty?
|
125
|
+
log("Response body", truncate_body(body))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def require_logging?(http, request)
|
132
|
+
self.logger && !ignored?(http, request) && (http.started? || fakeweb?(http, request))
|
133
|
+
end
|
134
|
+
|
135
|
+
def ignored?(http, request)
|
136
|
+
url = request_url(http, request)
|
137
|
+
self.class.ignore.any? do |pattern|
|
138
|
+
url =~ pattern
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def fakeweb?(http, request)
|
143
|
+
return false unless defined?(::FakeWeb)
|
144
|
+
uri = ::FakeWeb::Utility.request_uri_as_string(http, request)
|
145
|
+
method = request.method.downcase.to_sym
|
146
|
+
::FakeWeb.registered_uri?(method, uri)
|
147
|
+
end
|
148
|
+
|
149
|
+
def truncate_body(body)
|
150
|
+
if collapse_body_limit && collapse_body_limit > 0 && body && body.size >= collapse_body_limit
|
151
|
+
body_piece_size = collapse_body_limit / 2
|
152
|
+
body[0..body_piece_size] +
|
153
|
+
"\n\n<some data truncated>\n\n" +
|
154
|
+
body[(body.size - body_piece_size)..body.size]
|
155
|
+
else
|
156
|
+
body
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def log(message, dump)
|
161
|
+
self.logger.send(self.class.level, format_log_entry(message, dump)) if Rails.env.to_sym == :development
|
162
|
+
end
|
163
|
+
|
164
|
+
def format_log_entry(message, dump = nil)
|
165
|
+
if self.class.colorize
|
166
|
+
message_color, dump_color = "4;32;1", "0;1"
|
167
|
+
log_entry = " \e[#{message_color}m#{message}\e[0m "
|
168
|
+
log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
|
169
|
+
log_entry
|
170
|
+
else
|
171
|
+
"%s %s" % [message, dump]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def logger
|
176
|
+
self.class.logger
|
177
|
+
end
|
178
|
+
|
179
|
+
def collapse_body_limit
|
180
|
+
self.class.collapse_body_limit
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class Net::HTTP
|
185
|
+
|
186
|
+
def self.log_headers=(value)
|
187
|
+
HttpLogger.deprecate_config("log_headers")
|
188
|
+
HttpLogger.log_headers = value
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.colorize=(value)
|
192
|
+
HttpLogger.deprecate_config("colorize")
|
193
|
+
HttpLogger.colorize = value
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.logger=(value)
|
197
|
+
HttpLogger.deprecate_config("logger")
|
198
|
+
HttpLogger.logger = value
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
alias_method :request_without_logging, :request
|
203
|
+
|
204
|
+
def request(request, body = nil, &block)
|
205
|
+
HttpLogger.perform(self, request, body) do
|
206
|
+
request_without_logging(request, body, &block)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
if defined?(Rails)
|
212
|
+
if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
|
213
|
+
# Rails3
|
214
|
+
ActiveSupport.on_load(:after_initialize) do
|
215
|
+
HttpLogger.logger = Rails.logger unless HttpLogger.logger
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Middleware
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
# Object of this class is passed to the ActiveSupport::Notification hook
|
5
|
+
class PageRequest
|
6
|
+
|
7
|
+
# This method is triggered when a non error page is loaded (not 404)
|
8
|
+
def call(name, started, finished, unique_id, payload)
|
9
|
+
# If the url contains any css or JavaScript files then do not collect metrics for them
|
10
|
+
return nil if ["css", "assets", "jpg", "png", "jpeg", "ico"].any? { |word| payload[:path].include?(word) }
|
11
|
+
|
12
|
+
# Getting the endpoint and the content_type
|
13
|
+
content_hash = {:html => "text/html", :js => "application/javascript", :json => "application/json", :csv => "text/csv"}
|
14
|
+
content_type = content_hash.key?(payload[:format]) ? content_hash[payload[:format]] : payload[:format]
|
15
|
+
content_type = content_type.to_s.gsub('text/javascript', 'application/javascript')
|
16
|
+
|
17
|
+
# payloads with 500 requests do not have status as it is not set by the controller
|
18
|
+
# https://github.com/rails/rails/issues/33335
|
19
|
+
#status_code = payload[:status] ? payload[:status] : payload[:exception_object].present? ? 500 : ""
|
20
|
+
if payload[:exception].present?
|
21
|
+
status_code, exception = [500, payload[:exception].first]
|
22
|
+
else
|
23
|
+
status_code, exception = [payload[:status], nil]
|
24
|
+
end
|
25
|
+
|
26
|
+
tags = {method: payload[:method], status: status_code, error_type: exception, content_type: content_type, controller: payload[:controller], action: payload[:action]}.compact
|
27
|
+
|
28
|
+
values = {view_time: payload[:view_runtime], db_time: payload[:db_runtime], response_time: ((finished-started)*1000)}.compact
|
29
|
+
values = values.map{ |k,v| [k,v.round(2)]}.to_h
|
30
|
+
|
31
|
+
ZuoraConnect::AppInstanceBase.write_to_telegraf(direction: :inbound, tags: tags, values: values)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class MetricsMiddleware
|
36
|
+
|
37
|
+
require "zuora_connect/version"
|
38
|
+
require "zuora_api/version"
|
39
|
+
|
40
|
+
def initialize(app)
|
41
|
+
@app = app
|
42
|
+
end
|
43
|
+
|
44
|
+
def call(env)
|
45
|
+
@bad_headers = ["HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED_HOST", "HTTP_X_FORWARDED_PORT", "HTTP_X_FORWARDED_PROTO", "HTTP_X_FORWARDED_SCHEME", "HTTP_X_FORWARDED_SSL"]
|
46
|
+
if !ActionDispatch::Request::HTTP_METHODS.include?(env["REQUEST_METHOD"].upcase)
|
47
|
+
[405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]]
|
48
|
+
else
|
49
|
+
if (env['HTTP_ZUORA_LAYOUT_FETCH_TEMPLATE_ID'].present?)
|
50
|
+
Thread.current[:isHallway] = "/#{env['HTTP_ZUORA_LAYOUT_FETCH_TEMPLATE_ID']}"
|
51
|
+
env['PATH_INFO'] = env['PATH_INFO'].gsub(Thread.current[:isHallway], '')
|
52
|
+
env['REQUEST_URI'] = env['REQUEST_URI'].gsub(Thread.current[:isHallway], '')
|
53
|
+
env['REQUEST_PATH'] = env['REQUEST_PATH'].gsub(Thread.current[:isHallway], '')
|
54
|
+
|
55
|
+
#We need the forwarded host header to identify location of tenant
|
56
|
+
@bad_headers.delete('HTTP_X_FORWARDED_HOST')
|
57
|
+
else
|
58
|
+
Thread.current[:isHallway] = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
#Remove bad headers
|
62
|
+
@bad_headers.each { |header| env.delete(header) }
|
63
|
+
|
64
|
+
#Thread.current[:appinstance] = nil
|
65
|
+
start_time = Time.now
|
66
|
+
begin
|
67
|
+
@status, @headers, @response = @app.call(env)
|
68
|
+
ensure
|
69
|
+
|
70
|
+
# If the url contains any CSS or JavaScript files then do not collect metrics for them
|
71
|
+
if ["css", "assets", "jpg", "png", "jpeg", "ico"].any? { |word| env['PATH_INFO'].include?(word) } || /.*\.js$/.match(env['PATH_INFO'])
|
72
|
+
tags = {status: @status, controller: 'ActionController', action: 'Assets', app_instance: 0}
|
73
|
+
values = {response_time: ((Time.now - start_time)*1000).round(2) }
|
74
|
+
ZuoraConnect::AppInstanceBase.write_to_telegraf(direction: 'request-inbound-assets', tags: tags, values: values)
|
75
|
+
end
|
76
|
+
|
77
|
+
if defined? Prometheus
|
78
|
+
#Prometheus Stuff
|
79
|
+
if env['PATH_INFO'] == '/connect/internal/metrics'
|
80
|
+
|
81
|
+
#Do something before each scrape
|
82
|
+
if defined? Resque.redis
|
83
|
+
begin
|
84
|
+
|
85
|
+
Resque.redis.ping
|
86
|
+
|
87
|
+
Prometheus::REDIS_CONNECTION.set({connection:'redis',name: ZuoraConnect::Telegraf.app_name},1)
|
88
|
+
Prometheus::FINISHED_JOBS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:processed])
|
89
|
+
Prometheus::PENDING_JOBS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:pending])
|
90
|
+
Prometheus::ACTIVE_WORKERS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:working])
|
91
|
+
Prometheus::WORKERS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:workers])
|
92
|
+
Prometheus::FAILED_JOBS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:failed])
|
93
|
+
|
94
|
+
rescue Redis::CannotConnectError
|
95
|
+
Prometheus::REDIS_CONNECTION.set({connection:'redis',name: ZuoraConnect::Telegraf.app_name},0)
|
96
|
+
end
|
97
|
+
|
98
|
+
if ZuoraConnect.configuration.custom_prometheus_update_block != nil
|
99
|
+
ZuoraConnect.configuration.custom_prometheus_update_block.call()
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Uncomment following block of code for handling engine requests/requests without controller
|
107
|
+
# else
|
108
|
+
# # Handling requests which do not have controllers (engines)
|
109
|
+
if env["SCRIPT_NAME"].present?
|
110
|
+
controller_path = "#{env['SCRIPT_NAME'][1..-1]}"
|
111
|
+
controller_path = controller_path.sub("/", "::")
|
112
|
+
request_path = "#{controller_path}#UnknownAction"
|
113
|
+
else
|
114
|
+
# Writing to telegraf: Handle 404
|
115
|
+
if [404, 500].include?(@status)
|
116
|
+
content_type = @headers['Content-Type'].split(';')[0] if @headers['Content-Type']
|
117
|
+
content_type = content_type.gsub('text/javascript', 'application/javascript')
|
118
|
+
tags = {status: @status, content_type: content_type}
|
119
|
+
|
120
|
+
tags = tags.merge({controller: 'ActionController'})
|
121
|
+
tags = tags.merge({action: 'RoutingError' }) if @status == 404
|
122
|
+
|
123
|
+
values = {response_time: ((Time.now - start_time)*1000).round(2) }
|
124
|
+
|
125
|
+
ZuoraConnect::AppInstanceBase.write_to_telegraf(direction: :inbound, tags: tags, values: values)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
Thread.current[:inbound_metric] = nil
|
129
|
+
end
|
130
|
+
[@status, @headers, @response]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Resque
|
2
|
+
module Additions
|
3
|
+
def dequeue_from(queue, klass, *args)
|
4
|
+
####### ------ Resque Job --------
|
5
|
+
# Perform before_dequeue hooks. Don't perform dequeue if any hook returns false
|
6
|
+
before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook|
|
7
|
+
klass.send(hook, *args)
|
8
|
+
end
|
9
|
+
return if before_hooks.any? { |result| result == false }
|
10
|
+
|
11
|
+
destroyed = Job.destroy(queue, klass, *args)
|
12
|
+
|
13
|
+
Plugin.after_dequeue_hooks(klass).each do |hook|
|
14
|
+
klass.send(hook, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
destroyed
|
18
|
+
end
|
19
|
+
|
20
|
+
####### ------ Resque Delayed Job --------
|
21
|
+
# Returns delayed jobs schedule timestamp for +klass+, +args+.
|
22
|
+
def scheduled_at_with_queue(queue, klass, *args)
|
23
|
+
search = encode(job_to_hash_with_queue(queue,klass, args))
|
24
|
+
redis.smembers("timestamps:#{search}").map do |key|
|
25
|
+
key.tr('delayed:', '').to_i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Given an encoded item, remove it from the delayed_queue
|
30
|
+
def remove_delayed_with_queue(queue, klass, *args)
|
31
|
+
search = encode(job_to_hash_with_queue(queue,klass, args))
|
32
|
+
remove_delayed_job(search)
|
33
|
+
end
|
34
|
+
|
35
|
+
#Given a timestamp and job (klass + args) it removes all instances and
|
36
|
+
# returns the count of jobs removed.
|
37
|
+
#
|
38
|
+
# O(N) where N is the number of jobs scheduled to fire at the given
|
39
|
+
# timestamp
|
40
|
+
def remove_delayed_job_with_queue_from_timestamp(timestamp, queue, klass, *args)
|
41
|
+
return 0 if Resque.inline?
|
42
|
+
|
43
|
+
key = "delayed:#{timestamp.to_i}"
|
44
|
+
encoded_job = encode(job_to_hash_with_queue(queue, klass, args))
|
45
|
+
|
46
|
+
redis.srem("timestamps:#{encoded_job}", key)
|
47
|
+
count = redis.lrem(key, 0, encoded_job)
|
48
|
+
clean_up_timestamp(key, timestamp)
|
49
|
+
|
50
|
+
count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module Resque
|
2
|
+
module DynamicQueues
|
3
|
+
def filter_busy_queues qs
|
4
|
+
busy_queues = Resque::Worker.working.map { |worker| worker.job["queue"] }.compact
|
5
|
+
Array(qs.dup).compact - busy_queues
|
6
|
+
end
|
7
|
+
|
8
|
+
def rotated_queues
|
9
|
+
@n ||= 0
|
10
|
+
@n += 1
|
11
|
+
rot_queues = queues # since we rely on the resque-dynamic-queues plugin, this is all the queues, expanded out
|
12
|
+
if rot_queues.size > 0
|
13
|
+
@n = @n % rot_queues.size
|
14
|
+
rot_queues.rotate(@n)
|
15
|
+
else
|
16
|
+
rot_queues
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def queue_depth queuename
|
21
|
+
busy_queues = Resque::Worker.working.map { |worker| worker.job["queue"] }.compact
|
22
|
+
# find the queuename, count it.
|
23
|
+
busy_queues.select {|q| q == queuename }.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_categorized_queues(queue_list)
|
27
|
+
priority_map = {"Synchronous" => 0, "High" => 1, "Medium" => 2, "Low" => 3}
|
28
|
+
categorized_queues = {}
|
29
|
+
for queue in queue_list.uniq
|
30
|
+
priority = queue.split("_")[1]
|
31
|
+
priority = "Medium" if !["Synchronous", "High", "Medium", "Low"].include?(priority)
|
32
|
+
categorized_queues[priority] ||= []
|
33
|
+
categorized_queues[priority].push(queue)
|
34
|
+
end
|
35
|
+
return categorized_queues.transform_keys{ |key| priority_map[key.to_s]}.sort
|
36
|
+
end
|
37
|
+
|
38
|
+
DEFAULT_QUEUE_DEPTH = 0
|
39
|
+
def should_work_on_queue? queuename
|
40
|
+
return true if @queues.include? '*' # workers with QUEUES=* are special and are not subject to queue depth setting
|
41
|
+
max = DEFAULT_QUEUE_DEPTH
|
42
|
+
unless ENV["RESQUE_QUEUE_DEPTH"].nil? || ENV["RESQUE_QUEUE_DEPTH"] == ""
|
43
|
+
max = ENV["RESQUE_QUEUE_DEPTH"].to_i
|
44
|
+
end
|
45
|
+
return true if max == 0 # 0 means no limiting
|
46
|
+
cur_depth = queue_depth(queuename)
|
47
|
+
log! "queue #{queuename} depth = #{cur_depth} max = #{max}"
|
48
|
+
return true if cur_depth < max
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_grouped_queues
|
53
|
+
self.queues.sort.group_by{|u| /(\d{1,20})_.*/.match(u) ? /(\d{1,20})_.*/.match(u).captures.first : nil}
|
54
|
+
end
|
55
|
+
|
56
|
+
def reserve_with_round_robin
|
57
|
+
grouped_queues = self.get_grouped_queues
|
58
|
+
|
59
|
+
#Instance queue grouping
|
60
|
+
if !grouped_queues.keys.include?(nil) && grouped_queues.keys.size > 0
|
61
|
+
if ZuoraConnect.configuration.blpop_queue
|
62
|
+
@job_in_progress = get_restricted_job
|
63
|
+
return @job_in_progress if @job_in_progress.present?
|
64
|
+
return @job_in_progress = get_queued_job(grouped_queues)
|
65
|
+
else
|
66
|
+
@n ||= 0
|
67
|
+
@n += 1
|
68
|
+
@n = @n % grouped_queues.keys.size
|
69
|
+
grouped_queues.keys.rotate(@n).each do |key|
|
70
|
+
self.get_categorized_queues(grouped_queues[key]).each do |key, queues|
|
71
|
+
queues.each do |queue|
|
72
|
+
log! "Checking #{queue}"
|
73
|
+
if should_work_on_queue?(queue) && @job_in_progress = Resque::Job.reserve(queue)
|
74
|
+
log! "Found job on #{queue}"
|
75
|
+
return @job_in_progress
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@n += 1 # Start the next search at the queue after the one from which we pick a job.
|
80
|
+
end
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
else
|
84
|
+
return reserve_without_round_robin
|
85
|
+
end
|
86
|
+
|
87
|
+
rescue Exception => e
|
88
|
+
log "Error reserving job: #{e.inspect}"
|
89
|
+
log e.backtrace.join("\n")
|
90
|
+
raise e
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_job(queue, payload)
|
94
|
+
return unless payload
|
95
|
+
Resque::Job.new(queue, payload)
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_next_job(grouped_queues)
|
99
|
+
@n ||= 1
|
100
|
+
queue_index = {}
|
101
|
+
grouped_queues.each_with_index do |(key, queue_list), index|
|
102
|
+
queue_list.each do |queue|
|
103
|
+
queue_index[queue] = index
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
grouped_queues = grouped_queues.values.rotate(@n).map{|queue_list| get_categorized_queues(queue_list).to_h.values.flatten}.flatten.delete_if{|queue| !should_work_on_queue?(queue)}.map{|queue| "queue:#{queue}"}
|
108
|
+
queue, payload = Resque.redis.blpop(grouped_queues, :timeout => (ENV["BLPOP_TIMEOUT"].to_i || 30))
|
109
|
+
return nil if queue.blank?
|
110
|
+
|
111
|
+
queue = queue.split("queue:")[1]
|
112
|
+
@n = queue_index[queue] + 1
|
113
|
+
return create_job(queue, Resque.decode(payload))
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_restricted_job
|
117
|
+
Resque::Plugins::ConcurrentRestrictionJob.next_runnable_job_random
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_queued_job(grouped_queues)
|
121
|
+
if defined?(Resque::Plugins::ConcurrentRestriction)
|
122
|
+
# Bounded retry
|
123
|
+
1.upto(Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts) do |i|
|
124
|
+
resque_job = get_next_job(grouped_queues)
|
125
|
+
|
126
|
+
# Short-curcuit if a job was not found
|
127
|
+
return if resque_job.nil?
|
128
|
+
|
129
|
+
# If there is a job on regular queues, then only run it if its not restricted
|
130
|
+
job_class = resque_job.payload_class
|
131
|
+
job_args = resque_job.args
|
132
|
+
|
133
|
+
# Return to work on job if not a restricted job
|
134
|
+
return resque_job unless job_class.is_a?(Resque::Plugins::ConcurrentRestriction)
|
135
|
+
|
136
|
+
# Keep trying if job is restricted. If job is runnable, we keep the lock until
|
137
|
+
# done_working
|
138
|
+
return resque_job unless job_class.stash_if_restricted(resque_job)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Safety net, here in case we hit the upper bound and there are still queued items
|
142
|
+
return nil
|
143
|
+
else
|
144
|
+
return get_next_job(grouped_queues)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns a list of queues to use when searching for a job.
|
149
|
+
#
|
150
|
+
# A splat ("*") means you want every queue (in alpha order) - this
|
151
|
+
# can be useful for dynamically adding new queues.
|
152
|
+
#
|
153
|
+
# The splat can also be used as a wildcard within a queue name,
|
154
|
+
# e.g. "*high*", and negation can be indicated with a prefix of "!"
|
155
|
+
#
|
156
|
+
# An @key can be used to dynamically look up the queue list for key from redis.
|
157
|
+
# If no key is supplied, it defaults to the worker's hostname, and wildcards
|
158
|
+
# and negations can be used inside this dynamic queue list. Set the queue
|
159
|
+
# list for a key with Resque.set_dynamic_queue(key, ["q1", "q2"]
|
160
|
+
#
|
161
|
+
def queues_with_dynamic
|
162
|
+
queue_names = @queues.dup
|
163
|
+
|
164
|
+
return queues_without_dynamic if queue_names.grep(/(^!)|(^@)|(\*)/).size == 0
|
165
|
+
|
166
|
+
real_queues = Resque.queues
|
167
|
+
matched_queues = []
|
168
|
+
|
169
|
+
#Remove Queues under Api Limits
|
170
|
+
Redis.current.zremrangebyscore("APILimits", "0", "(#{Time.now.to_i}")
|
171
|
+
api_limit_instances = Redis.current.zrange("APILimits", 0, -1).map {|key| key.to_i if key.match(/^\d*$/)}.compact
|
172
|
+
real_queues = real_queues.select {|key| key if !api_limit_instances.include?((key.match(/^(\d*)_.*/) || [])[1].to_i)} ## 2
|
173
|
+
|
174
|
+
#Queue Pausing
|
175
|
+
Resque.redis.zremrangebyscore("PauseQueue", "0", "(#{Time.now.to_i}")
|
176
|
+
paused_instances = Resque.redis.zrange("PauseQueue", 0, -1).map {|key| key.split("__")[0].to_i if key.match(/^\d*__.*/)}.compact
|
177
|
+
real_queues = real_queues.select {|key| key if !paused_instances.include?((key.match(/^(\d*)_.*/) || [])[1].to_i)}
|
178
|
+
|
179
|
+
while q = queue_names.shift
|
180
|
+
q = q.to_s
|
181
|
+
|
182
|
+
if q =~ /^(!)?@(.*)/
|
183
|
+
key = $2.strip
|
184
|
+
key = hostname if key.size == 0
|
185
|
+
|
186
|
+
add_queues = Resque.get_dynamic_queue(key)
|
187
|
+
add_queues.map! { |q| q.gsub!(/^!/, '') || q.gsub!(/^/, '!') } if $1
|
188
|
+
|
189
|
+
queue_names.concat(add_queues)
|
190
|
+
next
|
191
|
+
end
|
192
|
+
|
193
|
+
if q =~ /^!/
|
194
|
+
negated = true
|
195
|
+
q = q[1..-1]
|
196
|
+
end
|
197
|
+
|
198
|
+
patstr = q.gsub(/\*/, '.*')
|
199
|
+
pattern = /^#{patstr}$/
|
200
|
+
if negated
|
201
|
+
matched_queues -= matched_queues.grep(pattern)
|
202
|
+
else
|
203
|
+
matches = real_queues.grep(/^#{pattern}$/)
|
204
|
+
matches = [q] if matches.size == 0 && q == patstr
|
205
|
+
matched_queues.concat(matches.sort)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
return matched_queues.uniq
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
def self.included(receiver)
|
214
|
+
receiver.class_eval do
|
215
|
+
alias queues_without_dynamic queues
|
216
|
+
alias queues queues_with_dynamic
|
217
|
+
alias reserve_without_round_robin reserve
|
218
|
+
alias reserve reserve_with_round_robin
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|