1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
module Puppet::Parser::Functions
newfunction(:monit_validate_tests, :type => :rvalue, :doc => <<-EOS
Validate monit tests and transforms some parts into monit language.
EOS
) do |args|
# Valid tests for resources.
# # RESOURCE TESTING: IF resource operator value THEN action
# # SPACE TESTING: IF SPACE operator value unit THEN action
# # INODE TESTING: IF INODE operator value [unit] THEN action
defined?(RESOURCE_TESTS) or RESOURCE_TESTS = [
'CPU', 'CPU(USER)', 'CPU(SYSTEM)', 'CPU(WAIT)', 'TOTAL CPU', 'CHILDREN',
'LOADAVG(1MIN)', 'LOADAVG(5MIN)', 'LOADAVG(15MIN)',
'TOTAL MEMORY', 'MEMORY', 'SWAP'
]
# Valid operators for resource testing.
defined?(RESOURCE_TESTS_OPERATORS) or RESOURCE_TESTS_OPERATORS = [
'<', '>', '!=', '==',
'GT', 'LT', 'EQ', 'NE',
'GREATER', 'LESS', 'EQUAL', 'NOTEQUAL',
]
defined?(PROTOCOL_TESTS) or PROTOCOL_TESTS = {
#TODO: GENERIC, SIP, RADIUS, WEBSOCKET
'GENERIC' => ['SEND', 'EXPECT'],
'HTTP' => ['REQUEST', 'STATUS', 'CHECKSUM', 'HOSTHEADER', 'CONTENT'],
'APACHE-STATUS' => ['LOGLIMIT', 'CLOSELIMIT', 'DNSLIMIT', 'KEEPALIVELIMIT', 'REPLYLIMIT', 'REQUESTLIMIT', 'STARTLIMIT', 'WAITLIMIT', 'GRACEFULLIMIT', 'CLEANUPLIMIT']
}
defined?(TEST_TYPES) or TEST_TYPES = {
'DIRECTORY' => [],
'FIFO' => [],
'FILE' => [],
'FILESYSTEM' => [
'FSFLAGS', 'SPACE', 'INODE', 'PERM', 'PERMISSION'
],
'HOST' => ['CONNECTION'],
'PROCESS' => RESOURCE_TESTS + ['CONNECTION',],
'PROGRAM' => [],
'SYSTEM' => RESOURCE_TESTS,
}
defined?(TEST_ACTIONS) or TEST_ACTIONS = ['ALERT', 'RESTART', 'START', 'STOP', 'EXEC', 'UNMONITOR']
check_type = args[0].upcase
tests = args[1]
tests.each_with_index do |test, index|
# Validate test type.
test['type'] = test['type'].upcase
unless TEST_TYPES[check_type].include? test['type']
raise Puppet::ParseError, "Tests for '#{check_type}': invalid test type '#{test['type']}'"
end
exception_prefix = "Tests for '#{check_type}' ('#{test['type']}'): "
# Validate failure tolerance and convert value to monit jargon.
# # https://mmonit.com/monit/documentation/monit.html#failure_tolerance
if test.key? 'tolerance'
unless test['tolerance'].class == Hash and test['tolerance'].key? 'cycles'
raise Puppet::ParseError, exception_prefix + "tolerance must be a hash with 'cycles' key and optionally 'times'."
else
if test['tolerance'].key? 'times'
test['tolerance'] = "#{test['tolerance']['times']} TIMES WITHIN #{test['tolerance']['cycles']} CYCLES"
else
test['tolerance'] = "#{test['tolerance']['cycles']} CYCLES"
end
end
end
# Validate action.
# # https://mmonit.com/monit/documentation/monit.html#action
unless test.key? 'action'
test['action'] = 'ALERT'
else
test['action'] = test['action'].upcase
unless TEST_ACTIONS.include? test['action']
raise Puppet::ParseError, exception_prefix + "invalid action '#{test['action']}'"
else
if test['action'] == 'EXEC' and not test.key? 'exec'
raise Puppet::ParseError, exception_prefix + "missing command for exec action"
end
end
end
# RESOURCE TESTS, SPACE and INODE
if RESOURCE_TESTS.include? test['type'] or ['SPACE', 'INODE'].include? test['type']
unless test.key? 'operator'
raise Puppet::ParseError, exception_prefix + "'operator' is mandatory"
end
unless RESOURCE_TESTS_OPERATORS.include? test['operator']
raise Puppet::ParseError, exception_prefix + "invalid operator: #{test['operator']}"
end
unless test.key? 'value'
raise Puppet::ParseError, exception_prefix + "'value' is mandatory"
end
test['operator'] = test['operator'].upcase
test['condition'] = "#{test['type']} #{test['operator']} #{test['value']}"
# FILESYSTEM FLAGS TESTING
elsif test['type'] == 'FSFLAGS'
test['condition'] = "CHANGED #{test['type']}"
# PERMISSION TESTING
elsif ['PERM', 'PERMISSION'].include? test['type']
unless test.key? 'value'
raise Puppet::ParseError, exception_prefix + "'value' is mandatory"
end
test['condition'] = "FAILED #{test['type']} #{test['value']}"
# CONNECTION TESTING
elsif test['type'] == 'CONNECTION'
unless test.key? 'port' or test.key? 'unixsocket'
raise Puppet::ParseError, exception_prefix + "'port' or 'unixsocket' is mandatory"
end
condition = 'FAILED'
if test.key? 'unixsocket'
condition += "UNIXSOCKET #{test['unixsocket']}"
else
if test.key? 'host'
condition += " HOST #{test['host']} PORT #{test['port']}"
else
condition += " PORT #{test['port']}"
end
if test.key? 'socket_type'
test['socket_type'] = test['socket_type'].upcase
condition += " TYPE #{test['socket_type']}"
if test['socket_type'] == 'TCPSSL'
if test.key? 'socket_type_cypher'
test['socket_type_cypher'] = test['socket_type_cypher'].upcase
condition += " #{test['socket_type_cypher']}"
end
if test.key? 'socket_type_checksum'
condition += " CERTMD5 #{test['socket_type_checksum']}"
end
end
end
if test.key? 'protocol'
test['protocol'] = test['protocol'].upcase
unless test['protocol'] == 'GENERIC'
condition += "\n PROTOCOL #{test['protocol']} "
end
# Protocol test.
if test.key? 'protocol_test'
# If we don't know about specific tests for this protocol,
# fallback to generic test.
if PROTOCOL_TESTS.key? test['protocol']
pt_type = test['protocol']
else
pt_type = 'GENERIC'
end
case pt_type
when 'HTTP', 'APACHE-STATUS'
pt_options = PROTOCOL_TESTS[pt_type]
# Validate test options.
unless test['protocol_test'].class == Hash
raise Puppet::ParseError, exception_prefix + "protocol_test must be a hash with any of this keys: #{pt_options.join(', ')}."
end
options = test['protocol_test']
invalid_opts = options.keys.map{|key| key.upcase} - pt_options
unless invalid_opts.empty?
raise Puppet::ParseError, exception_prefix + "invalid options in #{test['protocol']} ckeck: #{invalid_opts.join(', ')}"
end
# Enforce REQUEST key to be the first one. Applies only to HTTP test.
if options.key? 'request'
condition += "\n REQUEST #{options['request']}"
end
options.each do |key, value|
unless key == 'request'
condition += "\n #{key.upcase} #{value}"
end
end
when 'GENERIC'
# Validate test options.
unless test['protocol_test'].class == Array
raise Puppet::ParseError, exception_prefix + "protocol_test must be an array of hashes with send/expect pairs."
end
pt_options = PROTOCOL_TESTS[pt_type]
test['protocol_test'].each do |pair|
# Fail if any option is missing.
unless pt_options & pair.keys.map{|key| key.upcase} == pt_options
raise Puppet::ParseError, exception_prefix + "missing options in #{test['protocol']} ckeck: #{pt_options.join(', ')} are mandatory."
end
pair.each do |key, value|
condition += "\n #{key.upcase} #{value}"
end
end
end
end
end
end
if test.key? 'timeout'
condition += "\n WITH TIMEOUT #{test['timeout']} SECONDS"
end
if test.key? 'retry'
condition += " RETRY #{test['retry']}"
end
if test.key? 'action'
test['action'] = test['action'].upcase
else
test['action'] = 'ALERT'
end
test['condition'] = condition
end
tests[index] = test
end
return tests
end
end
|