onesnooper-server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ ARCH=x86_64
2
+ MODELNAME="Intel(R) Xeon(R) CPU E5649 @ 2.53GHz"
3
+ DS_LOCATION_USED_MB=149998
4
+ DS_LOCATION_TOTAL_MB=1092886
5
+ DS_LOCATION_FREE_MB=887373
6
+ DS = [
7
+ ID = 0,
8
+ USED_MB = 149998,
9
+ TOTAL_MB = 1092886,
10
+ FREE_MB = 887373
11
+ ]
12
+ DS = [
13
+ ID = 120,
14
+ USED_MB = 149998,
15
+ TOTAL_MB = 1092886,
16
+ FREE_MB = 887373
17
+ ]
18
+ HOSTNAME=host1.localhost
19
+ VM_POLL=YES
20
+ VM=[
21
+ ID=12036,
22
+ DEPLOY_ID=one-12036,
23
+ POLL="NETRX=247208960 NETTX=1786718208 USEDCPU=60.3 USEDMEMORY=16777216 NAME=one-12036 STATE=a" ]
24
+ VM=[
25
+ ID=12037,
26
+ DEPLOY_ID=one-12037,
27
+ POLL="NETRX=231688192 NETTX=1806806016 USEDCPU=60.1 USEDMEMORY=16777216 NAME=one-12037 STATE=a" ]
28
+ VM=[
29
+ ID=12039,
30
+ DEPLOY_ID=one-12039,
31
+ POLL="NETRX=155998208 NETTX=2421689344 USEDCPU=0.1 USEDMEMORY=1048576 NAME=one-12039 STATE=a" ]
32
+ VM=[
33
+ ID=12139,
34
+ DEPLOY_ID=one-12139,
35
+ POLL="NETRX=4646912 NETTX=433727488 USEDCPU=0.2 USEDMEMORY=1048576 NAME=one-12139 STATE=a" ]
36
+ VM=[
37
+ ID=12031,
38
+ DEPLOY_ID=one-12031,
39
+ POLL="NETRX=7949312 NETTX=1946089472 USEDCPU=0.1 USEDMEMORY=1048576 NAME=one-12031 STATE=a" ]
40
+ VM=[
41
+ ID=12032,
42
+ DEPLOY_ID=one-12032,
43
+ POLL="NETRX=9022464 NETTX=2323913728 USEDCPU=0.2 USEDMEMORY=3145728 NAME=one-12032 STATE=a" ]
44
+ VM=[
45
+ ID=12034,
46
+ DEPLOY_ID=one-12034,
47
+ POLL="NETRX=332258304 NETTX=4284767232 USEDCPU=50.5 USEDMEMORY=16777216 NAME=one-12034 STATE=a" ]
48
+ VM=[
49
+ ID=-1,
50
+ DEPLOY_ID=Domain-0,
51
+ POLL="NETRX=0 NETTX=0 USEDCPU=3.7 USEDMEMORY=5233724 NAME=Domain-0 STATE=a" ]
52
+ VM=[
53
+ ID=12035,
54
+ DEPLOY_ID=one-12035,
55
+ POLL="NETRX=209708032 NETTX=1790325760 USEDCPU=59.5 USEDMEMORY=16777216 NAME=one-12035 STATE=a" ]
56
+ VERSION="4.10.1"
57
+ HYPERVISOR=xen
58
+ TOTALCPU=2400
59
+ CPUSPEED=2533
60
+ TOTALMEMORY=100654080
61
+ FREEMEMORY=20727808
62
+ USEDMEMORY=79926272
63
+ USEDCPU=244
64
+ FREECPU=2156
65
+ NETTX=16400482
66
+ NETRX=1170391
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # -------------------------------------------------------------------------- #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may #
5
+ # not use this file except in compliance with the License. You may obtain #
6
+ # a copy of the License at #
7
+ # #
8
+ # http://www.apache.org/licenses/LICENSE-2.0 #
9
+ # #
10
+ # Unless required by applicable law or agreed to in writing, software #
11
+ # distributed under the License is distributed on an "AS IS" BASIS, #
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
13
+ # See the License for the specific language governing permissions and #
14
+ # limitations under the License. #
15
+ #--------------------------------------------------------------------------- #
16
+
17
+ # external dependencies
18
+ require 'rubygems'
19
+
20
+ require 'socket'
21
+ require 'base64'
22
+ require 'resolv'
23
+ require 'ipaddr'
24
+
25
+ # add local dirs to load path if necessary
26
+ lib = File.expand_path(File.join('..', '..', 'lib'), __FILE__)
27
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
28
+
29
+ # add example dirs
30
+ examples = File.expand_path(File.join('..'), __FILE__)
31
+
32
+ # prepare data
33
+ udp_socket = UDPSocket.new
34
+ result = "SUCCESS"
35
+ host_id = 527
36
+ data64 = Base64::encode64(File.read(File.join(examples, 'monitoring.data'))).strip.delete("\n")
37
+
38
+ # send data
39
+ udp_socket.send("MONITOR #{result} #{host_id} #{data64}\n", 0, 'localhost', 9000)
@@ -0,0 +1,32 @@
1
+ # Internals of the onesnooper-server application. Classes
2
+ # in this namespace should not be used by external libraries.
3
+ module OnesnooperServer; end
4
+
5
+ # internal ruby dependencies
6
+ require 'date'
7
+ require 'ipaddr'
8
+ require 'base64'
9
+
10
+ # active support stuff
11
+ require 'active_support'
12
+ require 'active_support/core_ext'
13
+ require 'active_support/json'
14
+ require 'active_support/inflector'
15
+ require 'active_support/notifications'
16
+
17
+ # external dependencies
18
+ require 'eventmachine'
19
+ require 'settingslogic'
20
+
21
+ # internal components
22
+ require 'onesnooper_server/version'
23
+ require 'onesnooper_server/settings'
24
+ require 'onesnooper_server/log'
25
+ require 'onesnooper_server/store'
26
+ require 'onesnooper_server/sql_store'
27
+ require 'onesnooper_server/stores'
28
+ require 'onesnooper_server/datagram'
29
+ require 'onesnooper_server/datagrams'
30
+ require 'onesnooper_server/payload_parser'
31
+ require 'onesnooper_server/request_handler'
32
+ require 'onesnooper_server/udp_handler'
@@ -0,0 +1,19 @@
1
+ # Base class for all datagram processing classes. Defines
2
+ # required stub methods. No functionality is implemented here.
3
+ class OnesnooperServer::Datagram
4
+
5
+ # Initializes class instances.
6
+ #
7
+ # @param params [Hash] hash-like with params
8
+ def initialize(params = {})
9
+ @params = params
10
+ end
11
+
12
+ # Runs datagram processing for the chosen datagram type.
13
+ #
14
+ # @param deferred_callback [::EventMachine::DefaultDeferrable] response callback
15
+ def run(deferred_callback)
16
+ fail "This method needs to be implemented in subclasses"
17
+ end
18
+
19
+ end
@@ -0,0 +1,7 @@
1
+ # Wrapper module for all available datagram processing
2
+ # classes. Each class has to implement method stubs
3
+ # outlined in `OnesnooperServer::Datagram`.
4
+ module OnesnooperServer::Datagrams; end
5
+
6
+ # Load all available datagram types
7
+ Dir.glob(File.join(File.dirname(__FILE__), 'datagrams', "*.rb")) { |datagram_file| require datagram_file.chomp('.rb') }
@@ -0,0 +1,9 @@
1
+ # Handles processing of datagrams with FAILED monitoring
2
+ # messages. Doesn't perform any backend operations.
3
+ class OnesnooperServer::Datagrams::FailureDatagram < ::OnesnooperServer::Datagram
4
+
5
+ def run(deferred_callback)
6
+ ::EventMachine.defer { deferred_callback.fail "Failed monitoring result will not be recorded" }
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ # Handles processing of datagrams with invalid
2
+ # messages. Doesn't perform any backend operations.
3
+ class OnesnooperServer::Datagrams::InvalidDatagram < ::OnesnooperServer::Datagram
4
+
5
+ def run(deferred_callback)
6
+ ::EventMachine.defer { deferred_callback.fail "Invalid monitoring result will not be recorded" }
7
+ end
8
+
9
+ end
@@ -0,0 +1,60 @@
1
+ # Handles processing of datagrams with SUCESS monitoring
2
+ # messages. Performs 'save' operation on the backend.
3
+ class OnesnooperServer::Datagrams::SuccessDatagram < ::OnesnooperServer::Datagram
4
+
5
+ def run(deferred_callback)
6
+ ::EventMachine.defer do
7
+ if parse_payload! && store_all!
8
+ deferred_callback.succeed "Successful monitoring result was " \
9
+ "recorded in #{store_info.join(', ')}"
10
+ else
11
+ deferred_callback.fail "Processing partially or completely failed, see logs"
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ # Decodes and parses datagram payload into a
19
+ # hash-like structure. Modification is done
20
+ # in-place.
21
+ def parse_payload!
22
+ @params[:payload] = ::OnesnooperServer::PayloadParser.parse(@params[:payload])
23
+ end
24
+
25
+ # Stores decoded and parsed payload in all
26
+ # enabled data stores. Errors are logged
27
+ # but not otherwise reported.
28
+ #
29
+ # @return [Boolean] success in all stores
30
+ def store_all!
31
+ if @params[:payload].blank?
32
+ ::OnesnooperServer::Log.warn "[#{self.class.name}] Skipping empty payload " \
33
+ "from ONE ID:#{@params[:host_id]}"
34
+ return false
35
+ end
36
+
37
+ all_good = true
38
+ @params[:stores].each do |store|
39
+ begin
40
+ ::OnesnooperServer::Log.debug "[#{self.class.name}] Saving data in #{store.class.name}"
41
+ store.save!(DateTime.now, @params[:payload])
42
+ rescue => ex
43
+ ::OnesnooperServer::Log.error "[#{self.class.name}] Error while saving " \
44
+ "in #{store.class.name}: #{ex.message}"
45
+ all_good = false
46
+ end
47
+ end
48
+
49
+ all_good
50
+ end
51
+
52
+ # Returns human-readable information about enabled
53
+ # backend stores.
54
+ #
55
+ # @return [Array] list of textual store information
56
+ def store_info
57
+ @params[:stores].collect { |store| store.class.name }
58
+ end
59
+
60
+ end
@@ -0,0 +1,74 @@
1
+ require 'logger'
2
+
3
+ # Wrapper for log subscribers combining the functionality
4
+ # of `Logger` and `ActiveSupport::Notifications`. Allows
5
+ # the use of Singleton-like logging facilities.
6
+ class OnesnooperServer::Log
7
+
8
+ include ::Logger::Severity
9
+
10
+ attr_reader :logger, :log_prefix
11
+
12
+ # Default subscription handle for notifications
13
+ SUBSCRIPTION_HANDLE = "onesnooper-server.log"
14
+
15
+ # Creates a new logger
16
+ #
17
+ # @param log_dev [IO,String] The log device. This is a filename (String) or IO object (typically +STDOUT+, +STDERR+, or an open file).
18
+ # @param log_prefix [String] String placed in front of every logged message
19
+ def initialize(log_dev, log_prefix = '[onesnooper-server]')
20
+ if log_dev.kind_of? ::Logger
21
+ @logger = log_dev
22
+ else
23
+ @logger = ::Logger.new(log_dev)
24
+ end
25
+
26
+ @log_prefix = log_prefix.blank? ? '' : log_prefix.strip
27
+
28
+ # subscribe to log messages and send to logger
29
+ @log_subscriber = ActiveSupport::Notifications.subscribe(self.class::SUBSCRIPTION_HANDLE) do |name, start, finish, id, payload|
30
+ @logger.log(payload[:level], "#{@log_prefix} #{payload[:message]}") if @logger
31
+ end
32
+ end
33
+
34
+ def close
35
+ ActiveSupport::Notifications.unsubscribe(@log_subscriber)
36
+ end
37
+
38
+ # @param severity [::Logger::Severity] severity
39
+ def level=(severity)
40
+ @logger.level = severity
41
+ end
42
+
43
+ # @return [::Logger::Severity]
44
+ def level
45
+ @logger.level
46
+ end
47
+
48
+ # @see info
49
+ def self.debug(message)
50
+ ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, :level => ::Logger::DEBUG, :message => message)
51
+ end
52
+
53
+ # Log an +INFO+ message
54
+ # @param message [String] message the message to log; does not need to be a String
55
+ def self.info(message)
56
+ ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, :level => ::Logger::INFO, :message => message)
57
+ end
58
+
59
+ # @see info
60
+ def self.warn(message)
61
+ ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, :level => ::Logger::WARN, :message => message)
62
+ end
63
+
64
+ # @see info
65
+ def self.error(message)
66
+ ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, :level => ::Logger::ERROR, :message => message)
67
+ end
68
+
69
+ # @see info
70
+ def self.fatal(message)
71
+ ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, :level => ::Logger::FATAL, :message => message)
72
+ end
73
+
74
+ end
@@ -0,0 +1,143 @@
1
+ # Central parsing class for decoding and analyzing
2
+ # the content of incoming monitoring messages. Implements
3
+ # one publicly available method `parse(payload)`. Internally,
4
+ # the payload is decoded from Base64 and parsed into a
5
+ # hash-like structure.
6
+ class OnesnooperServer::PayloadParser
7
+
8
+ TRIM_CLEANUP = /\s*/
9
+ KEY = /[[[:upper:]]|[[:digit:]]|_]+/
10
+ VALUE = /[[[:alnum:]]|_|\-|\.|:]+/
11
+ QUOTED_VALUE = /.+/
12
+ HASH_VALUE = /[^\]]+/
13
+ EQUALS = /#{TRIM_CLEANUP}=#{TRIM_CLEANUP}/
14
+
15
+ KEY_HASH_VALUE_REGEXP = /#{TRIM_CLEANUP}^#{TRIM_CLEANUP}(?<key>#{KEY})#{EQUALS}\[(?<value>#{HASH_VALUE})\]#{TRIM_CLEANUP}$#{TRIM_CLEANUP}/m
16
+ KEY_QUOTED_VALUE_REGEXP = /#{TRIM_CLEANUP}^#{TRIM_CLEANUP}(?<key>#{KEY})#{EQUALS}"(?<value>#{QUOTED_VALUE})"#{TRIM_CLEANUP}$#{TRIM_CLEANUP}/
17
+ KEY_RAW_VALUE_REGEXP = /#{TRIM_CLEANUP}^#{TRIM_CLEANUP}(?<key>#{KEY})#{EQUALS}(?<value>#{VALUE})#{TRIM_CLEANUP}$#{TRIM_CLEANUP}/
18
+
19
+ # Parses given payload into a hash-like structure. Payload
20
+ # is decoded from Base64 and then analyzed and parsed.
21
+ #
22
+ # @param payload [String] Base64-encoded payload with ONE monitoring data
23
+ # @return [Hash] hash-like structure with parsed payload
24
+ def self.parse(payload)
25
+ return {} if payload.blank?
26
+ analyze(decode(payload))
27
+ end
28
+
29
+ private
30
+
31
+ # Decodes given Base64-encoded string.
32
+ #
33
+ # @param payload [String] Base64-encoded string
34
+ # @return [String] decoded string
35
+ def self.decode(payload)
36
+ ::OnesnooperServer::Log.debug "[#{self.name}] Decoding #{payload.inspect}"
37
+ begin
38
+ Base64.strict_decode64(payload)
39
+ rescue => ex
40
+ ::OnesnooperServer::Log.error "[#{self.name}] Decoding Base64 failed with: #{ex.message}"
41
+ return ''
42
+ end
43
+ end
44
+
45
+ # Analyzes payload content and returns a corresponding
46
+ # hash-like structure.
47
+ #
48
+ # @param payload [String] plain text payload in ONE format
49
+ # @param subpayload [Boolean] recursive call for parsing complex values
50
+ # @return [Hash] a hash-like structure with analyzed payload content
51
+ def self.analyze(payload, subpayload = false)
52
+ ::OnesnooperServer::Log.debug "[#{self.name}] Scanning decoded #{subpayload ? 'sub-' : '' }payload #{payload.inspect}"
53
+ return {} if payload.blank?
54
+
55
+ scanned_payload = {}
56
+ scannable_payload = StringScanner.new(payload)
57
+ begin
58
+ if scanned = scannable_payload.scan(KEY_HASH_VALUE_REGEXP)
59
+ scanned.strip!
60
+ ::OnesnooperServer::Log.debug "[#{self.name}] Scanned #{scanned.inspect}"
61
+ analyze_simple_pair(scanned, scanned_payload, KEY_HASH_VALUE_REGEXP, true)
62
+ elsif scanned = scannable_payload.scan(KEY_QUOTED_VALUE_REGEXP)
63
+ scanned.strip!
64
+ scanned.gsub! "\n", ''
65
+ ::OnesnooperServer::Log.debug "[#{self.name}] Scanned #{scanned.inspect}"
66
+ analyze_simple_pair(scanned, scanned_payload, KEY_QUOTED_VALUE_REGEXP)
67
+ elsif scanned = scannable_payload.scan(KEY_RAW_VALUE_REGEXP)
68
+ scanned.strip!
69
+ scanned.gsub! "\n", ''
70
+ ::OnesnooperServer::Log.debug "[#{self.name}] Scanned #{scanned.inspect}"
71
+ analyze_simple_pair(scanned, scanned_payload, KEY_RAW_VALUE_REGEXP)
72
+ else
73
+ ::OnesnooperServer::Log.error "[#{self.name}] Failed scanning #{subpayload ? 'sub-' : '' }payload " \
74
+ "#{payload.inspect} at #{scannable_payload.pos}"
75
+ break
76
+ end
77
+ end until scannable_payload.eos?
78
+
79
+ scanned_payload
80
+ end
81
+
82
+ # Parses complex value strings into a
83
+ # hash-like structure.
84
+ #
85
+ # @param complex_value [String] input string
86
+ # @return [Hash] result
87
+ def self.analyze_complex_value(complex_value)
88
+ complex_parsed = analyze(complex_value.gsub(',', "\n").strip.gsub("\n\n", "\n"), true)
89
+ unless complex_parsed['POLL'].blank?
90
+ ::OnesnooperServer::Log.debug "[#{self.name}] Found complex POLL values, triggering analysis"
91
+ complex_parsed['POLL'] = analyze(complex_parsed['POLL'].gsub(/\s+/, "\n"), true)
92
+ end
93
+
94
+ complex_parsed
95
+ end
96
+
97
+ # Parses simple key value strings into the given
98
+ # hash-like structure.
99
+ #
100
+ # @param key_value [String] input string
101
+ # @param parsed [Hash] output hash-like structure
102
+ # @param regexp [Regexp] regular expression for parsing
103
+ # @param suspected_complex [Boolean] suspect complex value
104
+ # @return [Boolean] success or failure
105
+ def self.analyze_simple_pair(key_value, parsed, regexp, suspected_complex = false)
106
+ matched = key_value.match(regexp)
107
+ if matched
108
+ ::OnesnooperServer::Log.debug "[#{self.name}] Matched #{key_value.inspect} " \
109
+ "as #{matched[:key].inspect} and #{matched[:value].inspect}"
110
+ if suspected_complex
111
+ parsed[matched[:key]] ||= []
112
+ parsed[matched[:key]] << analyze_complex_value(matched[:value])
113
+ else
114
+ parsed[matched[:key]] = typecast_if_num(matched[:value])
115
+ end
116
+ else
117
+ ::OnesnooperServer::Log.error "[#{self.name}] Couldn't match " \
118
+ "#{key_value.inspect} as key & simple value"
119
+ end
120
+
121
+ true
122
+ end
123
+
124
+ # Attempts to type-cast values to `Integer` or `Float` if this casting
125
+ # makes sense for the given value. Otherwise the original value
126
+ # is returned.
127
+ #
128
+ # @param potential_num [String] value to type-cast if applicable
129
+ # @return [String, Integer, Float] type-casted value if applicable
130
+ def self.typecast_if_num(potential_num)
131
+ return potential_num unless potential_num.kind_of? String
132
+
133
+ case potential_num
134
+ when potential_num.to_i.to_s
135
+ potential_num.to_i
136
+ when potential_num.to_f.to_s
137
+ potential_num.to_f
138
+ else
139
+ potential_num
140
+ end
141
+ end
142
+
143
+ end