From 3840729daa88e268c09cf64665b6769d426a40c8 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Mon, 7 Jan 2008 11:22:40 +0000
Subject: [PATCH] Add unittest for DRBD8 drdbsetup show parser

This patch changes the bdev.DRBD8._GetDevInfo to take a string instead
of a minor, separates the `drbdsetup show` invocation into a new
separate method (bdev.DRBD8._GetShowData) and modifies the rest of the
DRBD8 class to make the appropriate calls.

It also adds a unittest script and data files for testing various cases
of device output.

Reviewed-by: imsnah
---
 lib/bdev.py                  | 39 +++++++++------
 test/Makefile.am             |  4 +-
 test/data/bdev-both.txt      | 35 +++++++++++++
 test/data/bdev-disk.txt      | 15 ++++++
 test/data/bdev-net.txt       | 28 +++++++++++
 test/ganeti.bdev_unittest.py | 96 ++++++++++++++++++++++++++++++++++++
 6 files changed, 201 insertions(+), 16 deletions(-)
 create mode 100644 test/data/bdev-both.txt
 create mode 100644 test/data/bdev-disk.txt
 create mode 100644 test/data/bdev-net.txt
 create mode 100755 test/ganeti.bdev_unittest.py

diff --git a/lib/bdev.py b/lib/bdev.py
index 5069bb2fe..8b2797191 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -1737,19 +1737,27 @@ class DRBD8(BaseDRBD):
     return bnf
 
   @classmethod
-  def _GetDevInfo(cls, minor):
-    """Get details about a given DRBD minor.
-
-    This return, if available, the local backing device (as a path)
-    and the local and remote (ip, port) information.
+  def _GetShowData(cls, minor):
+    """Return the `drbdsetup show` data for a minor.
 
     """
-    data = {}
     result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
     if result.failed:
       logger.Error("Can't display the drbd config: %s" % result.fail_reason)
-      return data
-    out = result.stdout
+      return None
+    return result.stdout
+
+  @classmethod
+  def _GetDevInfo(cls, out):
+    """Parse details about a given DRBD minor.
+
+    This return, if available, the local backing device (as a path)
+    and the local and remote (ip, port) information from a string
+    containing the output of the `drbdsetup show` command as returned
+    by _GetShowData.
+
+    """
+    data = {}
     if not out:
       return data
 
@@ -1881,7 +1889,7 @@ class DRBD8(BaseDRBD):
     timeout = time.time() + 10
     ok = False
     while time.time() < timeout:
-      info = cls._GetDevInfo(minor)
+      info = cls._GetDevInfo(cls._GetShowData(minor))
       if not "local_addr" in info or not "remote_addr" in info:
         time.sleep(1)
         continue
@@ -1904,7 +1912,7 @@ class DRBD8(BaseDRBD):
       raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
     if len(devices) != 2:
       raise errors.BlockDeviceError("Need two devices for AddChildren")
-    info = self._GetDevInfo(self.minor)
+    info = self._GetDevInfo(self._GetShowData(self.minor))
     if "local_dev" in info:
       raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
     backend, meta = devices
@@ -1930,7 +1938,7 @@ class DRBD8(BaseDRBD):
       raise errors.BlockDeviceError("Can't attach to drbd8 during"
                                     " RemoveChildren")
     # early return if we don't actually have backing storage
-    info = self._GetDevInfo(self.minor)
+    info = self._GetDevInfo(self._GetShowData(self.minor))
     if "local_dev" not in info:
       return
     if len(self._children) != 2:
@@ -2083,7 +2091,7 @@ class DRBD8(BaseDRBD):
 
     """
     for minor in self._GetUsedDevs():
-      info = self._GetDevInfo(minor)
+      info = self._GetDevInfo(self._GetShowData(minor))
       match_l = self._MatchesLocal(info)
       match_r = self._MatchesNet(info)
       if match_l and match_r:
@@ -2093,8 +2101,9 @@ class DRBD8(BaseDRBD):
                                   (self._lhost, self._lport,
                                    self._rhost, self._rport),
                                   "C")
-        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
-          break
+        if res_r:
+          if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
+            break
       # the weakest case: we find something that is only net attached
       # even though we were passed some children at init time
       if match_r and "local_dev" not in info:
@@ -2114,7 +2123,7 @@ class DRBD8(BaseDRBD):
         # None)
         if (self._AssembleNet(minor, (self._lhost, self._lport,
                                       self._rhost, self._rport), "C") and
-            self._MatchesNet(self._GetDevInfo(minor))):
+            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
           break
 
     else:
diff --git a/test/Makefile.am b/test/Makefile.am
index 83c7b9b00..bd221006c 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,7 +1,9 @@
 TESTS = \
   ganeti.config_unittest.py \
   ganeti.hooks_unittest.py \
-  ganeti.utils_unittest.py
+  ganeti.utils_unittest.py \
+  ganeti.bdev_unittest.py
+
 TESTS_ENVIRONMENT = PYTHONPATH=.:$(top_builddir)
 
 check-am: do-pre-check
