From 55cc0a44d56be13d3cf735c67c19a5d9849086cf Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Fri, 7 Jan 2011 16:34:19 +0100
Subject: [PATCH] Use new console information in hypervisor abstraction
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This makes use of the new way of returning console information from the
master daemon. Unittests are included.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: RenΓ© Nussbaumer <rn@google.com>
---
 Makefile.am                                  |  4 ++
 lib/cmdlib.py                                |  9 +---
 lib/hypervisor/hv_base.py                    |  4 +-
 lib/hypervisor/hv_chroot.py                  | 21 +++++---
 lib/hypervisor/hv_fake.py                    | 10 ++--
 lib/hypervisor/hv_kvm.py                     | 38 ++++++++------
 lib/hypervisor/hv_lxc.py                     |  9 +++-
 lib/hypervisor/hv_xen.py                     | 10 ++--
 test/ganeti.hypervisor.hv_chroot_unittest.py | 54 ++++++++++++++++++++
 test/ganeti.hypervisor.hv_fake_unittest.py   | 44 ++++++++++++++++
 test/ganeti.hypervisor.hv_kvm_unittest.py    | 46 ++++++++++++++++-
 test/ganeti.hypervisor.hv_lxc_unittest.py    | 46 +++++++++++++++++
 test/ganeti.hypervisor.hv_xen_unittest.py    | 48 +++++++++++++++++
 13 files changed, 301 insertions(+), 42 deletions(-)
 create mode 100755 test/ganeti.hypervisor.hv_chroot_unittest.py
 create mode 100755 test/ganeti.hypervisor.hv_fake_unittest.py
 create mode 100755 test/ganeti.hypervisor.hv_lxc_unittest.py
 create mode 100755 test/ganeti.hypervisor.hv_xen_unittest.py

diff --git a/Makefile.am b/Makefile.am
index 6c25dcaaa..612a98a5e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -449,7 +449,11 @@ python_tests = \
 	test/ganeti.ht_unittest.py \
 	test/ganeti.http_unittest.py \
 	test/ganeti.hypervisor_unittest.py \
+	test/ganeti.hypervisor.hv_chroot_unittest.py \
+	test/ganeti.hypervisor.hv_fake_unittest.py \
 	test/ganeti.hypervisor.hv_kvm_unittest.py \
+	test/ganeti.hypervisor.hv_lxc_unittest.py \
+	test/ganeti.hypervisor.hv_xen_unittest.py \
 	test/ganeti.impexpd_unittest.py \
 	test/ganeti.jqueue_unittest.py \
 	test/ganeti.locking_unittest.py \
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 996489e5c..5a4a08fa5 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -7691,14 +7691,9 @@ class LUConnectConsole(NoHooksLU):
     # instance and then saving the defaults in the instance itself.
     hvparams = cluster.FillHV(instance)
     beparams = cluster.FillBE(instance)
-    console_cmd = hyper.GetShellCommandForConsole(instance, hvparams, beparams)
-
-    console = objects.InstanceConsole(instance=instance.name,
-                                      kind=constants.CONS_SSH,
-                                      host=node,
-                                      user="root",
-                                      command=console_cmd)
+    console = hyper.GetInstanceConsole(instance, hvparams, beparams)
 
+    assert console.instance == instance.name
     assert console.Validate()
 
     return console.ToDict()
diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index 80675eedd..ec474005c 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -206,8 +206,8 @@ class BaseHypervisor(object):
     raise NotImplementedError
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
-    """Return a command for connecting to the console of an instance.
+  def GetInstanceConsole(cls, instance, hvparams, beparams):
+    """Return information for connecting to the console of an instance.
 
     """
     raise NotImplementedError
diff --git a/lib/hypervisor/hv_chroot.py b/lib/hypervisor/hv_chroot.py
index f33fed204..5b26e8b44 100644
--- a/lib/hypervisor/hv_chroot.py
+++ b/lib/hypervisor/hv_chroot.py
@@ -31,6 +31,7 @@ import logging
 from ganeti import constants
 from ganeti import errors # pylint: disable-msg=W0611
 from ganeti import utils
+from ganeti import objects
 from ganeti.hypervisor import hv_base
 from ganeti.errors import HypervisorError
 
@@ -246,15 +247,21 @@ class ChrootManager(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
-    """Return a command for connecting to the console of an instance.
+  def GetInstanceConsole(cls, instance, # pylint: disable-msg=W0221
+                         hvparams, beparams, root_dir=None):
+    """Return information for connecting to the console of an instance.
 
     """
