diff --git a/AUTHORS b/AUTHORS index 1fc5dd4334..73efaffd43 100644 --- a/AUTHORS +++ b/AUTHORS @@ -66,6 +66,7 @@ Paul McMillan (paul.mcmillan@nebula.com) Ewan Mellor (ewan.mellor@citrix.com) Samuel Merritt (sam@swiftstack.com) Stephen Milton (milton@isomedia.com) +Jola Mirecka (jola.mirecka@hp.com) Russ Nelson (russ@crynwr.com) Maru Newby (mnewby@internap.com) Colin Nicholson (colin.nicholson@iomart.com) diff --git a/bin/swift-drive-audit b/bin/swift-drive-audit index 6c742a483e..224f4b4dd7 100755 --- a/bin/swift-drive-audit +++ b/bin/swift-drive-audit @@ -15,13 +15,14 @@ # limitations under the License. import datetime +import glob import os import re import subprocess import sys from ConfigParser import ConfigParser -from swift.common.utils import get_logger +from swift.common.utils import backward, get_logger # To search for more types of errors, add the regex to the list below @@ -61,27 +62,56 @@ def get_devices(device_dir, logger): def get_errors(minutes): + # Assuming log rotation is being used, we need to examine + # recently rotated files in case the rotation occured + # just before the script is being run - the data we are + # looking for may have rotated. + log_files = [f for f in glob.glob('/var/log/kern.*[!.][!g][!z]')] + log_files.sort() + + now_time = datetime.datetime.now() + end_time = now_time - datetime.timedelta(minutes=minutes) + # kern.log does not contain the year so we need to keep + # track of the year and month in case the year recently + # ticked over + year = now_time.year + prev_entry_month = now_time.month errors = {} - start_time = datetime.datetime.now() - datetime.timedelta(minutes=minutes) - try: - for line in open('/var/log/kern.log'): - if '[ 0.000000]' in line: + + reached_old_logs = False + for path in log_files: + try: + f = open(path) + except IOError: + logger.error("Error: Unable to open " + path) + print("Unable to open " + path) + sys.exit(1) + for line in backward(f): + if '[ 0.000000]' in line \ + or 'KERNEL supported cpus:' in line \ + or 'BIOS-provided physical RAM map:' in line: # Ignore anything before the last boot - errors = {} - continue - log_time_string = '%s %s' % (start_time.year, - ' '.join(line.split()[:3])) + reached_old_logs = True + break + # Solves the problem with year change - kern.log does not + # keep track of the year. + log_time_entry = line.split()[:3] + if log_time_entry[0] == 'Dec' and prev_entry_month == 'Jan': + year -= 1 + prev_entry_month = log_time_entry[0] + log_time_string = '%s %s' % (year, ' '.join(log_time_entry)) log_time = datetime.datetime.strptime( log_time_string, '%Y %b %d %H:%M:%S') - if log_time > start_time: + if log_time > end_time: for err in error_re: for device in err.findall(line): errors[device] = errors.get(device, 0) + 1 - return errors - except IOError: - logger.error("Error: Unable to open /var/log/kern.log") - print("Unable to open /var/log/kern.log") - sys.exit(1) + else: + reached_old_logs = True + break + if reached_old_logs: + break + return errors def comment_fstab(mount_point): diff --git a/swift/common/utils.py b/swift/common/utils.py index ebe4443f82..74df8e0471 100644 --- a/swift/common/utils.py +++ b/swift/common/utils.py @@ -84,6 +84,39 @@ if hash_conf.read('/etc/swift/swift.conf'): except (NoSectionError, NoOptionError): pass + +def backward(f, blocksize=4096): + """ + A generator returning lines from a file starting with the last line, + then the second last line, etc. i.e., it reads lines backwards. + Stops when the first line (if any) is read. + This is useful when searching for recent activity in very + large files. + + :param f: file object to read + :param blocksize: no of characters to go backwards at each block + """ + f.seek(0, os.SEEK_END) + if f.tell() == 0: + return + last_row = '' + while f.tell() != 0: + try: + f.seek(-blocksize, os.SEEK_CUR) + except IOError: + blocksize = f.tell() + f.seek(-blocksize, os.SEEK_CUR) + block = f.read(blocksize) + f.seek(-blocksize, os.SEEK_CUR) + rows = block.split('\n') + rows[-1] = rows[-1] + last_row + while rows: + last_row = rows.pop(-1) + if rows and last_row: + yield last_row + yield last_row + + # Used when reading config values TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y')) diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py index ce166e7db6..5cf5600c96 100644 --- a/test/unit/common/test_utils.py +++ b/test/unit/common/test_utils.py @@ -147,6 +147,36 @@ class TestUtils(unittest.TestCase): self.assertRaises(ValueError, utils.normalize_timestamp, '') self.assertRaises(ValueError, utils.normalize_timestamp, 'abc') + def test_backwards(self): + """ Test swift.common.utils.backward """ + + # The lines are designed so that the function would encounter + # all of the boundary conditions and typical conditions. + # Block boundaries are marked with '<>' characters + blocksize = 25 + lines = ['123456789x12345678><123456789\n', # block larger than rest + '123456789x123>\n', # block ends just before \n character + '123423456789\n', + '123456789x\n', # block ends at the end of line + '<123456789x123456789x123\n', + '<6789x123\n', # block ends at the beginning of the line + '6789x1234\n', + '1234><234\n', # block ends typically in the middle of line + '123456789x123456789\n'] + + with TemporaryFile('r+w') as f: + for line in lines: + f.write(line) + + count = len(lines) - 1 + for line in utils.backward(f, blocksize): + self.assertEquals(line, lines[count].split('\n')[0]) + count -= 1 + + # Empty file case + with TemporaryFile('r') as f: + self.assertEquals([], list(utils.backward(f))) + def test_mkdirs(self): testroot = os.path.join(os.path.dirname(__file__), 'mkdirs') try: