marty 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb07735da359866d827f601b5dd6e88cb6867605
4
- data.tar.gz: b617fa0870da7065a59760ba74c7160c49334135
3
+ metadata.gz: bea1f7001c31f2ac05b9b5694618355e46661702
4
+ data.tar.gz: 6345a3b84a2319b1a25c2408dd134e371ff0eed1
5
5
  SHA512:
6
- metadata.gz: 1ba6ad619ea48c1429f1f1eb7776111008bf4bb4832b4ea2e83334e9963dfb598928b3d47394f2c9f0625adb3d1bac90ad3cd32ca87d1677f610c950c5ae049b
7
- data.tar.gz: b36df842fa6e092a2d698b6c299b42402bf8084b923075b2d691fcb3393c81104364558f57aa8de6b562a7221e5e78ef9dc6ad535aeac06d2c2b9502159f69b9
6
+ metadata.gz: f4ac10c766f9e4532065c95b75abc71b0d9d79fea51a83a3c3a0c5ba1bed72a287441d84e2c30efa1186ac8d1cc5ed109e9489d3aa557529e2091be723182486
7
+ data.tar.gz: dc8397f83c0acfbe180e3d7a7228b473d4e13bef61c5faf094abdb45d5dcac7cbfa419aa8d93944de57807f365130c5c28b676494f95813c3b31b19ffcdd3e10
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- marty (1.1.4)
4
+ marty (1.1.6)
5
5
  axlsx (= 2.1.0pre)
6
6
  coderay
7
7
  delorean_lang (~> 0.3.33)
@@ -1,433 +1,32 @@
1
- include ActionView::Helpers::TextHelper
2
- require 'erb'
3
-
4
1
  module Marty
5
2
  class DiagnosticController < ActionController::Base
6
- layout false
7
3
  def op
8
4
  begin
9
- # inject request object into base class of all diagnostics
10
- Base.request = request
11
-
12
- # determine if request is aggregate and return result
13
- params[:scope] = 'nodal' unless params[:scope]
14
- diag = self.class.get_sub_class(params[:op])
15
- @result = params[:scope] == 'local' ? diag.generate : diag.aggregate
5
+ @result = Diagnostic::Reporter.run(request)
16
6
  rescue NameError
17
7
  render file: 'public/400', formats: [:html], status: 400, layout: false
18
8
  else
19
9
  respond_to do |format|
20
- format.html {@result = diag.display(@result, params[:scope])}
21
- format.json {render json: @result}
10
+ format.html {@result = display_parameters}
11
+ format.json {render json: process_result_for_api}
22
12
  end
23
13
  end
24
14
  end
25
15
 
26
- def self.get_sub_class klass
27
- const_get(klass.downcase.camelize)
16
+ def process_result_for_api
17
+ @result.delete('data') unless request.params['data'] == 'true'
18
+ @result.delete('errors') if @result['errors'] && @result['errors'].empty?
19
+ @result
28
20
  end
29
21
 