-    root_dir = cls._InstanceDir(instance.name)
-    if not os.path.ismount(root_dir):
-      raise HypervisorError("Instance %s is not running" % instance.name)
-
-    return "chroot %s" % root_dir
+    if root_dir is None:
+      root_dir = cls._InstanceDir(instance.name)
+      if not os.path.ismount(root_dir):
+        raise HypervisorError("Instance %s is not running" % instance.name)
+
+    return objects.InstanceConsole(instance=instance.name,
+                                   kind=constants.CONS_SSH,
+                                   host=instance.primary_node,
+                                   user=constants.GANETI_RUNAS,
+                                   command=["chroot", root_dir])
 
   def Verify(self):
     """Verify the hypervisor.
diff --git a/lib/hypervisor/hv_fake.py b/lib/hypervisor/hv_fake.py
index 2d386d2e3..9e5f96e32 100644
--- a/lib/hypervisor/hv_fake.py
+++ b/lib/hypervisor/hv_fake.py
@@ -30,6 +30,7 @@ import logging
 from ganeti import utils
 from ganeti import constants
 from ganeti import errors
+from ganeti import objects
 from ganeti.hypervisor import hv_base
 
 
@@ -204,11 +205,14 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     return result
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
-    """Return a command for connecting to the console of an instance.
+  def GetInstanceConsole(cls, instance, hvparams, beparams):
+    """Return information for connecting to the console of an instance.
 
     """
-    return "echo Console not available for fake hypervisor"
+    return objects.InstanceConsole(instance=instance.name,
+                                   kind=constants.CONS_MESSAGE,
+                                   message=("Console not available for fake"
+                                            " hypervisor"))
 
   def Verify(self):
     """Verify the hypervisor.
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index 0538ffb62..46f41c5b4 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -1035,28 +1035,33 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
     if hvparams[constants.HV_SERIAL_CONSOLE]:
-      shell_command = ("%s STDIO,%s UNIX-CONNECT:%s" %
-                       (constants.SOCAT_PATH, cls._SocatUnixConsoleParams(),
-                        utils.ShellQuote(cls._InstanceSerial(instance.name))))
-    else:
-      shell_command = "echo 'No serial shell for instance %s'" % instance.name
+      cmd = [constants.SOCAT_PATH,
+             "STDIO,%s" % cls._SocatUnixConsoleParams(),
+             "UNIX-CONNECT:%s" % cls._InstanceSerial(instance.name)]
+      return objects.InstanceConsole(instance=instance.name,
+                                     kind=constants.CONS_SSH,
+                                     host=instance.primary_node,
+                                     user=constants.GANETI_RUNAS,
+                                     command=cmd)
 
     vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
-    if vnc_bind_address:
-      if instance.network_port > constants.VNC_BASE_PORT:
-        display = instance.network_port - constants.VNC_BASE_PORT
-        vnc_command = ("echo 'Instance has VNC listening on %s:%d"
-                       " (display: %d)'" % (vnc_bind_address,
-                                            instance.network_port,
-                                            display))
-        shell_command = "%s; %s" % (vnc_command, shell_command)
-
-    return shell_command
+    if vnc_bind_address and instance.network_port > constants.VNC_BASE_PORT:
+      display = instance.network_port - constants.VNC_BASE_PORT
+      return objects.InstanceConsole(instance=instance.name,
+                                     kind=constants.CONS_VNC,
+                                     host=vnc_bind_address,
+                                     port=instance.network_port,
+                                     display=display)
+
+    return objects.InstanceConsole(instance=instance.name,
+                                   kind=constants.CONS_MESSAGE,
+                                   message=("No serial shell for instance %s" %
+                                            instance.name))
 
   def Verify(self):
     """Verify the hypervisor.
@@ -1069,7 +1074,6 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     if not os.path.exists(constants.SOCAT_PATH):
       return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
 
