Skip to content

Commit 52c814e

Browse files
authored
Extend tests (#9)
* Extent tests and add some comments * Improve error messages * Small refactor to improve testability
2 parents 46543b3 + 38fbce2 commit 52c814e

File tree

2 files changed

+85
-57
lines changed

2 files changed

+85
-57
lines changed

check_monit.py

Lines changed: 67 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
#!/usr/bin/env python3
22

3-
# MIT License
4-
#
5-
# Copyright (c) 2021 NETWAYS GmbH
6-
#
7-
# Permission is hereby granted, free of charge, to any person obtaining a copy
8-
# of this software and associated documentation files (the "Software"), to deal
9-
# in the Software without restriction, including without limitation the rights
10-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11-
# copies of the Software, and to permit persons to whom the Software is
12-
# furnished to do so, subject to the following conditions:
13-
#
14-
# The above copyright notice and this permission notice shall be included in all
15-
# copies or substantial portions of the Software.
16-
#
17-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23-
# SOFTWARE.
3+
# monit.h Reference
4+
# typedef enum {
5+
# Monitor_Not = 0x0,
6+
# Monitor_Yes = 0x1,
7+
# Monitor_Init = 0x2,
8+
# Monitor_Waiting = 0x4
9+
# } Monitor_State;
10+
11+
# typedef enum {
12+
# State_Succeeded = 0x0,
13+
# State_Failed = 0x1,
14+
# State_Changed = 0x2,
15+
# State_ChangedNot = 0x4,
16+
# State_Init = 0x8,
17+
# State_None = State_Init // Alias
18+
# } State_Type;
19+
20+
# typedef enum {
21+
# Service_Filesystem = 0,
22+
# Service_Directory,
23+
# Service_File,
24+
# Service_Process,
25+
# Service_Host,
26+
# Service_System,
27+
# Service_Fifo,
28+
# Service_Program,
29+
# Service_Net,
30+
# Service_Last = Service_Net
31+
# } Service_Type;
2432

2533
import sys
2634
import argparse
@@ -38,7 +46,6 @@
3846
3: 'UNKNOWN'
3947
}
4048

41-
4249
def commandline(args):
4350

4451
parser = argparse.ArgumentParser(prog="check_monit.py")
@@ -57,7 +64,6 @@ def commandline(args):
5764

5865

5966
def print_output(status, count_ok, count_all, items):
60-
6167
s = icinga_status[status]
6268

6369
print(f"[{s}]: Monit Service Status {count_ok}/{count_all}")
@@ -69,16 +75,19 @@ def print_output(status, count_ok, count_all, items):
6975
print(' ' + item['output'])
7076

7177

72-
def service_output(service_type, element):
78+
def get_service_output(service_type, element):
79+
# Service Type Filesystem
7380
if service_type == 0:
7481
block = float(element.findall('block/percent')[0].text)
7582
inode = float(element.findall('inode/percent')[0].text)
7683
return 'user={0}%;inodes={1}%'.format(block, inode)
7784

85+
# Service Type Process
7886
if service_type == 3:
79-
# service type: PROCESS
8087
status = element.find('status').text
8188
return status
89+
90+
# Service Type Host
8291
if service_type == 5:
8392
output = []
8493

@@ -98,66 +107,70 @@ def service_output(service_type, element):
98107

99108
return ';'.join(output)
100109

110+
# Service Type Program
101111
if service_type == 7:
102-
# status = float(element.findall('program/status')[0].text)
103112
return element.findall('program/output')[0].text
104113

105114
return 'Service (type={0}) not implemented'.format(service_type)
106115

116+
def get_service_states(services):
117+
items = []
118+
count_all = 0
119+
count_ok = 0
120+
121+
for service in services:
122+
# Get the monitor state for the service (0: Not, 1: Yes, 2: Init, 4: Waiting)
123+
monitor = int(service.find('monitor').text)
124+
# if the monitor is yes or initialize, check its status
125+
if monitor in (1, 2):
126+
status = int(service.find('status').text)
127+
if status == 0:
128+
count_ok += 1
129+
130+
count_all += 1
131+
132+
items.append({
133+
"name": service.find('name').text,
134+
"status": status,
135+
"output": get_service_output(int(service.get('type')), service)
136+
})
137+
138+
return items, count_all, count_ok
107139

108140
def main(args):
109141
url = '{0}:{1}/_status?format=xml'.format(args.host, args.port)
110142

