From f65f63ef8d43adbec08450e4ca7e77e7ba0629f5 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Tue, 20 Jan 2009 11:18:20 +0000
Subject: [PATCH] Add a TailFile function

This patch adds a tail file function, to be used for parsing and returning in
the job log OS installation failures.

Reviewed-by: ultrotter
---
 lib/utils.py                  | 26 ++++++++++++++++++++
 test/ganeti.utils_unittest.py | 46 ++++++++++++++++++++++++++++++++++-
 2 files changed, 71 insertions(+), 1 deletion(-)

diff --git a/lib/utils.py b/lib/utils.py
index 6a6679c3f..8a4087a42 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -1713,6 +1713,32 @@ def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
       raise
 
 
+def TailFile(fname, lines=20):
+  """Return the last lines from a file.
+
+  @note: this function will only read and parse the last 4KB of
+      the file; if the lines are very long, it could be that less
+      than the requested number of lines are returned
+
+  @param fname: the file name
+  @type lines: int
+  @param lines: the (maximum) number of lines to return
+
+  """
+  fd = open(fname, "r")
+  try:
+    fd.seek(0, 2)
+    pos = fd.tell()
+    pos = max(0, pos-4096)
+    fd.seek(pos, 0)
+    raw_data = fd.read()
+  finally:
+    fd.close()
+
+  rows = raw_data.splitlines()
+  return rows[-lines:]
+
+
 def LockedMethod(fn):
   """Synchronized object access decorator.
 
diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py
index 133d68765..423f5fa0b 100755
--- a/test/ganeti.utils_unittest.py
+++ b/test/ganeti.utils_unittest.py
@@ -42,7 +42,9 @@ from ganeti.utils import IsProcessAlive, RunCmd, \
      RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
      ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
      ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \
-     SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress
+     SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress, \
+     TailFile
+
 from ganeti.errors import LockError, UnitParseError, GenericError, \
      ProgrammerError
 
@@ -772,6 +774,48 @@ class TestFirstFree(unittest.TestCase):
     self.failUnlessRaises(AssertionError, FirstFree, [0, 3, 4, 6], base=3)
 
 
+class TestTailFile(testutils.GanetiTestCase):
+  """Test case for the TailFile function"""
+
+  def testEmpty(self):
+    fname = self._CreateTempFile()
+    self.failUnlessEqual(TailFile(fname), [])
+    self.failUnlessEqual(TailFile(fname, lines=25), [])
+
+  def testAllLines(self):
+    data = ["test %d" % i for i in range(30)]
+    for i in range(30):
+      fname = self._CreateTempFile()
+      fd = open(fname, "w")
+      fd.write("\n".join(data[:i]))
+      if i > 0:
+        fd.write("\n")
+      fd.close()
+      self.failUnlessEqual(TailFile(fname, lines=i), data[:i])
+
+  def testPartialLines(self):
+    data = ["test %d" % i for i in range(30)]
+    fname = self._CreateTempFile()
+    fd = open(fname, "w")
+    fd.write("\n".join(data))
+    fd.write("\n")
+    fd.close()
+    for i in range(1, 30):
+      self.failUnlessEqual(TailFile(fname, lines=i), data[-i:])
+
+  def testBigFile(self):
+    data = ["test %d" % i for i in range(30)]
+    fname = self._CreateTempFile()
+    fd = open(fname, "w")
+    fd.write("X" * 1048576)
+    fd.write("\n")
+    fd.write("\n".join(data))
+    fd.write("\n")
+    fd.close()
+    for i in range(1, 30):
+      self.failUnlessEqual(TailFile(fname, lines=i), data[-i:])
+
+
 class TestFileLock(unittest.TestCase):
   """Test case for the FileLock class"""
 
-- 
GitLab