-
   @classmethod
   def CheckParameterSyntax(cls, hvparams):
     """Check the given parameters for validity.
diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index 3779a68cd..78c1f3467 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -31,6 +31,7 @@ import logging
 from ganeti import constants
 from ganeti import errors # pylint: disable-msg=W0611
 from ganeti import utils
+from ganeti import objects
 from ganeti.hypervisor import hv_base
 from ganeti.errors import HypervisorError
 
@@ -374,11 +375,15 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     return self.GetLinuxNodeInfo()
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
-    return "lxc-console -n %s" % instance.name
+    return objects.InstanceConsole(instance=instance.name,
+                                   kind=constants.CONS_SSH,
+                                   host=instance.primary_node,
+                                   user=constants.GANETI_RUNAS,
+                                   command=["lxc-console", "-n", instance.name])
 
   def Verify(self):
     """Verify the hypervisor.
diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py
index 5d6de5cc8..8d36e202a 100644
--- a/lib/hypervisor/hv_xen.py
+++ b/lib/hypervisor/hv_xen.py
@@ -31,6 +31,7 @@ from ganeti import errors
 from ganeti import utils
 from ganeti.hypervisor import hv_base
 from ganeti import netutils
+from ganeti import objects
 
 
 class XenHypervisor(hv_base.BaseHypervisor):
@@ -294,12 +295,15 @@ class XenHypervisor(hv_base.BaseHypervisor):
     return result
 
   @classmethod
-  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
+  def GetInstanceConsole(cls, instance, hvparams, beparams):
     """Return a command for connecting to the console of an instance.
 
     """
