marty 1.0.44 → 1.0.46

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: 817a8f32d8a912597e8941cb60889a6f9e3723bc
4
- data.tar.gz: cccaf421c0216156db7c106b69577f0e363c4c58
3
+ metadata.gz: 0437c9ffb81afc74c8bd834226b6855064e0d8bf
4
+ data.tar.gz: 27f841b78da83b42493bb74e5a6d3ee5944ee3b0
5
5
  SHA512:
6
- metadata.gz: 030c85527c4809aeb5a18634425c5dc5924534b7f8b3f44fc83189adccfc7724b87b97e06cdeac5c5004b1301c4f9745918bcb47c8004ac0b07492d6e2fcc2b3
7
- data.tar.gz: dcbabb68d76e981e927eda2e9c8e9b3a31a4896ef66561f7bcaa1d553fc31f4b8e5d056b084b7199982e242f67938964ec59366b645588c7e266a97d9ea724aa
6
+ metadata.gz: 1ab061544b74d83b417815f07a85e3e8ee106a9ee03da67b0e54974b749dfdcb3a57387bc1e60f55a62fefc994ec15e5c59228d4ebe58e11999da2a7d9e816fb
7
+ data.tar.gz: a5466cb70f5908c1c2a93cf96b938fa10815f8b2683d0502a1293a04f75b943796f9bc0518ca180d2fdfe70eb3efcfdb2fe70f9353bf2e913a1e8ac14c6cdd17
data/Gemfile CHANGED
@@ -11,6 +11,9 @@ gem 'mime-types', '< 3.0', platforms: :ruby_19
11
11
  gem 'rails', '~> 4.2.1'
12
12
  gem 'pg', '~> 0.18.4'
13
13
  gem 'sqlite3'
14
+ # for signing of aws ec2 requests
15
+ gem 'aws-sigv4', '~> 1.0', '>= 1.0.2'
16
+
14
17
 
15
18
  group :development, :test do
16
19
  gem 'pry-rails'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- marty (1.0.44)
4
+ marty (1.0.46)
5
5
  axlsx (= 2.1.0pre)
6
6
  coderay
7
7
  delorean_lang (~> 0.1)
@@ -54,6 +54,7 @@ GEM
54
54
  addressable (2.5.2)
55
55
  public_suffix (>= 2.0.2, < 4.0)
56
56
  arel (6.0.4)
57
+ aws-sigv4 (1.0.2)
57
58
  axlsx (2.1.0.pre)
58
59
  htmlentities (~> 4.3.1)
59
60
  nokogiri (>= 1.4.1)
@@ -81,7 +82,7 @@ GEM
81
82
  delayed_job_active_record (4.1.2)
82
83
  activerecord (>= 3.0, < 5.2)
83
84
  delayed_job (>= 3.0, < 5)
84
- delorean_lang (0.3.30)
85
+ delorean_lang (0.3.32)
85
86
  activerecord (>= 3.2)
86
87
  treetop (~> 1.5)
87
88
  diff-lcs (1.3)
@@ -205,6 +206,7 @@ PLATFORMS
205
206
  ruby
206
207
 
207
208
  DEPENDENCIES
209
+ aws-sigv4 (~> 1.0, >= 1.0.2)
208
210
  capybara
209
211
  daemons (~> 1.1.9)
210
212
  database_cleaner
@@ -1,101 +1,353 @@
1
+ require 'erb'
1
2
  module Marty
2
3
  class DiagnosticController < ActionController::Base
3
- layout 'marty/diagnostic'
4
-
5
- def index
6
- if action_methods.include?(params[:testop].to_s)
7
- send(params[:testop])
8
- elsif params[:testop] == 'env'
9
- send('environment')
4
+ layout false
5
+ def op
6
+ begin
7
+ # inject request into Base class of all diagnostics
8
+ Base.request = request
9
+ params[:scope] = 'nodal' unless params[:scope]
10
+ diag = self.class.get_sub_class(params[:op])
11
+ @result = params[:scope] == 'local' ? diag.generate : diag.aggregate
12
+ rescue NameError
13
+ render file: 'public/400', formats: [:html], status: 400, layout: false
10
14
  else
11
- render file: 'public/404', status: 404, layout: false
15
+ respond_to do |format|
16
+ format.html {@result = diag.display(@result, params[:scope])}
17
+ format.json {render json: @result}
18
+ end
12
19
  end
13
20
  end
14
21
 
15
- def version
16
- diag_response git_details +
17
- [Diagnostic.new('Marty Version', true, VERSION)]
22
+ def self.get_sub_class klass
23
+ const_get(klass.downcase.camelize)
18
24
  end
19
25
 