111143
try:
112144
r = requests.get(url, auth=(args.user, args.password), timeout=5)
113145
except Exception as e: # pylint: disable=broad-except
114-
print('[UNKNOWN]: Monit Socket error={0}'.format(str(e)))
146+
print('[UNKNOWN]: Could not connect to Monit. error={0}'.format(str(e)))
115147
return 3
116148

117149
status_code = r.status_code
118150

119151
if status_code != 200:
120-
print('[UNKNOWN]: Monit HTTP status={0}'.format(status_code))
152+
print('[UNKNOWN]: No valid response from Monit HTTP Server. error={0}'.format(status_code))
121153
return 3
122154

123155
try:
124156
tree = ElementTree.fromstring(r.content)
125157
except Exception as e: # pylint: disable=broad-except
126-
print('[UNKNOWN]: Monit XML error={0}'.format(str(e)))
158+
print('[UNKNOWN]: Unable to parse XML response from Monit HTTP Server. error={0}'.format(str(e)))
127159
return 3
128160

129-
items = []
130161
services = tree.findall('service')
162+
items, count_all, count_ok = get_service_states(services)
131163

132-
count_all = 0
133-
count_ok = 0
134-
135-
for service in services:
136-
monitor = int(service.find('monitor').text)
137-
if monitor in (1, 2):
138-
status = int(service.find('status').text)
139-
if status == 0:
140-
count_ok += 1
141-
142-
count_all += 1
143-
144-
items.append({
145-
"name": service.find('name').text,
146-
"status": status,
147-
"output": service_output(int(service.get('type')), service)
148-
})
149-
150-
status = 0
151-
164+
exit_status = 0
152165
if count_ok < count_all:
153-
status = 2
166+
exit_status = 2
154167

155168
if count_ok == 0:
156-
status = 2
169+
exit_status = 2
157170

158-
print_output(status, count_ok, count_all, items)
171+
print_output(exit_status, count_ok, count_all, items)
159172

160-
return status
173+
return exit_status
161174

162175

163176
if __package__ == '__main__' or __package__ is None: # pragma: no cover

test_check_monit.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import unittest
44
import unittest.mock as mock
5+
import xml.etree.ElementTree as ET
56
import sys
67

78
sys.path.append('..')
@@ -10,6 +11,7 @@
1011
from check_monit import main
1112
from check_monit import commandline
1213
from check_monit import print_output
14+
from check_monit import get_service_output
1315

1416

1517
class MockRequest():
@@ -29,6 +31,19 @@ def test_commandline(self):
2931
self.assertEqual(actual.user, 'user')
3032
self.assertEqual(actual.password, 'password')
3133

34+
def test_service_output(self):
35+
input_element = ET.Element('foobar')
36+
actual = get_service_output(-1, input_element)
37+
self.assertEqual(actual, 'Service (type=-1) not implemented')
38+
39+
input_element = ET.ElementTree(ET.fromstring("""<doc><status>unittest</status></doc>"""))
40+
actual = get_service_output(3, input_element)
41+
self.assertEqual(actual, 'unittest')
42+
43+
input_element = ET.ElementTree(ET.fromstring("""<doc><program><output>foobar</output></program></doc>"""))
44+
actual = get_service_output(7, input_element)
45+
self.assertEqual(actual, 'foobar')
46+
3247
class UtilTesting(unittest.TestCase):
3348

3449
@mock.patch('builtins.print')
@@ -54,7 +69,7 @@ def test_main_request_error(self, mock_get, mock_print):
5469

5570
self.assertEqual(actual, 3)
5671

57-
calls = [mock.call('[UNKNOWN]: Monit Socket error=Boom!')]
72+
calls = [mock.call('[UNKNOWN]: Could not connect to Monit. error=Boom!')]
5873

5974
mock_print.assert_has_calls(calls)
6075

@@ -69,7 +84,7 @@ def test_main_http_error(self, mock_get, mock_print):
6984

7085
self.assertEqual(actual, 3)
7186

72-
calls = [mock.call('[UNKNOWN]: Monit HTTP status=400')]
87+
calls = [mock.call('[UNKNOWN]: No valid response from Monit HTTP Server. error=400')]
7388

7489
mock_print.assert_has_calls(calls)
7590

@@ -84,7 +99,7 @@ def test_main_xml_error(self, mock_get, mock_print):
8499

85100
self.assertEqual(actual, 3)
86101

87-
calls = [mock.call('[UNKNOWN]: Monit XML error=syntax error: line 1, column 0')]
102+
calls = [mock.call('[UNKNOWN]: Unable to parse XML response from Monit HTTP Server. error=syntax error: line 1, column 0')]
88103

89104
mock_print.assert_has_calls(calls)
90105

0 commit comments

Comments
 (0)