es-scout 5.3.0.es1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/AUTHORS +4 -0
  2. data/CHANGELOG +225 -0
  3. data/COPYING +340 -0
  4. data/INSTALL +18 -0
  5. data/LICENSE +6 -0
  6. data/README +66 -0
  7. data/Rakefile +74 -0
  8. data/TODO +6 -0
  9. data/bin/es-scout +10 -0
  10. data/data/cacert.pem +3154 -0
  11. data/data/code_key.pub +13 -0
  12. data/data/gpl-2.0.txt +339 -0
  13. data/data/lgpl-2.1.txt +504 -0
  14. data/lib/es-scout/command/install.rb +68 -0
  15. data/lib/es-scout/command/run.rb +56 -0
  16. data/lib/es-scout/command/test.rb +62 -0
  17. data/lib/es-scout/command/troubleshoot.rb +142 -0
  18. data/lib/es-scout/command.rb +258 -0
  19. data/lib/es-scout/plugin.rb +237 -0
  20. data/lib/es-scout/plugin_options.rb +80 -0
  21. data/lib/es-scout/scout_logger.rb +19 -0
  22. data/lib/es-scout/server.rb +578 -0
  23. data/lib/es-scout.rb +11 -0
  24. data/vendor/json_pure/CHANGES +162 -0
  25. data/vendor/json_pure/COPYING +58 -0
  26. data/vendor/json_pure/GPL +340 -0
  27. data/vendor/json_pure/README +358 -0
  28. data/vendor/json_pure/Rakefile +292 -0
  29. data/vendor/json_pure/TODO +1 -0
  30. data/vendor/json_pure/VERSION +1 -0
  31. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log +52 -0
  32. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast-autocorrelation.dat +1000 -0
  33. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast.dat +1001 -0
  34. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty-autocorrelation.dat +900 -0
  35. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty.dat +901 -0
  36. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe-autocorrelation.dat +1000 -0
  37. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe.dat +1001 -0
  38. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt.log +261 -0
  39. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast-autocorrelation.dat +1000 -0
  40. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast.dat +1001 -0
  41. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty-autocorrelation.dat +1000 -0
  42. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty.dat +1001 -0
  43. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe-autocorrelation.dat +1000 -0
  44. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe.dat +1001 -0
  45. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure.log +262 -0
  46. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator-autocorrelation.dat +1000 -0
  47. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator.dat +1001 -0
  48. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails.log +82 -0
  49. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkComparison.log +34 -0
  50. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser-autocorrelation.dat +900 -0
  51. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser.dat +901 -0
  52. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt.log +81 -0
  53. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser-autocorrelation.dat +1000 -0
  54. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser.dat +1001 -0
  55. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure.log +82 -0
  56. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser-autocorrelation.dat +1000 -0
  57. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser.dat +1001 -0
  58. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails.log +82 -0
  59. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser-autocorrelation.dat +1000 -0
  60. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser.dat +1001 -0
  61. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML.log +82 -0
  62. data/vendor/json_pure/benchmarks/generator2_benchmark.rb +222 -0
  63. data/vendor/json_pure/benchmarks/generator_benchmark.rb +224 -0
  64. data/vendor/json_pure/benchmarks/ohai.json +1216 -0
  65. data/vendor/json_pure/benchmarks/ohai.ruby +1 -0
  66. data/vendor/json_pure/benchmarks/parser2_benchmark.rb +251 -0
  67. data/vendor/json_pure/benchmarks/parser_benchmark.rb +259 -0
  68. data/vendor/json_pure/bin/edit_json.rb +9 -0
  69. data/vendor/json_pure/bin/prettify_json.rb +75 -0
  70. data/vendor/json_pure/data/example.json +1 -0
  71. data/vendor/json_pure/data/index.html +38 -0
  72. data/vendor/json_pure/data/prototype.js +4184 -0
  73. data/vendor/json_pure/ext/json/ext/generator/extconf.rb +16 -0
  74. data/vendor/json_pure/ext/json/ext/generator/generator.c +1341 -0
  75. data/vendor/json_pure/ext/json/ext/generator/generator.h +170 -0
  76. data/vendor/json_pure/ext/json/ext/parser/extconf.rb +15 -0
  77. data/vendor/json_pure/ext/json/ext/parser/parser.c +1935 -0
  78. data/vendor/json_pure/ext/json/ext/parser/parser.h +71 -0
  79. data/vendor/json_pure/ext/json/ext/parser/parser.rl +792 -0
  80. data/vendor/json_pure/install.rb +26 -0
  81. data/vendor/json_pure/lib/json/Array.xpm +21 -0
  82. data/vendor/json_pure/lib/json/FalseClass.xpm +21 -0
  83. data/vendor/json_pure/lib/json/Hash.xpm +21 -0
  84. data/vendor/json_pure/lib/json/Key.xpm +73 -0
  85. data/vendor/json_pure/lib/json/NilClass.xpm +21 -0
  86. data/vendor/json_pure/lib/json/Numeric.xpm +28 -0
  87. data/vendor/json_pure/lib/json/String.xpm +96 -0
  88. data/vendor/json_pure/lib/json/TrueClass.xpm +21 -0
  89. data/vendor/json_pure/lib/json/add/core.rb +148 -0
  90. data/vendor/json_pure/lib/json/add/rails.rb +58 -0
  91. data/vendor/json_pure/lib/json/common.rb +397 -0
  92. data/vendor/json_pure/lib/json/editor.rb +1371 -0
  93. data/vendor/json_pure/lib/json/ext.rb +15 -0
  94. data/vendor/json_pure/lib/json/json.xpm +1499 -0
  95. data/vendor/json_pure/lib/json/pure/generator.rb +452 -0
  96. data/vendor/json_pure/lib/json/pure/parser.rb +307 -0
  97. data/vendor/json_pure/lib/json/pure.rb +77 -0
  98. data/vendor/json_pure/lib/json/version.rb +8 -0
  99. data/vendor/json_pure/lib/json.rb +10 -0
  100. data/vendor/json_pure/tests/fixtures/fail1.json +1 -0
  101. data/vendor/json_pure/tests/fixtures/fail10.json +1 -0
  102. data/vendor/json_pure/tests/fixtures/fail11.json +1 -0
  103. data/vendor/json_pure/tests/fixtures/fail12.json +1 -0
  104. data/vendor/json_pure/tests/fixtures/fail13.json +1 -0
  105. data/vendor/json_pure/tests/fixtures/fail14.json +1 -0
  106. data/vendor/json_pure/tests/fixtures/fail18.json +1 -0
  107. data/vendor/json_pure/tests/fixtures/fail19.json +1 -0
  108. data/vendor/json_pure/tests/fixtures/fail2.json +1 -0
  109. data/vendor/json_pure/tests/fixtures/fail20.json +1 -0
  110. data/vendor/json_pure/tests/fixtures/fail21.json +1 -0
  111. data/vendor/json_pure/tests/fixtures/fail22.json +1 -0
  112. data/vendor/json_pure/tests/fixtures/fail23.json +1 -0
  113. data/vendor/json_pure/tests/fixtures/fail24.json +1 -0
  114. data/vendor/json_pure/tests/fixtures/fail25.json +1 -0
  115. data/vendor/json_pure/tests/fixtures/fail27.json +2 -0
  116. data/vendor/json_pure/tests/fixtures/fail28.json +2 -0
  117. data/vendor/json_pure/tests/fixtures/fail3.json +1 -0
  118. data/vendor/json_pure/tests/fixtures/fail4.json +1 -0
  119. data/vendor/json_pure/tests/fixtures/fail5.json +1 -0
  120. data/vendor/json_pure/tests/fixtures/fail6.json +1 -0
  121. data/vendor/json_pure/tests/fixtures/fail7.json +1 -0
  122. data/vendor/json_pure/tests/fixtures/fail8.json +1 -0
  123. data/vendor/json_pure/tests/fixtures/fail9.json +1 -0
  124. data/vendor/json_pure/tests/fixtures/pass1.json +56 -0
  125. data/vendor/json_pure/tests/fixtures/pass15.json +1 -0
  126. data/vendor/json_pure/tests/fixtures/pass16.json +1 -0
  127. data/vendor/json_pure/tests/fixtures/pass17.json +1 -0
  128. data/vendor/json_pure/tests/fixtures/pass2.json +1 -0
  129. data/vendor/json_pure/tests/fixtures/pass26.json +1 -0
  130. data/vendor/json_pure/tests/fixtures/pass3.json +6 -0
  131. data/vendor/json_pure/tests/test_json.rb +340 -0
  132. data/vendor/json_pure/tests/test_json_addition.rb +162 -0
  133. data/vendor/json_pure/tests/test_json_encoding.rb +68 -0
  134. data/vendor/json_pure/tests/test_json_fixtures.rb +34 -0
  135. data/vendor/json_pure/tests/test_json_generate.rb +122 -0
  136. data/vendor/json_pure/tests/test_json_rails.rb +144 -0
  137. data/vendor/json_pure/tests/test_json_unicode.rb +76 -0
  138. data/vendor/json_pure/tools/fuzz.rb +139 -0
  139. data/vendor/json_pure/tools/server.rb +61 -0
  140. metadata +233 -0
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require 'yaml'
4
+
5
+ module Scout
6
+ # a data structure of an individual plugin option
7
+ class PluginOption
8
+ attr_reader :name, :notes, :default, :advanced, :password, :required
9
+ def initialize(name, h)
10
+ @name=name
11
+ @notes=h['notes'] || ''
12
+ @default=h['default'] || ''
13
+ @attributes=h['attributes'] || ''
14
+ @advanced = @attributes.include?('advanced')
15
+ @password = @attributes.include?('password')
16
+ @required = @attributes.include?('required')
17
+ end
18
+
19
+ # convenience -- for nicer syntax
20
+ def advanced?; @advanced; end
21
+ def password?; @password; end
22
+ def required?; @required; end
23
+ def has_default?; default != '';end
24
+
25
+ def to_s
26
+ required_string = required? ? " (required). " : ""
27
+ default_string = default == '' ? '' : " Default: #{default}. "
28
+ "'#{name}'#{required_string}#{default_string}#{notes}"
29
+ end
30
+ end
31
+
32
+ # A collection of pluginOption
33
+ # Create: opts=PluginOptions.from_yaml(yaml_string)
34
+ # Check if there were any problems -- opts.error -- should be nil.
35
+ #
36
+ # A valid options yaml looks like this:
37
+ # max_swap_used:
38
+ # notes: If swap is larger than this amount, an alert is generated. Amount should be in MB.
39
+ # default: 2048 # 2 GB
40
+ # max_swap_ratio:
41
+ # notes: If swap used over memory used is larger than this amount, an alert is generated
42
+ # default: 3
43
+ # attributes: required advanced
44
+ class PluginOptions < Array
45
+
46
+ attr_accessor :error
47
+
48
+ # Should be valid YAML, a hash of hashes ... if not, will be caught in the rescue below
49
+ def self.from_yaml(string)
50
+ options_array=[]
51
+ error=nil
52
+
53
+ items=YAML.load(string)
54
+ items.each_pair {|name, hash| options_array.push(PluginOption.new(name,hash)) }
55
+ rescue
56
+ error="Invalid Plugin Options"
57
+ ensure
58
+ res=PluginOptions.new(options_array)
59
+ res.error=error
60
+ return res
61
+ end
62
+
63
+ def advanced
64
+ select{|o|o.advanced? }
65
+ end
66
+
67
+ def regular
68
+ select{|o|!o.advanced? }
69
+ end
70
+
71
+ def to_s
72
+ res=[]
73
+ each_with_index do |opt,i|
74
+ res.push "#{i+1}. #{opt.to_s}"
75
+ end
76
+ res.join("\n")
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ # We use this subclass of Logger in the Scout Agent so we can retrieve all the logged messages at the end of the run.
2
+ # This works well only because the Agent is not a long-running process.
3
+
4
+ require 'logger'
5
+
6
+ class ScoutLogger < Logger
7
+ attr_reader :messages
8
+
9
+ def initialize(*args)
10
+ @messages=[]
11
+ super
12
+ end
13
+
14
+ # this is the method that ultimately gets called whenever you invoke info, debug, etc.
15
+ def add(severity, message=nil, progname = nil, &block)
16
+ @messages << "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S ')} ##{$$}] : #{progname}"
17
+ super
18
+ end
19
+ end
@@ -0,0 +1,578 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "net/https"
4
+ require "uri"
5
+ require "yaml"
6
+ require "timeout"
7
+ require "stringio"
8
+ require "zlib"
9
+ require "socket"
10
+ require "base64"
11
+
12
+ $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. .. vendor json_pure lib])
13
+ require "json"
14
+
15
+ module Scout
16
+ class Server
17
+ # A new class for plugin Timeout errors.
18
+ class PluginTimeoutError < RuntimeError; end
19
+ # A new class for API Timeout errors.
20
+ class APITimeoutError < RuntimeError; end
21
+
22
+ # Headers passed up with all API requests.
23
+ HTTP_HEADERS = { "Client-Version" => Scout::VERSION,
24
+ "Client-Hostname" => Socket.gethostname,
25
+ "Accept-Encoding" => "gzip" }
26
+
27
+ #
28
+ # A plugin cannot take more than DEFAULT_PLUGIN_TIMEOUT seconds to execute,
29
+ # otherwise, a timeout error is generated. This can be overriden by
30
+ # individual plugins.
31
+ #
32
+ DEFAULT_PLUGIN_TIMEOUT = 60
33
+ #
34
+ # A fuzzy range of seconds in which it is okay to rerun a plugin.
35
+ # We consider the interval close enough at this point.
36
+ #
37
+ RUN_DELTA = 30
38
+
39
+ attr_reader :new_plan
40
+ attr_reader :directives
41
+ attr_reader :plugin_config
42
+
43
+ # Creates a new Scout Server connection.
44
+ def initialize(server, client_key, history_file, logger = nil)
45
+ @server = server
46
+ @client_key = client_key
47
+ @history_file = history_file
48
+ @history = Hash.new
49
+ @logger = logger
50
+ @plugin_plan = []
51
+ @directives = {} # take_snapshots, interval, sleep_interval
52
+ @new_plan = false
53
+ @local_plugin_path = File.dirname(history_file) # just put overrides and ad-hoc plugins in same directory as history file.
54
+ @plugin_config_path = File.join(@local_plugin_path, "plugins.properties")
55
+ @plugin_config = load_plugin_configs(@plugin_config_path)
56
+
57
+ # the block is only passed for install and test, since we split plan retrieval outside the lockfile for run
58
+ if block_given?
59
+ load_history
60
+ yield self
61
+ save_history
62
+ end
63
+ end
64
+
65
+ def refresh?
66
+ return true if !ping_key
67
+
68
+ url=URI.join( @server.sub("https://","http://"), "/clients/#{ping_key}/ping.scout")
69
+
70
+ headers = {"x-scout-tty" => ($stdin.tty? ? 'true' : 'false')}
71
+ if @history["plan_last_modified"] and @history["old_plugins"]
72
+ headers["If-Modified-Since"] = @history["plan_last_modified"]
73
+ end
74
+ get(url, "Could not ping #{url} for refresh info", headers) do |res|
75
+ if res.is_a?(Net::HTTPNotModified)
76
+ return false
77
+ else
78
+ info "Plan has been modified!"
79
+ return true
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ #
86
+ # Retrieves the Plugin Plan from the server. This is the list of plugins
87
+ # to execute, along with all options.
88
+ #
89
+ # This method has a couple of side effects:
90
+ # 1) it sets the @plugin_plan with either A) whatever is in history, B) the results of the /plan retrieval
91
+ # 2) it sets @checkin_to = true IF so directed by the scout server
92
+ def fetch_plan
93
+ if refresh?
94
+
95
+ url = urlify(:plan)
96
+ info "Fetching plan from server at #{url}..."
97
+ headers = {"x-scout-tty" => ($stdin.tty? ? 'true' : 'false')}
98
+
99
+ get(url, "Could not retrieve plan from server.", headers) do |res|
100
+ begin
101
+ body = res.body
102
+ if res["Content-Encoding"] == "gzip" and body and not body.empty?
103
+ body = Zlib::GzipReader.new(StringIO.new(body)).read
104
+ end
105
+
106
+ body_as_hash = JSON.parse(body)
107
+
108
+ # Ensure all the plugins in the new plan are properly signed. Load the public key for this.
109
+ public_key_text = File.read(File.join( File.dirname(__FILE__), *%w[.. .. data code_key.pub] ))
110
+ debug "Loaded public key used for verifying code signatures (#{public_key_text.size} bytes)"
111
+ code_public_key = OpenSSL::PKey::RSA.new(public_key_text)
112
+
113
+ temp_plugins=Array(body_as_hash["plugins"])
114
+ plugin_signature_error = false
115
+ temp_plugins.each do |plugin|
116
+ signature=plugin['signature']
117
+ id_and_name = "#{plugin['id']}-#{plugin['name']}".sub(/\A-/, "")
118
+ if signature
119
+ code=plugin['code'].gsub(/ +$/,'') # we strip trailing whitespace before calculating signatures. Same here.
120
+ decoded_signature=Base64.decode64(signature)
121
+ if !code_public_key.verify(OpenSSL::Digest::SHA1.new, decoded_signature, code)
122
+ info "#{id_and_name} signature doesn't match!"
123
+ plugin_signature_error=true
124
+ end
125
+ else
126
+ info "#{id_and_name} has no signature!"
127
+ plugin_signature_error=true
128
+ end
129
+ end
130
+
131
+
132
+ if(!plugin_signature_error)
133
+ @plugin_plan = temp_plugins
134
+ @directives = body_as_hash["directives"].is_a?(Hash) ? body_as_hash["directives"] : Hash.new
135
+ @history["plan_last_modified"] = res["last-modified"]
136
+ @history["old_plugins"] = @plugin_plan.clone # important that the plan is cloned -- we're going to add local plugins, and they shouldn't go into history
137
+ @history["directives"] = @directives
138
+
139
+ info "Plan loaded. (#{@plugin_plan.size} plugins: " +
140
+ "#{@plugin_plan.map { |p| p['name'] }.join(', ')})" +
141
+ ". Directives: #{@directives.to_a.map{|a| "#{a.first}:#{a.last}"}.join(", ")}"
142
+
143
+ @new_plan = true # used in determination if we should checkin this time or not
144
+ else
145
+ info "There was a problem with plugin signatures. Reusing old plan."
146
+ @plugin_plan = Array(@history["old_plugins"])
147
+ @directives = @history["directives"] || Hash.new
148
+ end
149
+
150
+ # Add local plugins to the plan. Note that local plugins are NOT saved to history file
151
+ @plugin_plan += get_local_plugins
152
+ rescue Exception =>e
153
+ fatal "Plan from server was malformed: #{e.message} - #{e.backtrace}"
154
+ exit
155
+ end
156
+ end
157
+ else
158
+ info "Plan not modified."
159
+ @plugin_plan = Array(@history["old_plugins"])
160
+ @plugin_plan += get_local_plugins
161
+ @directives = @history["directives"] || Hash.new
162
+ end
163
+ end
164
+
165
+ # returns an array of hashes representing local plugins found on the filesystem
166
+ # The glob pattern requires that filenames begin with a letter,
167
+ # which excludes plugin overrides (like 12345.rb)
168
+ def get_local_plugins
169
+ local_plugin_paths=Dir.glob(File.join(@local_plugin_path,"[a-zA-Z]*.rb"))
170
+ local_plugin_paths.map do |plugin_path|
171
+ begin
172
+ {
173
+ 'name' => File.basename(plugin_path),
174
+ 'local_filename' => File.basename(plugin_path),
175
+ 'origin' => 'LOCAL',
176
+ 'code' => File.read(plugin_path),
177
+ 'interval' => 0
178
+ }
179
+ rescue => e
180
+ info "Error trying to read local plugin: #{plugin_path} -- #{e.backtrace.join('\n')}"
181
+ nil
182
+ end
183
+ end.compact
184
+ end
185
+
186
+ # To distribute pings across a longer timeframe, the agent will sleep for a given
187
+ # amount of time. When using the --force option the sleep_interval is ignored.
188
+ def sleep_interval
189
+ (@history['directives'] || {})['sleep_interval'].to_f
190
+ end
191
+
192
+ def ping_key
193
+ (@history['directives'] || {})['ping_key']
194
+ end
195
+
196
+ # uses values from history and current time to determine if we should checkin at this time
197
+ def time_to_checkin?
198
+ @history['last_checkin'] == nil ||
199
+ @directives['interval'] == nil ||
200
+ (Time.now.to_i - Time.at(@history['last_checkin']).to_i).abs+15 > @directives['interval'].to_i*60
201
+ rescue
202
+ debug "Failed to calculate time_to_checkin. @history['last_checkin']=#{@history['last_checkin']}. "+
203
+ "@directives['interval']=#{@directives['interval']}. Time.now.to_i=#{Time.now.to_i}"
204
+ return true
205
+ end
206
+
207
+ # uses values from history and current time to determine if we should ping the server at this time
208
+ def time_to_ping?
209
+ return true if
210
+ @history['last_ping'] == nil ||
211
+ @directives['ping_interval'] == nil ||
212
+ (Time.now.to_i - Time.at(@history['last_ping']).to_i).abs+15 > @directives['ping_interval'].to_i*60
213
+ rescue
214
+ debug "Failed to calculate time_to_ping. @history['last_ping']=#{@history['last_ping']}. "+
215
+ "@directives['ping_interval']=#{@directives['ping_interval']}. Time.now.to_i=#{Time.now.to_i}"
216
+ return true
217
+ end
218
+
219
+ # returns a human-readable representation of the next checkin, i.e., 5min 30sec
220
+ def next_checkin
221
+ secs= @directives['interval'].to_i*60 - (Time.now.to_i - Time.at(@history['last_checkin']).to_i).abs
222
+ minutes=(secs.to_f/60).floor
223
+ secs=secs%60
224
+ "#{minutes}min #{secs} sec"
225
+ rescue
226
+ "[next scout invocation]"
227
+ end
228
+
229
+ # Runs all plugins from the given plan. Calls process_plugin on each plugin.
230
+ # @plugin_execution_plan is populated by calling fetch_plan
231
+ def run_plugins_by_plan
232
+ prepare_checkin
233
+ @plugin_plan.each do |plugin|
234
+ begin
235
+ process_plugin(plugin)
236
+ rescue Exception
237
+ @checkin[:errors] << build_report(
238
+ plugin,
239
+ :subject => "Exception: #{$!.message}.",
240
+ :body => $!.backtrace
241
+ )
242
+ error("Encountered an error: #{$!.message}")
243
+ puts $!.backtrace.join('\n')
244
+ end
245
+ end
246
+ take_snapshot if @directives['take_snapshots']
247
+ checkin
248
+ end
249
+
250
+ #
251
+ # This is the heart of Scout.
252
+ #
253
+ # First, it determines if a plugin is past interval and needs to be run.
254
+ # If it is, it simply evals the code, compiling it.
255
+ # It then loads the plugin and runs it with a PLUGIN_TIMEOUT time limit.
256
+ # The plugin generates data, alerts, and errors. In addition, it will
257
+ # set memory and last_run information in the history file.
258
+ #
259
+ # The plugin argument is a hash with keys: id, name, code, timeout, options, signature.
260
+ def process_plugin(plugin)
261
+ info "Processing the '#{plugin['name']}' plugin:"
262
+ id_and_name = "#{plugin['id']}-#{plugin['name']}".sub(/\A-/, "")
263
+ plugin_id = plugin['id']
264
+ last_run = @history["last_runs"][id_and_name] ||
265
+ @history["last_runs"][plugin['name']]
266
+ memory = @history["memory"][id_and_name] ||
267
+ @history["memory"][plugin['name']]
268
+ run_time = Time.now
269
+ delta = last_run.nil? ? nil : run_time -
270
+ (last_run + plugin['interval'] * 60)
271
+ if last_run.nil? or delta.between?(-RUN_DELTA, 0) or delta >= 0
272
+ debug "Plugin is past interval and needs to be run. " +
273
+ "(last run: #{last_run || 'nil'})"
274
+ code_to_run = plugin['code']
275
+ if plugin_id && plugin_id != ""
276
+ override_path=File.join(@local_plugin_path, "#{plugin_id}.rb")
277
+ # debug "Checking for local plugin override file at #{override_path}"
278
+ if File.exist?(override_path)
279
+ code_to_run = File.read(override_path)
280
+ debug "Override file found - Using #{code_to_run.size} chars of code in #{override_path} for plugin id=#{plugin_id}"
281
+ plugin['origin'] = "OVERRIDE"
282
+ else
283
+ plugin['origin'] = nil
284
+ end
285
+ end
286
+ debug "Compiling plugin..."
287
+ begin
288
+ eval( code_to_run,
289
+ TOPLEVEL_BINDING,
290
+ plugin['path'] || plugin['name'] )
291
+ info "Plugin compiled."
292
+ rescue Exception
293
+ raise if $!.is_a? SystemExit
294
+ error "Plugin would not compile: #{$!.message}"
295
+ @checkin[:errors] << build_report(plugin,:subject => "Plugin would not compile", :body=>"#{$!.message}\n\n#{$!.backtrace}")
296
+ return
297
+ end
298
+
299
+ # Lookup any local options in plugin_config.properies as needed
300
+ options=(plugin['options'] || Hash.new)
301
+ options.each_pair do |k,v|
302
+ if v=~/^lookup:(.+)$/
303
+ lookup_key = $1.strip
304
+ if plugin_config[lookup_key]
305
+ options[k]=plugin_config[lookup_key]
306
+ else
307
+ info "Plugin #{id_and_name}: option #{k} appears to be a lookup, but we can't find #{lookup_key} in #{@plugin_config_path}"
308
+ end
309
+ end
310
+ end
311
+
312
+
313
+ debug "Loading plugin..."
314
+ if job = Plugin.last_defined.load( last_run, (memory || Hash.new), options)
315
+ info "Plugin loaded."
316
+ debug "Running plugin..."
317
+ begin
318
+ data = {}
319
+ timeout = plugin['timeout'].to_i
320
+ timeout = DEFAULT_PLUGIN_TIMEOUT unless timeout > 0
321
+ Timeout.timeout(timeout, PluginTimeoutError) do
322
+ data = job.run
323
+ end
324
+ rescue Timeout::Error, PluginTimeoutError
325
+ error "Plugin took too long to run."
326
+ @checkin[:errors] << build_report(plugin,
327
+ :subject => "Plugin took too long to run",
328
+ :body=>"Execution timed out.")
329
+ return
330
+ rescue Exception
331
+ raise if $!.is_a? SystemExit
332
+ error "Plugin failed to run: #{$!.class}: #{$!.message}\n" +
333
+ "#{$!.backtrace.join("\n")}"
334
+ @checkin[:errors] << build_report(plugin,
335
+ :subject => "Plugin failed to run",
336
+ :body=>"#{$!.class}: #{$!.message}\n#{$!.backtrace.join("\n")}")
337
+ end
338
+ info "Plugin completed its run."
339
+
340
+ %w[report alert error summary].each do |type|
341
+ plural = "#{type}s".sub(/ys\z/, "ies").to_sym
342
+ reports = data[plural].is_a?(Array) ? data[plural] :
343
+ [data[plural]].compact
344
+ if report = data[type.to_sym]
345
+ reports << report
346
+ end
347
+ reports.each do |fields|
348
+ @checkin[plural] << build_report(plugin, fields)
349
+ end
350
+ end
351
+
352
+ @history["last_runs"].delete(plugin['name'])
353
+ @history["memory"].delete(plugin['name'])
354
+ @history["last_runs"][id_and_name] = run_time
355
+ @history["memory"][id_and_name] = data[:memory]
356
+ else
357
+ @checkin[:errors] << build_report(
358
+ plugin,
359
+ :subject => "Plugin would not load."
360
+ )
361
+ end
362
+ else
363
+ debug "Plugin does not need to be run at this time. " +
364
+ "(last run: #{last_run || 'nil'})"
365
+ end
366
+ data
367
+ ensure
368
+ if Plugin.last_defined
369
+ debug "Removing plugin code..."
370
+ begin
371
+ Object.send(:remove_const, Plugin.last_defined.to_s.split("::").first)
372
+ Plugin.last_defined = nil
373
+ info "Plugin Removed."
374
+ rescue
375
+ raise if $!.is_a? SystemExit
376
+ error "Unable to remove plugin."
377
+ end
378
+ end
379
+ info "Plugin '#{plugin['name']}' processing complete."
380
+ end
381
+
382
+
383
+ # captures a list of processes running at this moment
384
+ def take_snapshot
385
+ info "Taking a process snapshot"
386
+ ps=%x(ps aux).split("\n")[1..-1].join("\n") # get rid of the header line
387
+ @checkin[:snapshot]=ps
388
+ rescue Exception
389
+ error "unable to capture processes on this server. #{$!.message}"
390
+ return nil
391
+ end
392
+
393
+ # Prepares a check-in data structure to hold Plugin generated data.
394
+ def prepare_checkin
395
+ @checkin = { :reports => Array.new,
396
+ :alerts => Array.new,
397
+ :errors => Array.new,
398
+ :summaries => Array.new,
399
+ :snapshot => '',
400
+ :config_path => File.expand_path(File.dirname(@history_file))}
401
+ end
402
+
403
+ def show_checkin(printer = :p)
404
+ send(printer, @checkin)
405
+ end
406
+
407
+ #
408
+ # Loads the history file from disk. If the file does not exist,
409
+ # it creates one.
410
+ #
411
+ def load_history
412
+ if !File.exist?(@history_file) || File.zero?(@history_file)
413
+ create_blank_history
414
+ end
415
+ debug "Loading history file..."
416
+ contents=File.read(@history_file)
417
+ begin
418
+ @history = YAML.load(contents)
419
+ rescue => e
420
+ backup_path=File.join(File.dirname(@history_file), "history.corrupt")
421
+ info "Couldn't parse the history file. Deleting it and resetting to an empty history file. Keeping a backup at #{backup_path}"
422
+ File.open(backup_path,"w"){|f|f.write contents}
423
+ File.delete(@history_file)
424
+ create_blank_history
425
+ @history = File.open(@history_file) { |file| YAML.load(file) }
426
+ end
427
+
428
+ info "History file loaded."
429
+ end
430
+
431
+ # creates a blank history file
432
+ def create_blank_history
433
+ debug "Creating empty history file..."
434
+ File.open(@history_file, "w") do |file|
435
+ YAML.dump({"last_runs" => Hash.new, "memory" => Hash.new}, file)
436
+ end
437
+ info "History file created."
438
+ end
439
+
440
+ # Saves the history file to disk.
441
+ def save_history
442
+ debug "Saving history file..."
443
+ File.open(@history_file, "w") { |file| YAML.dump(@history, file) }
444
+ info "History file saved."
445
+ end
446
+
447
+ private
448
+
449
+ def build_report(plugin_hash, fields)
450
+ { :plugin_id => plugin_hash['id'],
451
+ :created_at => Time.now.utc.strftime("%Y-%m-%d %H:%M:%S"),
452
+ :fields => fields,
453
+ :local_filename => plugin_hash['local_filename'], # this will be nil unless it's an ad-hoc plugin
454
+ :origin => plugin_hash['origin'] # [LOCAL|OVERRIDE|nil]
455
+ }
456
+ end
457
+
458
+ def urlify(url_name, options = Hash.new)
459
+ return unless @server
460
+ options.merge!(:client_version => Scout::VERSION)
461
+ URI.join( @server,
462
+ "/clients/CLIENT_KEY/#{url_name}.scout".
463
+ gsub(/\bCLIENT_KEY\b/, @client_key).
464
+ gsub(/\b[A-Z_]+\b/) { |k| options[k.downcase.to_sym] || k } )
465
+ end
466
+
467
+ def post(url, error, body, headers = Hash.new, &response_handler)
468
+ return unless url
469
+ request(url, response_handler, error) do |connection|
470
+ post = Net::HTTP::Post.new( url.path +
471
+ (url.query ? ('?' + url.query) : ''),
472
+ HTTP_HEADERS.merge(headers) )
473
+ post.body = body
474
+ connection.request(post)
475
+ end
476
+ end
477
+
478
+ def get(url, error, headers = Hash.new, &response_handler)
479
+ return unless url
480
+ request(url, response_handler, error) do |connection|
481
+ connection.get( url.path + (url.query ? ('?' + url.query) : ''),
482
+ HTTP_HEADERS.merge(headers) )
483
+ end
484
+ end
485
+
486
+ def request(url, response_handler, error, &connector)
487
+ response = nil
488
+ Timeout.timeout(5 * 60, APITimeoutError) do
489
+ http = Net::HTTP.new(url.host, url.port)
490
+ if url.is_a? URI::HTTPS
491
+ http.use_ssl = true
492
+ http.ca_file = File.join( File.dirname(__FILE__),
493
+ *%w[.. .. data cacert.pem] )
494
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER |
495
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
496
+ end
497
+ response = no_warnings { http.start(&connector) }
498
+ end
499
+ case response
500
+ when Net::HTTPSuccess, Net::HTTPNotModified
501
+ response_handler[response] unless response_handler.nil?
502
+ else
503
+ error = "Server says: #{response['x-scout-msg']}" if response['x-scout-msg']
504
+ fatal error
505
+ raise SystemExit.new(error)
506
+ end
507
+ rescue Timeout::Error
508
+ fatal "Request timed out."
509
+ exit
510
+ rescue Exception
511
+ raise if $!.is_a? SystemExit
512
+ fatal "An HTTP error occurred: #{$!.message}"
513
+ exit
514
+ end
515
+
516
+ def checkin
517
+ @history['last_checkin'] = Time.now.to_i # might have to save the time of invocation and use here to prevent drift
518
+ io = StringIO.new
519
+ gzip = Zlib::GzipWriter.new(io)
520
+ gzip << @checkin.to_json
521
+ gzip.close
522
+ post( urlify(:checkin),
523
+ "Unable to check in with the server.",
524
+ io.string,
525
+ "Content-Type" => "application/json",
526
+ "Content-Encoding" => "gzip" )
527
+ rescue Exception
528
+ error "Unable to check in with the server."
529
+ debug $!.class.to_s
530
+ debug $!.message
531
+ debug $!.backtrace
532
+ end
533
+
534
+
535
+ def no_warnings
536
+ old_verbose = $VERBOSE
537
+ $VERBOSE = false
538
+ yield
539
+ ensure
540
+ $VERBOSE = old_verbose
541
+ end
542
+
543
+ # Forward Logger methods to an active instance, when there is one.
544
+ def method_missing(meth, *args, &block)
545
+ if (Logger::SEV_LABEL - %w[ANY]).include? meth.to_s.upcase
546
+ @logger.send(meth, *args, &block) unless @logger.nil?
547
+ else
548
+ super
549
+ end
550
+ end
551
+
552
+ private
553
+
554
+ # Called during initialization; loads the plugin_configs (local plugin configurations for passwords, etc)
555
+ # if the file is there. Returns a hash like {"db.username"=>"secr3t"}
556
+ def load_plugin_configs(path)
557
+ temp_configs={}
558
+ if File.exist?(path)
559
+ debug "Loading Plugin Configs at #{path}"
560
+ begin
561
+ File.open(path,"r").read.each_line do |line|
562
+ line.strip!
563
+ next if line[0] == '#'
564
+ next unless line.include? "="
565
+ k,v =line.split('=')
566
+ temp_configs[k]=v
567
+ end
568
+ debug("#{temp_configs.size} plugin config(s) loaded.")
569
+ rescue
570
+ info "Error loading Plugin Configs at #{path}: #{$!}"
571
+ end
572
+ else
573
+ debug "No Plugin Configs at #{path}"
574
+ end
575
+ return temp_configs
576
+ end
577
+ end
578
+ end
data/lib/es-scout.rb ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ module Scout
4
+ VERSION = "5.3.0.es1".freeze
5
+ end
6
+
7
+ require "es-scout/command"
8
+ require "es-scout/plugin"
9
+ require "es-scout/plugin_options"
10
+ require "es-scout/scout_logger"
11
+ require "es-scout/server"