diff --git a/test/data/bdev-both.txt b/test/data/bdev-both.txt
new file mode 100644
index 000000000..b40f5e831
--- /dev/null
+++ b/test/data/bdev-both.txt
@@ -0,0 +1,35 @@
+disk {
+	size            	0s _is_default; # bytes
+	on-io-error     	detach;
+	fencing         	dont-care _is_default;
+}
+net {
+	timeout         	60 _is_default; # 1/10 seconds
+	max-epoch-size  	16384;
+	max-buffers     	16384;
+	unplug-watermark	128 _is_default;
+	connect-int     	10 _is_default; # seconds
+	ping-int        	10 _is_default; # seconds
+	sndbuf-size     	8388608; # bytes
+	ko-count        	0 _is_default;
+	after-sb-0pri   	disconnect _is_default;
+	after-sb-1pri   	disconnect _is_default;
+	after-sb-2pri   	disconnect _is_default;
+	rr-conflict     	disconnect _is_default;
+	ping-timeout    	5 _is_default; # 1/10 seconds
+}
+syncer {
+	rate            	30720k; # bytes/second
+	after           	-1 _is_default;
+	al-extents      	257;
+}
+protocol A;
+_this_host {
+	device			"/dev/drbd63";
+	disk			"/dev/xenvg/test.data";
+	meta-disk		"/dev/xenvg/test.meta" [ 0 ];
+	address			192.168.1.1:11000;
+}
+_remote_host {
+	address			192.168.1.2:11000;
+}
diff --git a/test/data/bdev-disk.txt b/test/data/bdev-disk.txt
new file mode 100644
index 000000000..caed5009c
--- /dev/null
+++ b/test/data/bdev-disk.txt
@@ -0,0 +1,15 @@
+disk {
+	size            	0s _is_default; # bytes
+	on-io-error     	detach;
+	fencing         	dont-care _is_default;
+}
+syncer {
+	rate            	250k _is_default; # bytes/second
+	after           	-1 _is_default;
+	al-extents      	257;
+}
+_this_host {
+	device			"/dev/drbd58";
+	disk			"/dev/xenvg/test.data";
+	meta-disk		"/dev/xenvg/test.meta" [ 0 ];
+}
diff --git a/test/data/bdev-net.txt b/test/data/bdev-net.txt
new file mode 100644
index 000000000..eae1f4c0d
--- /dev/null
+++ b/test/data/bdev-net.txt
@@ -0,0 +1,28 @@
+net {
+	timeout         	60 _is_default; # 1/10 seconds
+	max-epoch-size  	2048 _is_default;
+	max-buffers     	2048 _is_default;
+	unplug-watermark	128 _is_default;
+	connect-int     	10 _is_default; # seconds
+	ping-int        	10 _is_default; # seconds
+	sndbuf-size     	131070 _is_default; # bytes
+	ko-count        	0 _is_default;
+	after-sb-0pri   	disconnect _is_default;
+	after-sb-1pri   	disconnect _is_default;
+	after-sb-2pri   	disconnect _is_default;
+	rr-conflict     	disconnect _is_default;
+	ping-timeout    	5 _is_default; # 1/10 seconds
+}
+syncer {
+	rate            	250k _is_default; # bytes/second
+	after           	-1 _is_default;
+	al-extents      	127 _is_default;
+}
+protocol C;
+_this_host {
+	device			"/dev/drbd59";
+	address			192.168.1.1:11002;
+}
+_remote_host {
+	address			192.168.1.2:11002;
+}
diff --git a/test/ganeti.bdev_unittest.py b/test/ganeti.bdev_unittest.py
new file mode 100755
index 000000000..5a775c796
--- /dev/null
+++ b/test/ganeti.bdev_unittest.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2006, 2007 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for unittesting the bdev module"""
+
+
+import unittest
+
+from ganeti import bdev
+
+
+class TestDRBD8Runner(unittest.TestCase):
+  """Testing case for DRBD8"""
+
+  @staticmethod
+  def _has_disk(data, dname, mname):
+    """Check local disk corectness"""
+    retval = (
+      "local_dev" in data and
+      data["local_dev"] == dname and
+      "meta_dev" in data and
+      data["meta_dev"] == mname and
+      "meta_index" in data and
+      data["meta_index"] == 0
+      )
+    return retval
+
+  @staticmethod
+  def _has_net(data, local, remote):
+    """Check network connection parameters"""
+    retval = (
+      "local_addr" in data and
+      data["local_addr"] == local and
+      "remote_addr" in data and
+      data["remote_addr"] == remote
+      )
+    return retval
+
+  def testParserCreation(self):
+    """Test drbdsetup show parser creation"""
+    bdev.DRBD8._GetShowParser()
+
+  def testParserBoth(self):
+    """Test drbdsetup show parser for disk and network"""
+    data = open("data/bdev-both.txt").read()
+    result = bdev.DRBD8._GetDevInfo(data)
+    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
+                                   "/dev/xenvg/test.meta"),
+                    "Wrong local disk info")
+    self.failUnless(self._has_net(result, ("192.168.1.1", 11000),
+                                  ("192.168.1.2", 11000)),
+                    "Wrong network info")
+
+  def testParserNet(self):
+    """Test drbdsetup show parser for disk and network"""
+    data = open("data/bdev-net.txt").read()
+    result = bdev.DRBD8._GetDevInfo(data)
+    self.failUnless(("local_dev" not in result and
+                     "meta_dev" not in result and
+                     "meta_index" not in result),
+                    "Should not find local disk info")
+    self.failUnless(self._has_net(result, ("192.168.1.1", 11002),
+                                  ("192.168.1.2", 11002)),
+                    "Wrong network info")
+
+  def testParserDisk(self):
+    """Test drbdsetup show parser for disk and network"""
+    data = open("data/bdev-disk.txt").read()
+    result = bdev.DRBD8._GetDevInfo(data)
+    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
+                                   "/dev/xenvg/test.meta"),
+                    "Wrong local disk info")
+    self.failUnless(("local_addr" not in result and
+                     "remote_addr" not in result),
+                    "Should not find network info")
+
+if __name__ == '__main__':
+  unittest.main()
-- 
GitLab