#! /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()