-    return "xm console %s" % instance.name
-
+    return objects.InstanceConsole(instance=instance.name,
+                                   kind=constants.CONS_SSH,
+                                   host=instance.primary_node,
+                                   user=constants.GANETI_RUNAS,
+                                   command=["xm", "console", instance.name])
 
   def Verify(self):
     """Verify the hypervisor.
diff --git a/test/ganeti.hypervisor.hv_chroot_unittest.py b/test/ganeti.hypervisor.hv_chroot_unittest.py
new file mode 100755
index 000000000..233808c4a
--- /dev/null
+++ b/test/ganeti.hypervisor.hv_chroot_unittest.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2011 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 testing ganeti.hypervisor.hv_chroot"""
+
+import unittest
+import tempfile
+import shutil
+
+from ganeti import constants
+from ganeti import objects
+from ganeti import hypervisor
+
+from ganeti.hypervisor import hv_chroot
+
+import testutils
+
+
+class TestConsole(unittest.TestCase):
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def test(self):
+    instance = objects.Instance(name="fake.example.com", primary_node="node837")
+    cons = hv_chroot.ChrootManager.GetInstanceConsole(instance, {}, {},
+                                                      root_dir=self.tmpdir)
+    self.assertTrue(cons.Validate())
+    self.assertEqual(cons.kind, constants.CONS_SSH)
+    self.assertEqual(cons.host, instance.primary_node)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/ganeti.hypervisor.hv_fake_unittest.py b/test/ganeti.hypervisor.hv_fake_unittest.py
new file mode 100755
index 000000000..e0c42407b
--- /dev/null
+++ b/test/ganeti.hypervisor.hv_fake_unittest.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2011 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 testing ganeti.hypervisor.hv_fake"""
+
+import unittest
+
+from ganeti import constants
+from ganeti import objects
+from ganeti import hypervisor
+
+from ganeti.hypervisor import hv_fake
+
+import testutils
+
+
+class TestConsole(unittest.TestCase):
+  def test(self):
+    instance = objects.Instance(name="fake.example.com")
+    cons = hv_fake.FakeHypervisor.GetInstanceConsole(instance, {}, {})
+    self.assertTrue(cons.Validate())
+    self.assertEqual(cons.kind, constants.CONS_MESSAGE)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/ganeti.hypervisor.hv_kvm_unittest.py b/test/ganeti.hypervisor.hv_kvm_unittest.py
index 28a936be1..c2a964f7c 100755
--- a/test/ganeti.hypervisor.hv_kvm_unittest.py
+++ b/test/ganeti.hypervisor.hv_kvm_unittest.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2010, 2011 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
@@ -80,5 +80,49 @@ class TestWriteNetScript(unittest.TestCase):
                       inst, nic, 2)
 
 
+class TestConsole(unittest.TestCase):
+  def _Test(self, instance, hvparams):
+    cons = hv_kvm.KVMHypervisor.GetInstanceConsole(instance, hvparams, {})
+    self.assertTrue(cons.Validate())
+    return cons
+
+  def testSerial(self):
+    instance = objects.Instance(name="kvm.example.com",
+                                primary_node="node6017")
+    hvparams = {
+      constants.HV_SERIAL_CONSOLE: True,
+      constants.HV_VNC_BIND_ADDRESS: None,
+      }
+    cons = self._Test(instance, hvparams)
+    self.assertEqual(cons.kind, constants.CONS_SSH)
+    self.assertEqual(cons.host, instance.primary_node)
+    self.assertEqual(cons.command[0], constants.SOCAT_PATH)
+
+  def testVnc(self):
+    instance = objects.Instance(name="kvm.example.com",
+                                primary_node="node7235",
+                                network_port=constants.VNC_BASE_PORT + 10)
+    hvparams = {
+      constants.HV_SERIAL_CONSOLE: False,
+      constants.HV_VNC_BIND_ADDRESS: "192.0.2.1",
+      }
+    cons = self._Test(instance, hvparams)
+    self.assertEqual(cons.kind, constants.CONS_VNC)
+    self.assertEqual(cons.host, "192.0.2.1")
+    self.assertEqual(cons.port, constants.VNC_BASE_PORT + 10)
+    self.assertEqual(cons.display, 10)
+
+  def testNoConsole(self):
+    instance = objects.Instance(name="kvm.example.com",
+                                primary_node="node24325",
+                                network_port=0)
+    hvparams = {
+      constants.HV_SERIAL_CONSOLE: False,
+      constants.HV_VNC_BIND_ADDRESS: None,
+      }
+    cons = self._Test(instance, hvparams)
+    self.assertEqual(cons.kind, constants.CONS_MESSAGE)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/ganeti.hypervisor.hv_lxc_unittest.py b/test/ganeti.hypervisor.hv_lxc_unittest.py
new file mode 100755
index 000000000..9d9e441ed
--- /dev/null
+++ b/test/ganeti.hypervisor.hv_lxc_unittest.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2011 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 testing ganeti.hypervisor.hv_lxc"""
+
+import unittest
+
+from ganeti import constants
+from ganeti import objects
+from ganeti import hypervisor
+
+from ganeti.hypervisor import hv_lxc
+
+import testutils
+
+
+class TestConsole(unittest.TestCase):
+  def test(self):
+    instance = objects.Instance(name="lxc.example.com", primary_node="node199")
+    cons = hv_lxc.LXCHypervisor.GetInstanceConsole(instance, {}, {})
+    self.assertTrue(cons.Validate())
+    self.assertEqual(cons.kind, constants.CONS_SSH)
+    self.assertEqual(cons.host, instance.primary_node)
+    self.assertEqual(cons.command[-1], instance.name)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/ganeti.hypervisor.hv_xen_unittest.py b/test/ganeti.hypervisor.hv_xen_unittest.py
new file mode 100755
index 000000000..dc868396e
--- /dev/null
+++ b/test/ganeti.hypervisor.hv_xen_unittest.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2011 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 testing ganeti.hypervisor.hv_lxc"""
+
+import unittest
+
+from ganeti import constants
+from ganeti import objects
+from ganeti import hypervisor
+
+from ganeti.hypervisor import hv_xen
+
+import testutils
+
+
+class TestConsole(unittest.TestCase):
+  def test(self):
+    for cls in [hv_xen.XenPvmHypervisor, hv_xen.XenHvmHypervisor]:
+      instance = objects.Instance(name="xen.example.com",
+                                  primary_node="node24828")
+      cons = cls.GetInstanceConsole(instance, {}, {})
+      self.assertTrue(cons.Validate())
+      self.assertEqual(cons.kind, constants.CONS_SSH)
+      self.assertEqual(cons.host, instance.primary_node)
+      self.assertEqual(cons.command[-1], instance.name)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
-- 
GitLab