30
- private
31
- ############################################################################
32
- #
33
- # Diagnostics
34
- #
35
- ############################################################################
36
- class Base
37
- @@request = nil
38
- @@read_only = Marty::Util.db_in_recovery?
39
- @@template = ActionController::Base.new.lookup_context.
40
- find_template("marty/diagnostic/diag").identifier
41
-
42
- def self.request
43
- @@request
44
- end
45
-
46
- def self.request= req
47
- @@request = req
48
- end
49
-
50
- def self.aggregate op_name=name.demodulize
51
- get_nodal_diags(op_name)
52
- end
53
-
54
- def self.get_nodal_diags op_name, scope='local'
55
- self.get_nodes.map do |n|
56
- ssl = ENV['HTTPS'] == 'on'
57
- uri = Addressable::URI.new(host: n, port: ssl ? 443 : @@request.port)
58
- uri.query_values = {op: op_name.underscore,
59
- scope: scope}
60
- uri.scheme = ssl ? 'https' : 'http'
61
- uri.path = '/marty/diag.json'
62
- opts = {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}
63
- {n => JSON.parse(open(uri, opts).readlines[0])}
64
- end.sum
65
- end
66
-
67
- def self.find_failures data
68
- data.each_with_object({}){
69
- |(k,v), h| h[k] = v.include?('Failure') ? 'F' : 'P'}
70
- end
71
-
72
- def self.errors data
73
- data.keys.count{|n| is_failure?(data[n])}
74
- end
75
-
76
- def self.diff data
77
- data.keys.map{|n| data[n]}.uniq.length != 1
78
- end
79
-
80
- def self.package message
81
- {name.demodulize => message}
82
- end
83
-
84
- def self.is_failure? message
85
- message.to_s.include?('Failure')
86
- end
87
-
88
- def self.error message
89
- "Failure: #{message}"
90
- end
91
-
92
- # determine "target" (highest) value for tests
93
- def self.get_targets data
94
- data.each_with_object({}) do |(_, v), h|
95
- v.each do |k, r|
96
- r = r.to_s
97
- h[k] ||= r
98
- h[k] = r if h[k] < r
99
- end
100
- end
101
- end
102
-
103
- def self.display data, type='nodal'
104
- data = {'local' => data} if type == 'local'
105
- ERB.new(File.open(@@template).read).result(binding)
106
- end
107
-
108
- def self.get_pg_connections
109
- info = ActiveRecord::Base.connection.execute("SELECT datname,"\
110
- "application_name,"\
111
- "state,"\
112
- "pid,"\
113
- "client_addr "\
114
- "FROM pg_stat_activity")
115
- info.each_with_object({}) do |x, h|
116
- h[x["datname"]] ||= []
117
- h[x["datname"]] << {"name" => x["application_name"],
118
- "address"=> x["client_addr"],
119
- "state" => x["state"],
120
- "pid" => x["pid"]}
121
- end
122
- end
123
-
124
- def self.resolve_target_nodes target
125
- db = ActiveRecord::Base.connection_config[:database]
126
- db_conns = get_pg_connections
127
- target_conns = db_conns[db].select{|x|
128
- x['name'].include? target}
129
- target_conns.map{|x| x['address']}.uniq.compact
130
- end
131
-
132
- def self.get_nodes
133
- nodes = resolve_target_nodes("Passenger")
134
- nodes.empty? ? ['127.0.0.1'] : nodes
135
- end
136
- end
137
- ############################################################################
138
- #
139
- # Diagnostic Definitions
140
- # Default: pulls from all nodes; force local with '&scope=local'
141
- #
142
- ############################################################################
143
- class Version < Base
144
- def self.generate
145
- begin
146
- message = `cd #{Rails.root.to_s}; git describe --tags --always;`.strip
147
- rescue
148
- message = error("Failed accessing git")
149
- end
150
- {
151
- 'Marty' => Marty::VERSION,
152
- 'Delorean' => Delorean::VERSION,
153
- 'Mcfly' => Mcfly::VERSION,
154
- 'Git' => message,
155
- }
156
- end
157
- end
158
-
159
- class Database < Base
160
- def self.db_server_name
161
- ActiveRecord::Base.connection_config[:host] || error('undefined')
162
- end
163
-
164
- def self.db_adapter_name
165
- ActiveRecord::Base.connection.adapter_name
166
- end
167
-
168
- def self.db_time
169
- ActiveRecord::Base.connection.execute('SELECT NOW();')
170
- end
171
-
172
- def self.db_version
173
- begin
174
- message = ActiveRecord::Base.connection.
175
- execute('SELECT VERSION();')[0]['version']
176
- rescue => e
177
- return error(message)
178
- end
179
- message
180
- end
181
-
182
- def self.db_schema
183
- begin
184
- current = ActiveRecord::Migrator.current_version
185
- needs_migration = ActiveRecord::Migrator.needs_migration?
186
- rescue => e
187
- return error(e.message)
188
- end
189
- needs_migration ? error("Migration is needed.\n"\
190
- "Current Version: #{current}") : current
191
- end
192
- end
193
-
194
- class Environment < Database
195
- def self.generate
196
- rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
197
- {
198
- 'Environment' => Rails.env,
199
- 'Rails' => Rails.version,
200
- 'Netzke Core' => Netzke::Core::VERSION,
201
- 'Netzke Basepack' => Netzke::Basepack::VERSION,
202
- 'Ruby' => rbv,
203
- 'RubyGems' => Gem::VERSION,
204
- 'Database Adapter' => db_adapter_name,
205
- 'Database Server' => db_server_name,
206
- 'Database Version' => db_version,
207
- 'Database Schema Version' => db_schema
208
- }
209
- end
210
- end
211
-
212
- class Nodes < Base
213
- def self.generate
214
- begin
215
- a_nodes = AwsInstanceInfo.new.nodes.sort if AwsInstanceInfo.is_aws?
216
- rescue => e
217
- a_nodes = [e.message]
218
- end
219
- pg_nodes = get_nodes.sort
220
- message = a_nodes.nil? || pg_nodes == a_nodes ? pg_nodes.join("\n") :
221
- error("There is a discrepancy between nodes connected to "\
222
- "Postgres and those discovered through AWS EC2.\n"\
223
- "Postgres: \n#{pg_nodes.join("\n")}\n"\
224
- "AWS: \n#{a_nodes.join("\n")}")
225
- {"PG/AWS" => message}
226
- end
227
- end
228
-
229
- class Env < Base
230
- def self.filter_env filter=''
231
- env = ENV.clone
232
-
233
- # obfuscate SECRET_KEY_BASE for comparison
234
- env['SECRET_KEY_BASE'] = env['SECRET_KEY_BASE'][0,4] if
235
- env['SECRET_KEY_BASE']
236
-
237
- # remove SCRIPT_URI, SCRIPT_URL as calling node differs
238
- ['SCRIPT_URI', 'SCRIPT_URL'].each{|k| env.delete(k)}
239
-
240
- to_block = ['PASSWORD', 'DEBUG']
241
- env.sort.each_with_object({}){|(k,v),h|
242
- h[k] = v if to_block.all?{|b| !k.include?(b)} && k.include?(filter)}
243
- end
244
-
245
- def self.generate
246
- filter_env
247
- end
248
-
249
- def self.aggregate
250
- envs = get_nodal_diags(name.demodulize)
251
- diff(envs) ? envs : package({})
252
- end
253
- end
254
-
255
- class DelayedJob < Base
256
- def self.delayed_job_count
257
- db = ActiveRecord::Base.connection_config[:database]
258
- get_pg_connections[db].count{|c| c['pid'] if
259
- c['name'].include?('delayed_job')}
260
- end
261
-
262
- def self.validate data
263
- data.each_with_object({}) do
264
- |(k,v), h|
265
- h[k] = v.count > 1 ? error("\n" + v.join("\n")) :
266
- v[0] != ENV['DELAYED_VER'] ? error(v[0]) : v[0]
267
- end
268
- end
269
-
270
- def self.generate
271
- count = delayed_job_count
272
- return {'Issue' => ['No delayed jobs are running.']} if count.zero?
273
-
274
- # we will only iterate by half of the total delayed workers to avoid
275
- # excess use of delayed job time
276
- count = (count/2).zero? ? 1 : count/2
277
-
278
- d_engine = Marty::ScriptSet.new.get_engine("Diagnostics")
279
- res = d_engine.evaluate('VersionDelay', 'result', {'count' => count-1})
280
-
281
- # merge results and remove duplicates
282
- res.each_with_object({}){
283
- |r, h|
284
- h[r[0]] ||= []
285
- h[r[0]] << r[1]
286
- }.each_with_object({}){|(k,v), h| h[k] = v.uniq}
287
- end
288
-
289
- def self.aggregate
290
- package(validate(generate))
291
- end
292
-
293
- def self.diff data
294
- data = data[name.demodulize] if data[name.demodulize]
295
- data.keys.map{|k| data[k]}.flatten.uniq.count != 1 ||
296
- data[data.keys[0]] != ENV['DELAYED_VER']
297
- end
298
- end
299
-
300
- ############################################################################
301
- #
302
- # Reports
303
- #
304
- ############################################################################
305
- class Report < Base
306
- class << self
307
- attr_accessor :diags
308
- end
309
-
310
- def diags
311
- self.class.diags
312
- end
313
-
314
- self.diags = ['nodes', 'version', 'environment']
315
-
316
- def self.get_diag_klass diag
317
- controller = name.split(name.demodulize)[0].constantize
318
- controller.const_get(diag.capitalize)
319
- end
320
-
321
- def self.generate
322
- diags.each_with_object({}){|d, h| h[d] = get_diag_klass(d).generate}
323
- end
324
-
325
- def self.aggregate
326
- diags.each_with_object({}){|d, h| h[d] = get_diag_klass(d).aggregate}
327
- end
328
-
329
- def self.display data, type
330
- report = '<h3>' +
331
- name.demodulize +
332
- " #{'(' + type + ')' if type == 'local'}" +
333
- '</h3>'
334
- displays = diags.map{|d| get_diag_klass(d).display(data[d], type)}
335
- ([report] + displays).sum
336
- end
337
- end
338
-
339
- ############################################################################
340
- #
341
- # AWS Helper Class
342
- #
343
- ############################################################################
344
- class AwsInstanceInfo
345
- attr_accessor :id, :doc, :role, :creds, :version, :host, :tag, :nodes
346
-
347
- # aws reserved host used to get instance meta-data
348
- META_DATA_HOST = '169.254.169.254'
349
-
350
- def self.is_aws?
351
- uri = URI.parse("http://#{META_DATA_HOST}")
352
- !(Net::HTTP.get(uri) rescue nil).nil?
353
- end
354
-
355
- def initialize
356
- @id = get_instance_id
357
- @doc = get_document
358
- @role = get_role
359
- @creds = get_credentials
360
- @host = "ec2.#{@doc['region']}.amazonaws.com"
361
- @version = '2016-11-15'
362
- @tag = get_tag
363
- @nodes = get_private_ips
364
- end
365
-
366
- private
367
- def query_meta_data query
368
- uri = URI.parse("http://#{META_DATA_HOST}/latest/meta-data/#{query}/")
369
- Net::HTTP.get(uri)
370
- end
371
-
372
- def query_dynamic query
373
- uri = URI.parse("http://#{META_DATA_HOST}/latest/dynamic/#{query}/")
374
- Net::HTTP.get(uri)
375
- end
376
-
377
- def get_instance_id
378
- query_meta_data('instance-id').to_s
379
- end
380
-
381
- def get_role
382
- query_meta_data('iam/security-credentials').to_s
383
- end
384
-
385
- def get_credentials
386
- JSON.parse(query_meta_data("iam/security-credentials/#{@role}"))
387
- end
388
-
389
- def get_document
390
- JSON.parse(query_dynamic('instance-identity/document'))
391
- end
392
-
393
- def ec2_req action, params = {}
394
- url = "https://#{@host}/?Action=#{action}&Version=#{@version}"
395
- params.each{|a, v| url += "&#{a}=#{v}"}
396
-
397
- sig = Aws::Sigv4::Signer.new(service: 'ec2',
398
- region: @doc['region'],
399
- access_key_id: @creds['AccessKeyId'],
400
- secret_access_key: @creds['SecretAccessKey'],
401
- session_token: @creds['Token'])
402
- signed_url = sig.presign_url(http_method:'GET', url: url)
403
-
404
- http = Net::HTTP.new(@host, 443)
405
- http.use_ssl = true
406
- Hash.from_xml(Net::HTTP.get(signed_url))["#{action}Response"]
407
- end
408
-
409
- def get_tag
410
- params = {'Filter.1.Name' => 'resource-id',
411
- 'Filter.1.Value.1' => get_instance_id,
412
- 'Filter.2.Name' => 'key',
413
- 'Filter.2.Value.1' => 'Name'}
414
- ec2_req('DescribeTags', params)['tagSet']['item']['value']
415
- end
416
-
417
- def get_instances
418
- params = {'Filter.1.Name' => 'tag-value',
419
- 'Filter.1.Value.1' => @tag}
420
- ec2_req('DescribeInstances', params)
421
- end
422
-
423
- def get_private_ips
424
- get_instances['reservationSet']['item'].map{
425
- |i|
426
- item = i['instancesSet']['item']
427
- item.is_a?(Array) ? item.map{|i| i['privateIpAddress']} :
428
- item['privateIpAddress']
429
- }.flatten
430
- end
22
+ def display_parameters
23
+ local = params[:scope] == 'local'
24
+ data = local ? @result : @result['data']
25
+ errors = local ? Diagnostic::Reporter.errors(data) : @result['errors']
26
+ {
27
+ 'display' => Diagnostic::Reporter.displays(data),
28
+ 'errors' => errors
29
+ }
431
30
  end
432
31
  end
433
32
  end