20
- def environment
21
- rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
22
-
23
- details = [
24
- Diagnostic.new('Environment', true, Rails.env),
25
- Diagnostic.new('Rails Version', true, Rails.version),
26
- Diagnostic.new('Netzke Core Version', true, Netzke::Core::VERSION),
27
- Diagnostic.new('Netzke Basepack Version', true,
28
- Netzke::Basepack::VERSION),
29
- Diagnostic.new('Ruby Version', true, rbv),
30
- Diagnostic.new('RubyGems Version', true, Gem::VERSION),
31
- Diagnostic.new('Database Adapter', true,
32
- ActiveRecord::Base.connection.adapter_name)
33
- ]
34
- begin
35
- status = true
36
- result = ActiveRecord::Base.connection.execute('SELECT VERSION();')
37
- message = result[0]['version'] if result
38
- rescue => e
39
- status = false
40
- message = e.message
26
+ private
27
+ ############################################################################
28
+ #
29
+ # Diagnostics
30
+ #
31
+ ############################################################################
32
+ class Base
33
+ @@request = nil
34
+ @@read_only = Marty::Util.db_in_recovery?
35
+
36
+ def self.request= req
37
+ @@request = req
41
38
  end
42
- details << Diagnostic.new('Database Version', status, message)
43
39
 
44
- begin
45
- status, message = true, ActiveRecord::Migrator.current_version
46
- rescue => e
47
- status = false
48
- message = e.message
40
+ def self.request
41
+ @@request
42
+ end
43
+
44
+ def self.aggregate op_name=name.demodulize
45
+ get_nodal_diags(op_name)
46
+ end
47
+
48
+ def self.get_nodal_diags op_name, scope='local'
49
+ self.get_nodes.map do |n|
50
+ ssl = ENV['HTTPS'] == 'on'
51
+ uri = Addressable::URI.new(host: n, port: ssl ? 443 : @@request.port)
52
+ uri.query_values = {op: op_name.underscore,
53
+ scope: scope}
54
+ uri.scheme = ssl ? 'https' : 'http'
55
+ uri.path = '/marty/diag.json'
56
+ opts = {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}
57
+ {n => JSON.parse(open(uri, opts).readlines[0])}
58
+ end.sum
59
+ end
60
+
61
+ def self.errors data
62
+ data.keys.count{|n| is_failure?(data[n])}
63
+ end
64
+
65
+ def self.diff data
66
+ data.keys.map{|n| data[n]}.uniq.length != 1
67
+ end
68
+
69
+ def self.package message
70
+ {name.demodulize => message}
71
+ end
72
+
73
+ def self.is_failure? message
74
+ message.to_s.include?('Failure')
75
+ end
76
+
77
+ def self.error message
78
+ "Failure: (#{message})"
79
+ end
80
+
81
+ def self.display data, type='nodal'
82
+ data = {'local' => data} if type == 'local'
83
+ display = <<-ERB
84
+ <% inconsistent = diff(data) %>
85
+ <h3><%=name.demodulize%></h3>
86
+ <%='<h3 class="error">&#x26a0; Issues Detected</h3>' if
87
+ inconsistent%>
88
+ <div class="wrapper">
89
+ <% data.each do |node, result| %>
90
+ <table>
91
+ <% issues = ('error' if inconsistent) %>
92
+ <th class="<%=issues%>"><%=inconsistent ? node :
93
+ '<small>consistent</small>'%></th>
94
+ <th class="<%=issues%>"></th>
95
+ <% result.each do |name, value| %>
96
+ <tr class="<%=is_failure?(value) ? 'failed' :
97
+ 'passed' %>">
98
+ <td><%=name%></td>
99
+ <td class="overflow"><%=value%></td>
100
+ </tr>
101
+ <% end %>
102
+ </table>
103
+ <% break unless inconsistent %>
104
+ <% end %>
105
+ </div>
106
+ ERB
107
+ ERB.new(display.html_safe).result(binding)
108
+ end
109
+
110
+ def self.get_pg_connections
111
+ info = ActiveRecord::Base.connection.execute("SELECT datname,"\
112
+ "application_name,"\
113
+ "state,"\
114
+ "pid,"\
115
+ "client_addr "\
116
+ "FROM pg_stat_activity")
117
+ info.each_with_object({}) do |x, h|
118
+ h[x["datname"]] ||= []
119
+ h[x["datname"]] << {"name" => x["application_name"],
120
+ "address"=> x["client_addr"],
121
+ "state" => x["state"],
122
+ "pid" => x["pid"]}
123
+ end
124
+ end
125
+
126
+ def self.resolve_target_nodes target
127
+ db = ActiveRecord::Base.connection_config[:database]
128
+ db_conns = get_pg_connections
129
+ target_conns = db_conns[db].select{|x|
130
+ x['name'].include? target}
131
+ target_conns.map{|x| x['address']}.uniq.compact
132
+ end
133
+
134
+ def self.get_nodes
135
+ nodes = resolve_target_nodes("Passenger")
136
+ nodes.empty? ? ['127.0.0.1'] : nodes
137
+ end
138
+ end
139
+ ############################################################################
140
+ #
141
+ # Diagnostic Definitions
142
+ # Default: pulls from all nodes; force local with '&scope=local'
143
+ #
144
+ ############################################################################
145
+ class Version < Base
146
+ def self.generate
147
+ begin
148
+ message = `cd #{Rails.root.to_s}; git describe;`.strip
149
+ rescue
150
+ message = error("Failed accessing git")
151
+ end
152
+ {
153
+ 'Git' => message,
154
+ 'Marty' => Marty::VERSION,
155
+ 'Delorean' => Delorean::VERSION,
156
+ 'Mcfly' => Mcfly::VERSION
157
+ }
49
158
  end
