d59759259e
Update on nrpe check check_swift_storage: * Refactor check_replication function * Raise STATUS_CRIT on null values * Clean code per flake8 and previous review * Add unit_test for nrpe check check_swift_storage.py Change-Id: Ie076b8ea56f66c1e15a9bfe2e400b49f968fa4ed Closes-Bug: #1673441 Signed-off-by: Alvaro Uria <alvaro.uria@canonical.com>
388 lines
17 KiB
Python
388 lines
17 KiB
Python
# Copyright 2017 Canonical Ltd
|
|
#
|
|
# Licensed 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 datetime
|
|
import sys
|
|
import unittest
|
|
import urllib2
|
|
|
|
from mock import (
|
|
patch,
|
|
mock_open,
|
|
MagicMock,
|
|
Mock,
|
|
PropertyMock,
|
|
)
|
|
|
|
sys.path.append('files/nrpe-external-master')
|
|
from check_swift_storage import (
|
|
check_md5,
|
|
check_replication,
|
|
generate_md5,
|
|
repl_last_timestamp,
|
|
)
|
|
|
|
|
|
STATUS_OK = 0
|
|
STATUS_WARN = 1
|
|
STATUS_CRIT = 2
|
|
STATUS_UNKNOWN = 3
|
|
|
|
|
|
class NewDate(datetime.datetime):
|
|
@classmethod
|
|
def now(cls):
|
|
"""
|
|
Mock for builtin datetime.datetime.now(), to test repl_last_timestamp()
|
|
Non-defined methods are inherited from real class
|
|
"""
|
|
return cls(2017, 4, 27,
|
|
13, 25, 46, 629282)
|
|
|
|
@classmethod
|
|
def fromtimestamp(cls, timestamp):
|
|
return cls.utcfromtimestamp(timestamp)
|
|
|
|
|
|
datetime.datetime = NewDate
|
|
|
|
|
|
class CheckSwiftStorageTestCase(unittest.TestCase):
|
|
def test_generate_md5(self):
|
|
"""
|
|
Ensure md5 checksum is generated from a file content
|
|
"""
|
|
with patch("__builtin__.open", mock_open(read_data='data')) as \
|
|
mock_file:
|
|
result = generate_md5('path/to/file')
|
|
mock_file.assert_called_with('path/to/file', 'rb')
|
|
# md5 hash for 'data' string
|
|
self.assertEqual(result,
|
|
'8d777f385d3dfec8815d20f7496026dc')
|
|
|
|
@patch('urllib2.urlopen')
|
|
def test_check_md5_unknown_urlerror(self, mock_urlopen):
|
|
"""
|
|
Force urllib2.URLError to test try-except
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
url = '{}ringmd5'.format(base_url)
|
|
error = 'connection refused'
|
|
mock_urlopen.side_effect = urllib2.URLError(Mock(return_value=error))
|
|
result = check_md5(base_url)
|
|
self.assertEqual(result,
|
|
[(STATUS_UNKNOWN,
|
|
"Can't open url: {}".format(url))])
|
|
|
|
@patch('urllib2.urlopen')
|
|
def test_check_md5_unknown_valueerror1(self, mock_urlopen):
|
|
"""
|
|
Force ValueError on urllib2 to test try-except
|
|
"""
|
|
base_url = 'asdfasdf'
|
|
url = '{}ringmd5'.format(base_url)
|
|
mock_urlopen.side_effect = ValueError(Mock(return_value=''))
|
|
result = check_md5(base_url)
|
|
mock_urlopen.assert_called_with(url)
|
|
self.assertEqual(result,
|
|
[(STATUS_UNKNOWN,
|
|
"Can't parse status data")])
|
|
|
|
@patch('urllib2.urlopen')
|
|
def test_check_md5_unknown_valueerror2(self, mock_urlopen):
|
|
"""
|
|
Force ValueError on json to test try-catch
|
|
"""
|
|
jdata = PropertyMock(return_value='X')
|
|
mock_urlopen.return_value = MagicMock(read=jdata)
|
|
result = check_md5('.')
|
|
mock_urlopen.assert_called_with('.ringmd5')
|
|
self.assertEqual(result,
|
|
[(STATUS_UNKNOWN,
|
|
"Can't parse status data")])
|
|
|
|
@patch('check_swift_storage.generate_md5')
|
|
def test_check_md5_unknown_ioerror(self, mock_generate_md5):
|
|
"""
|
|
Force IOError (reading file) to test try-catch
|
|
"""
|
|
jdata = '{"/etc/swift/object.ring.gz": ' \
|
|
'"6b4f3a0ef3731f18291ecd053ce0d9b6", ' \
|
|
'"/etc/swift/account.ring.gz": ' \
|
|
'"93fc4ae496a7343362ebf13988a137e7", ' \
|
|
'"/etc/swift/container.ring.gz": ' \
|
|
'"0ea1ec9585ef644ce2b5c5b1dced4128"}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_generate_md5.side_effect = IOError()
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_md5('.')
|
|
mock_urlopen.assert_called_with('.ringmd5')
|
|
expected_result = [(STATUS_UNKNOWN,
|
|
"Can't open ringfile "
|
|
"/etc/swift/{}.ring.gz".format(name))
|
|
for name in ('object', 'account', 'container')]
|
|
self.assertEqual(result, expected_result)
|
|
|
|
@patch('check_swift_storage.generate_md5')
|
|
def test_check_md5_crit_md5sum_mismatch(self, mock_generate_md5):
|
|
"""
|
|
Ensure md5 checksums match, STATUS_CRIT
|
|
"""
|
|
jdata = '{"/etc/swift/object.ring.gz": ' \
|
|
'"6b4f3a0ef3731f18291ecd053ce0d9b6", ' \
|
|
'"/etc/swift/account.ring.gz": ' \
|
|
'"93fc4ae496a7343362ebf13988a137e7", ' \
|
|
'"/etc/swift/container.ring.gz": ' \
|
|
'"0ea1ec9585ef644ce2b5c5b1dced4128"}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_generate_md5.return_value = 'xxxx'
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_md5('.')
|
|
mock_urlopen.assert_called_with('.ringmd5')
|
|
expected_result = [(STATUS_CRIT,
|
|
'Ringfile /etc/swift/{}.ring.gz '
|
|
'MD5 sum mismatch'.format(name))
|
|
for name in ('object', 'account', 'container')]
|
|
self.assertEqual(result, expected_result)
|
|
|
|
@patch('check_swift_storage.generate_md5')
|
|
def test_check_md5_ok(self, mock_generate_md5):
|
|
"""
|
|
Ensure md5 checksums match, STATUS_OK
|
|
"""
|
|
jdata = '{"/etc/swift/object.ring.gz": ' \
|
|
'"6b4f3a0ef3731f18291ecd053ce0d9b6", ' \
|
|
'"/etc/swift/account.ring.gz": ' \
|
|
'"6b4f3a0ef3731f18291ecd053ce0d9b6", ' \
|
|
'"/etc/swift/container.ring.gz": ' \
|
|
'"6b4f3a0ef3731f18291ecd053ce0d9b6"}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_generate_md5.return_value = '6b4f3a0ef3731f18291ecd053ce0d9b6'
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_md5('.')
|
|
mock_urlopen.assert_called_with('.ringmd5')
|
|
self.assertEqual(result,
|
|
[(STATUS_OK, 'OK')])
|
|
|
|
@patch('urllib2.urlopen')
|
|
def test_check_replication_unknown_urlerror(self, mock_urlopen):
|
|
"""
|
|
Force urllib2.URLError to test try-catch
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
url = '{}replication/{}'
|
|
error = 'connection refused'
|
|
mock_urlopen.side_effect = urllib2.URLError(Mock(return_value=error))
|
|
result = check_replication(base_url, 60)
|
|
expected_result = [(STATUS_UNKNOWN,
|
|
"Can't open url: "
|
|
"{}".format(url.format(base_url, name)))
|
|
for name in ('account', 'object', 'container')]
|
|
self.assertEqual(result, expected_result)
|
|
|
|
@patch('urllib2.urlopen')
|
|
def test_check_replication_unknown_valueerror1(self, mock_urlopen):
|
|
"""
|
|
Force ValueError on urllib2 to test try-catch
|
|
"""
|
|
base_url = '.'
|
|
mock_urlopen.side_effect = ValueError(Mock(return_value=''))
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
3*[(STATUS_UNKNOWN,
|
|
"Can't parse status data")])
|
|
|
|
@patch('urllib2.urlopen')
|
|
def test_check_replication_unknown_valueerror2(self, mock_urlopen):
|
|
"""
|
|
Force ValueError on json to test try-catch
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = PropertyMock(return_value='X')
|
|
mock_urlopen.return_value = MagicMock(read=jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
3*[(STATUS_UNKNOWN,
|
|
"Can't parse status data")])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_crit_lag_notworking(self, mock_timestamp):
|
|
"""
|
|
Catch NULL replication value, STATUS_CRIT
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (None, 0)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
[(STATUS_CRIT,
|
|
"'{}' replication lag not working "
|
|
"(perms issue? check syslog)".format(repl))
|
|
for repl in ('account', 'object', 'container')])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_crit_lag(self, mock_timestamp):
|
|
"""
|
|
Replication lag over CRIT threshold, STATUS_CRIT
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (MagicMock(seconds=12), 0)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
[(STATUS_CRIT,
|
|
"'{}' replication lag is "
|
|
"12 seconds".format(repl))
|
|
for repl in ('account', 'object', 'container')])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_crit_failures(self, mock_timestamp):
|
|
"""
|
|
Replication failures over CRIT threshold, STATUS_CRIT
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (MagicMock(seconds=0), 12)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
3*[(STATUS_CRIT, "12 replication failures")])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_crit_null_failures(self, mock_timestamp):
|
|
"""
|
|
Catch NULL value on failures stats, STATUS_CRIT
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (MagicMock(seconds=0), -1)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
3*[(STATUS_CRIT,
|
|
"replication failures counter is NULL "
|
|
"(check syslog)")])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_warn_lag(self, mock_timestamp):
|
|
"""
|
|
Replication lag over WARN threshold (below CRIT), STATUS_WARN
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (MagicMock(seconds=5), 0)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
[(STATUS_WARN,
|
|
"'{}' replication lag is "
|
|
"5 seconds".format(repl))
|
|
for repl in ('account', 'object', 'container')])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_warn_failures(self, mock_timestamp):
|
|
"""
|
|
Replication failures over WARN threshold (below CRIT), STATUS_WARN
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (MagicMock(seconds=0), 5)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result,
|
|
3*[(STATUS_WARN, "5 replication failures")])
|
|
|
|
@patch('check_swift_storage.repl_last_timestamp')
|
|
def test_check_replication_ok(self, mock_timestamp):
|
|
"""
|
|
Replication lag and number of failures are below WARN threshold,
|
|
STATUS_OK
|
|
"""
|
|
base_url = 'http://localhost:6000/recon/'
|
|
jdata = '{"replication_last": 1493299546.629282, ' \
|
|
'"replication_stats": {"no_change": 0, "rsync": 0, ' \
|
|
'"success": 0, "failure": 0, "attempted": 0, "ts_repl": 0, ' \
|
|
'"remove": 0, "remote_merge": 0, "diff_capped": 0, ' \
|
|
'"start": 1493299546.621624, "hashmatch": 0, "diff": 0, ' \
|
|
'"empty": 0}, "replication_time": 0.0076580047607421875}'
|
|
pmock_jdata = PropertyMock(return_value=jdata)
|
|
mock_timestamp.return_value = (MagicMock(seconds=0), 0)
|
|
with patch('urllib2.urlopen') as mock_urlopen:
|
|
mock_urlopen.return_value = MagicMock(read=pmock_jdata)
|
|
result = check_replication(base_url, [4, 10, 4, 10])
|
|
self.assertEqual(result, [(STATUS_OK, 'OK')])
|
|
|
|
def test_repl_last_timestamp(self):
|
|
"""
|
|
Calculates delta between NOW and last replication date
|
|
Also gathers the number of failures
|
|
"""
|
|
# 1493299546.629282
|
|
jdata = {u'replication_last': 1493299546.629282, u'replication_stats':
|
|
{u'no_change': 0, u'rsync': 0, u'success': 0, u'start':
|
|
1493299546.621624, u'attempted': 0, u'ts_repl': 0, u'remove':
|
|
0, u'remote_merge': 0, u'diff_capped': 0, u'failure': 0,
|
|
u'hashmatch': 0, u'diff': 0, u'empty': 0},
|
|
u'replication_time': 0.0076580047607421875}
|
|
result = repl_last_timestamp(jdata)
|
|
self.assertEqual(result,
|
|
(datetime.timedelta(0), 0))
|