283 lines
8.0 KiB
Python
283 lines
8.0 KiB
Python
#! /usr/bin/env python
|
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import unittest
|
|
import socket
|
|
import sys
|
|
|
|
from StringIO import StringIO
|
|
|
|
from check_zookeeper import ZooKeeperServer, NagiosHandler, CactiHandler, GangliaHandler
|
|
|
|
ZK_MNTR_OUTPUT = """zk_version\t3.4.0--1, built on 06/19/2010 15:07 GMT
|
|
zk_avg_latency\t1
|
|
zk_max_latency\t132
|
|
zk_min_latency\t0
|
|
zk_packets_received\t640
|
|
zk_packets_sent\t639
|
|
zk_outstanding_requests\t0
|
|
zk_server_state\tfollower
|
|
zk_znode_count\t4
|
|
zk_watch_count\t0
|
|
zk_ephemerals_count\t0
|
|
zk_approximate_data_size\t27
|
|
zk_open_file_descriptor_count\t22
|
|
zk_max_file_descriptor_count\t1024
|
|
"""
|
|
|
|
ZK_MNTR_OUTPUT_WITH_BROKEN_LINES = """zk_version\t3.4.0
|
|
zk_avg_latency\t23
|
|
broken-line
|
|
|
|
"""
|
|
|
|
ZK_STAT_OUTPUT = """Zookeeper version: 3.3.0-943314, built on 05/11/2010 22:20 GMT
|
|
Clients:
|
|
/0:0:0:0:0:0:0:1:34564[0](queued=0,recved=1,sent=0)
|
|
|
|
Latency min/avg/max: 0/40/121
|
|
Received: 11
|
|
Sent: 10
|
|
Outstanding: 0
|
|
Zxid: 0x700000003
|
|
Mode: follower
|
|
Node count: 4
|
|
"""
|
|
|
|
class SocketMock(object):
|
|
def __init__(self):
|
|
self.sent = []
|
|
|
|
def settimeout(self, timeout):
|
|
self.timeout = timeout
|
|
|
|
def connect(self, address):
|
|
self.address = address
|
|
|
|
def send(self, data):
|
|
self.sent.append(data)
|
|
return len(data)
|
|
|
|
def recv(self, size):
|
|
return ZK_MNTR_OUTPUT[:size]
|
|
|
|
def close(self): pass
|
|
|
|
class ZK33xSocketMock(SocketMock):
|
|
def __init__(self):
|
|
SocketMock.__init__(self)
|
|
self.got_stat_cmd = False
|
|
|
|
def recv(self, size):
|
|
if 'stat' in self.sent:
|
|
return ZK_STAT_OUTPUT[:size]
|
|
else:
|
|
return ''
|
|
|
|
class UnableToConnectSocketMock(SocketMock):
|
|
def connect(self, _):
|
|
raise socket.error('[Errno 111] Connection refused')
|
|
|
|
def create_server_mock(socket_class):
|
|
class ZooKeeperServerMock(ZooKeeperServer):
|
|
def _create_socket(self):
|
|
return socket_class()
|
|
return ZooKeeperServerMock()
|
|
|
|
class TestCheckZookeeper(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.zk = ZooKeeperServer()
|
|
|
|
def test_parse_valid_line(self):
|
|
key, value = self.zk._parse_line('something\t5')
|
|
|
|
self.assertEqual(key, 'something')
|
|
self.assertEqual(value, 5)
|
|
|
|
def test_parse_line_raises_exception_on_invalid_output(self):
|
|
invalid_lines = ['something', '', 'a\tb\tc', '\t1']
|
|
for line in invalid_lines:
|
|
self.assertRaises(ValueError, self.zk._parse_line, line)
|
|
|
|
def test_parser_on_valid_output(self):
|
|
data = self.zk._parse(ZK_MNTR_OUTPUT)
|
|
|
|
self.assertEqual(len(data), 14)
|
|
self.assertEqual(data['zk_znode_count'], 4)
|
|
|
|
def test_parse_should_ignore_invalid_lines(self):
|
|
data = self.zk._parse(ZK_MNTR_OUTPUT_WITH_BROKEN_LINES)
|
|
|
|
self.assertEqual(len(data), 2)
|
|
|
|
def test_parse_stat_valid_output(self):
|
|
data = self.zk._parse_stat(ZK_STAT_OUTPUT)
|
|
|
|
result = {
|
|
'zk_version' : '3.3.0-943314, built on 05/11/2010 22:20 GMT',
|
|
'zk_min_latency' : 0,
|
|
'zk_avg_latency' : 40,
|
|
'zk_max_latency' : 121,
|
|
'zk_packets_received': 11,
|
|
'zk_packets_sent': 10,
|
|
'zk_server_state': 'follower',
|
|
'zk_znode_count': 4
|
|
}
|
|
for k, v in result.iteritems():
|
|
self.assertEqual(v, data[k])
|
|
|
|
def test_recv_valid_output(self):
|
|
zk = create_server_mock(SocketMock)
|
|
|
|
data = zk.get_stats()
|
|
self.assertEqual(len(data), 14)
|
|
self.assertEqual(data['zk_znode_count'], 4)
|
|
|
|
def test_socket_unable_to_connect(self):
|
|
zk = create_server_mock(UnableToConnectSocketMock)
|
|
|
|
self.assertRaises(socket.error, zk.get_stats)
|
|
|
|
def test_use_stat_cmd_if_mntr_is_not_available(self):
|
|
zk = create_server_mock(ZK33xSocketMock)
|
|
|
|
data = zk.get_stats()
|
|
self.assertEqual(data['zk_version'], '3.3.0-943314, built on 05/11/2010 22:20 GMT')
|
|
|
|
class HandlerTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
try:
|
|
sys._stdout
|
|
except:
|
|
sys._stdout = sys.stdout
|
|
|
|
sys.stdout = StringIO()
|
|
|
|
def tearDown(self):
|
|
sys.stdout = sys._stdout
|
|
|
|
def output(self):
|
|
sys.stdout.seek(0)
|
|
return sys.stdout.read()
|
|
|
|
|
|
class TestNagiosHandler(HandlerTestCase):
|
|
|
|
def _analyze(self, w, c, k, stats):
|
|
class Opts(object):
|
|
warning = w
|
|
critical = c
|
|
key = k
|
|
|
|
return NagiosHandler().analyze(Opts(), {'localhost:2181':stats})
|
|
|
|
def test_ok_status(self):
|
|
r = self._analyze(10, 20, 'a', {'a': 5})
|
|
|
|
self.assertEqual(r, 0)
|
|
self.assertEqual(self.output(), 'Ok "a"!|localhost:2181=5;10;20\n')
|
|
|
|
r = self._analyze(20, 10, 'a', {'a': 30})
|
|
self.assertEqual(r, 0)
|
|
|
|
def test_warning_status(self):
|
|
r = self._analyze(10, 20, 'a', {'a': 15})
|
|
self.assertEqual(r, 1)
|
|
self.assertEqual(self.output(),
|
|
'Warning "a" localhost:2181!|localhost:2181=15;10;20\n')
|
|
|
|
r = self._analyze(20, 10, 'a', {'a': 15})
|
|
self.assertEqual(r, 1)
|
|
|
|
def test_critical_status(self):
|
|
r = self._analyze(10, 20, 'a', {'a': 30})
|
|
self.assertEqual(r, 2)
|
|
self.assertEqual(self.output(),
|
|
'Critical "a" localhost:2181!|localhost:2181=30;10;20\n')
|
|
|
|
r = self._analyze(20, 10, 'a', {'a': 5})
|
|
self.assertEqual(r, 2)
|
|
|
|
def test_check_a_specific_key_on_all_hosts(self):
|
|
class Opts(object):
|
|
warning = 10
|
|
critical = 20
|
|
key = 'latency'
|
|
|
|
r = NagiosHandler().analyze(Opts(), {
|
|
's1:2181': {'latency': 5},
|
|
's2:2181': {'latency': 15},
|
|
's3:2181': {'latency': 35},
|
|
})
|
|
self.assertEqual(r, 2)
|
|
self.assertEqual(self.output(),
|
|
'Critical "latency" s3:2181!|s1:2181=5;10;20 '\
|
|
's3:2181=35;10;20 s2:2181=15;10;20\n')
|
|
|
|
class TestCactiHandler(HandlerTestCase):
|
|
class Opts(object):
|
|
key = 'a'
|
|
leader = False
|
|
|
|
def __init__(self, leader=False):
|
|
self.leader = leader
|
|
|
|
def test_output_values_for_all_hosts(self):
|
|
r = CactiHandler().analyze(TestCactiHandler.Opts(), {
|
|
's1:2181':{'a':1},
|
|
's2:2181':{'a':2, 'b':3}
|
|
})
|
|
self.assertEqual(r, None)
|
|
self.assertEqual(self.output(), 's1_2181:1 s2_2181:2')
|
|
|
|
def test_output_single_value_for_leader(self):
|
|
r = CactiHandler().analyze(TestCactiHandler.Opts(leader=True), {
|
|
's1:2181': {'a':1, 'zk_server_state': 'leader'},
|
|
's2:2181': {'a':2}
|
|
})
|
|
self.assertEqual(r, 0)
|
|
self.assertEqual(self.output(), '1\n')
|
|
|
|
|
|
class TestGangliaHandler(unittest.TestCase):
|
|
|
|
class TestableGangliaHandler(GangliaHandler):
|
|
def __init__(self):
|
|
GangliaHandler.__init__(self)
|
|
self.cli_calls = []
|
|
|
|
def call(self, cli):
|
|
self.cli_calls.append(' '.join(cli))
|
|
|
|
def test_send_single_metric(self):
|
|
class Opts(object):
|
|
@property
|
|
def gmetric(self): return '/usr/bin/gmetric'
|
|
opts = Opts()
|
|
|
|
h = TestGangliaHandler.TestableGangliaHandler()
|
|
h.analyze(opts, {'localhost:2181':{'latency':10}})
|
|
|
|
cmd = "%s -n latency -v 10 -t uint32" % opts.gmetric
|
|
assert cmd in h.cli_calls
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
|