50
- details << Diagnostic.new('Database Schema Version', status, message)
51
- diag_response details
52
159
  end
53
160
 
54
- private
55
- def diag_response details
56
- if @aggregate_diags
57
- @aggregated_details += details
58
- else
59
- @details = details
60
- respond_to do |format|
61
- format.html { render 'diagnostic' }
62
- format.json { render json: [{ error_count: error_count(details),
63
- diag_count: details.count }] + details }
161
+ class Database < Base
162
+ def self.db_server_name
163
+ ActiveRecord::Base.connection_config[:host] || 'undefined'
164
+ end
165
+
166
+ def self.db_adapter_name
167
+ ActiveRecord::Base.connection.adapter_name
168
+ end
169
+
170
+ def self.db_time
171
+ ActiveRecord::Base.connection.execute('SELECT NOW();')
172
+ end
173
+
174
+ def self.db_version
175
+ begin
176
+ message = ActiveRecord::Base.connection.
177
+ execute('SELECT VERSION();')[0]['version']
178
+ rescue => e
179
+ return error(message)
180
+ end
181
+ message
182
+ end
183
+
184
+ def self.db_schema
185
+ begin
186
+ message = ActiveRecord::Migrator.current_version
187
+ rescue => e
188
+ return error(e.message)
64
189
  end
190
+ message
65
191
  end
66
192
  end
67
193
 
68
- def aggregate_diags
69
- begin
70
- @aggregate_diags = true
71
- @aggregated_details = []
72
- yield
73
- ensure
74
- @aggregate_diags = false
75
- diag_response @aggregated_details
194
+ class Environment < Database
195
+ def self.generate
196
+ rbv = "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
197
+ infos = {'Environment' => Rails.env,
198
+ 'Rails' => Rails.version,
199
+ 'Netzke Core' => Netzke::Core::VERSION,
200
+ 'Netzke Basepack' => Netzke::Basepack::VERSION,
201
+ 'Ruby' => rbv,
202
+ 'RubyGems' => Gem::VERSION,
203
+ 'Database Adapter' => db_adapter_name,
204
+ 'Database Server' => db_server_name,
205
+ 'Database Version' => db_version,
206
+ 'Database Schema Version' => db_schema}
76
207
  end
77
208
  end
78
209
 
79
- def error_count details
80
- details.count { |detail| !detail.status }
210
+ class Nodes < Base
211
+ def self.generate
212
+ a_nodes = AwsInstanceInfo.is_aws? ? AwsInstanceInfo.new.nodes.sort : []
213
+ pg_nodes = get_nodes.sort
214
+ message = pg_nodes == a_nodes ? pg_nodes.join(', ') :
215
+ error("Postgres: [#{pg_nodes.join(', ')}]"\
216
+ " - AWS: [#{a_nodes.join(', ')}]")
217
+ {"PG/AWS" => message}
218
+ end
219
+
220
+ def self.aggregate
221
+ {'local' => generate}
222
+ end
81
223
  end
82
224
 
