onesnooper-server 0.0.1

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.
@@ -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