83
- def git_details app_name = Rails.application.class.parent.to_s
84
- [
85
- Diagnostic.new("#{app_name} Git Version", true,
86
- `git describe 2>&1`.strip),
87
- Diagnostic.new("#{app_name} Git Details", true,
88
- `git show --pretty=format:"sha: %h, %D" --no-patch 2>&1`.strip)
89
- ]
225
+ ############################################################################
226
+ #
227
+ # Reports
228
+ #
229
+ ############################################################################
230
+ class Report < Base
231
+ class << self
232
+ attr_accessor :diags
233
+ end
234
+
235
+ def diags
236
+ self.class.diags
237
+ end
238
+
239
+ self.diags = ['nodes', 'version', 'environment']
240
+
241
+ def self.get_diag_klass diag
242
+ controller = name.split(name.demodulize)[0].constantize
243
+ controller.const_get(diag.capitalize)
244
+ end
245
+
246
+ def self.generate
247
+ diags.each_with_object({}){|d, h| h[d] = get_diag_klass(d).generate}
248
+ end
249
+
250
+ def self.aggregate
251
+ diags.each_with_object({}){|d, h| h[d] = get_diag_klass(d).aggregate}
252
+ end
253
+
254
+ def self.display data, type
255
+ report = '<h3>' +
256
+ name.demodulize +
257
+ " #{'(' + type + ')' if type == 'local'}" +
258
+ '</h3>'
259
+ displays = diags.map{|d| get_diag_klass(d).display(data[d], type)}
260
+ ([report] + displays).sum
261
+ end
90
262
  end
91
263
 
92
- class Diagnostic < Struct.new(:name, :status, :description)
93
- def status_css
94
- status ? 'passed' : 'failed'
264
+ ############################################################################
265
+ #
266
+ # AWS Helper Class
267
+ #
268
+ ############################################################################
269
+ class AwsInstanceInfo
270
+ attr_accessor :id, :doc, :role, :creds, :version, :host, :tag, :nodes
271
+
272
+ # aws reserved host used to get instance meta-data
273
+ META_DATA_HOST = '169.254.169.254'
274
+
275
+ def self.is_aws?
276
+ uri = URI.parse("http://#{META_DATA_HOST}")
277
+ !(Net::HTTP.get(uri) rescue nil).nil?
278
+ end
279
+
280
+ def initialize
281
+ @id = get_instance_id
282
+ @doc = get_document
283
+ @role = get_role
284
+ @creds = get_credentials
285
+ @host = "ec2.#{@doc['region']}.amazonaws.com"
286
+ @version = '2016-11-15'
287
+ @tag = get_tag
288
+ @nodes = get_private_ips
289
+ end
290
+
291
+ private
292
+ def query_meta_data query
293
+ uri = URI.parse("http://#{META_DATA_HOST}/latest/meta-data/#{query}/")
294
+ Net::HTTP.get(uri)
295
+ end
296
+
297
+ def query_dynamic query
298
+ uri = URI.parse("http://#{META_DATA_HOST}/latest/dynamic/#{query}/")
299
+ Net::HTTP.get(uri)
300
+ end
301
+
302
+ def get_instance_id
303
+ query_meta_data('instance-id').to_s
304
+ end
305
+
306
+ def get_role
307
+ query_meta_data('iam/security-credentials').to_s
308
+ end
309
+
310
+ def get_credentials
311
+ JSON.parse(query_meta_data("iam/security-credentials/#{@role}"))
312
+ end
313
+
314
+ def get_document
315
+ JSON.parse(query_dynamic('instance-identity/document'))
316
+ end
317
+
318
+ def ec2_req action, params = {}
319
+ url = "https://#{@host}/?Action=#{action}&Version=#{@version}"
320
+ params.each{|a, v| url += "&#{a}=#{v}"}
321
+
322
+ sig = Aws::Sigv4::Signer.new(service: 'ec2',
323
+ region: @doc['region'],
324
+ access_key_id: @creds['AccessKeyId'],
325
+ secret_access_key: @creds['SecretAccessKey'],
326
+ session_token: @creds['Token'])
327
+ signed_url = sig.presign_url(http_method:'GET', url: url)
328
+
329
+ http = Net::HTTP.new(@host, 443)
330
+ http.use_ssl = true
331
+ Hash.from_xml(Net::HTTP.get(signed_url))["#{action}Response"]
332
+ end
333
+
334
+ def get_tag
335
+ params = {'Filter.1.Name' => 'resource-id',
336
+ 'Filter.1.Value.1' => get_instance_id,
337
+ 'Filter.2.Name' => 'key',
338
+ 'Filter.2.Value.1' => 'Name'}
339
+ ec2_req('DescribeTags', params)['tagSet']['item']['value']
340
+ end
341
+
342
+ def get_instances
343
+ params = {'Filter.1.Name' => 'tag-value',
344
+ 'Filter.1.Value.1' => @tag}
345
+ ec2_req('DescribeInstances', params)
95
346
  end
96
347
 
97
- def status_text
98
- status ? 'Passed' : 'Failed'
348
+ def get_private_ips
349
+ get_instances['reservationSet']['item'].
350
+ map{|i| i['instancesSet']['item']['privateIpAddress']}
99
351
  end
100
352
  end
101
353
  end