commit 24711492cd6b52fd4c90d1c282ec7395d47307f0
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Wed Oct 30 18:56:01 2013 +0200

    Check if hotplug is supported in CheckPrereq
    
    Introduce new RPC hotplug_supported that invokes the corresponding
    hypervisor's method which checks if hotplug is generally
    supported. Call this RPC early in CheckPrereq() and abort if
    hotplug is not supported. Currently only KVM hypervisor with
    qemu versions > 1.0. Please note that this check is unaware of
    target device and hotplug action. This latter check is still
    done during hotplug_device() RPC.
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>
    Signed-off-by: Klaus Aehlig <aehlig@google.com>
    Reviewed-by: Klaus Aehlig <aehlig@google.com>

diff --git a/lib/backend.py b/lib/backend.py
index a8e1f01..51bd080 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -2003,6 +2003,17 @@ def HotplugDevice(instance, action, dev_type, device, extra, seq):
   return fn(instance, dev_type, device, extra, seq)
 
 
+def HotplugSupported(instance):
+  """Checks if hotplug is generally supported.
+
+  """
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
+  try:
+    hyper.HotplugSupported(instance)
+  except errors.HotplugError, err:
+    _Fail("Hotplug is not supported: %s", err)
+
+
 def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
   """Creates a block device for an instance.
 
diff --git a/lib/cmdlib/instance.py b/lib/cmdlib/instance.py
index 023cf45..310899f 100644
--- a/lib/cmdlib/instance.py
+++ b/lib/cmdlib/instance.py
@@ -2843,6 +2843,11 @@ class LUInstanceSetParams(LogicalUnit):
     # dictionary with instance information after the modification
     ispec = {}
 
+    if self.op.hotplug:
+      result = self.rpc.call_hotplug_supported(self.instance.primary_node,
+                                               self.instance)
+      result.Raise("Hotplug is not supported.")
+
     # Prepare NIC modifications
     self.nicmod = _PrepareContainerMods(self.op.nics, _InstNicModPrivate)
 
diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index d124eeb..f3e1b8d 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -589,8 +589,8 @@ class BaseHypervisor(object):
   def VerifyHotplugSupport(self, instance, action, dev_type):
     """Verifies that hotplug is supported.
 
-    Hotplug is not supported by default. If a hypervisor wants to support
-    it it should override this method.
+    Given the target device and hotplug action checks if hotplug is
+    actually supported.
 
     @type instance: L{objects.Instance}
     @param instance: the instance object
@@ -601,4 +601,12 @@ class BaseHypervisor(object):
     @raise errors.HotplugError: if hotplugging is not supported
 
     """
+    raise errors.HotplugError("Hotplug is not supported.")
+
+  def HotplugSupported(self, instance):
+    """Checks if hotplug is supported.
+
+    By default is not. Currently only KVM hypervisor supports it.
+
+    """
     raise errors.HotplugError("Hotplug is not supported by this hypervisor")
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index d499222..57d37ce 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -2003,22 +2003,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     """Verifies that hotplug is supported.
 
     Hotplug is *not* supported in case of:
-     - qemu versions < 1.0
      - security models and chroot (disk hotplug)
      - fdsend module is missing (nic hot-add)
 
     @raise errors.HypervisorError: in one of the previous cases
 
     """
-    output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
-    # TODO: search for netdev_add, drive_add, device_add.....
-    match = self._INFO_VERSION_RE.search(output.stdout)
-    if not match:
-      raise errors.HotplugError("Try hotplug only in running instances.")
-    v_major, v_min, _, _ = match.groups()
-    if (int(v_major), int(v_min)) < (1, 0):
-      raise errors.HotplugError("Hotplug not supported for qemu versions < 1.0")
-
     if dev_type == constants.HOTPLUG_TARGET_DISK:
       hvp = instance.hvparams
       security_model = hvp[constants.HV_SECURITY_MODEL]
@@ -2035,6 +2025,25 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       raise errors.HotplugError("Cannot hot-add NIC."
                                 " fdsend python module is missing.")
 
+  def HotplugSupported(self, instance):
+    """Checks if hotplug is generally supported.
+
+    Hotplug is *not* supported in case of:
+     - qemu versions < 1.0
+     - for stopped instances
+
+    @raise errors.HypervisorError: in one of the previous cases
+
+    """
+    output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
+    # TODO: search for netdev_add, drive_add, device_add.....
+    match = self._INFO_VERSION_RE.search(output.stdout)
+    if not match:
+      raise errors.HotplugError("Try hotplug only in running instances.")
+    v_major, v_min, _, _ = match.groups()
+    if (int(v_major), int(v_min)) < (1, 0):
+      raise errors.HotplugError("Hotplug not supported for qemu versions < 1.0")
+
   def _CallHotplugCommand(self, name, cmd):
     output = self._CallMonitorCommand(name, cmd)
     # TODO: parse output and check if succeeded
diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
index bbea012..75e6535 100644
--- a/lib/rpc_defs.py
+++ b/lib/rpc_defs.py
@@ -300,6 +300,9 @@ _INSTANCE_CALLS = [
     ("extra", None, "Extra info for device (dev_path for disk)"),
     ("seq", None, "Device seq"),
     ], None, None, "Hoplug a device to a running instance"),
+  ("hotplug_supported", SINGLE, None, constants.RPC_TMO_NORMAL, [
+    ("instance", ED_INST_DICT, "Instance object"),
+    ], None, None, "Check if hotplug is supported"),
   ]
 
 _IMPEXP_CALLS = [
diff --git a/lib/server/noded.py b/lib/server/noded.py
index 9969b00..22a1f06 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -631,6 +631,14 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     return backend.HotplugDevice(instance, action, dev_type, device, extra, seq)
 
   @staticmethod
+  def perspective_hotplug_supported(params):
+    """Checks if hotplug is supported.
+
+    """
+    instance = objects.Instance.FromDict(params[0])
+    return backend.HotplugSupported(instance)
+
+  @staticmethod
   def perspective_migration_info(params):
     """Gather information about an instance to be migrated.
 
diff --git a/test/py/cmdlib/instance_unittest.py b/test/py/cmdlib/instance_unittest.py
index 57371fd..7997dd2 100644
--- a/test/py/cmdlib/instance_unittest.py
+++ b/test/py/cmdlib/instance_unittest.py
@@ -1975,6 +1975,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                          nics=[(constants.DDM_ADD, -1, {})],
                          hotplug=True)
     self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
     self.assertTrue(self.rpc.call_hotplug_device.called)
 
   def testAddNicWithIp(self):
@@ -2065,6 +2066,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                          nics=[(constants.DDM_MODIFY, 0, {})],
                          hotplug=True)
     self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
     self.assertTrue(self.rpc.call_hotplug_device.called)
 
   def testRemoveLastNic(self):
@@ -2089,6 +2091,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                          nics=[(constants.DDM_REMOVE, 0, {})],
                          hotplug=True)
     self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
     self.assertTrue(self.rpc.call_hotplug_device.called)
 
   def testSetOffline(self):
@@ -2177,6 +2180,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                                  }]],
                          hotplug=True)
     self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
     self.assertTrue(self.rpc.call_blockdev_create.called)
     self.assertTrue(self.rpc.call_blockdev_assemble.called)
     self.assertTrue(self.rpc.call_hotplug_device.called)
@@ -2190,6 +2194,7 @@ class TestLUInstanceSetParams(CmdlibTestCase):
                                  {}]],
                          hotplug=True)
     self.ExecOpCode(op)
+    self.assertTrue(self.rpc.call_hotplug_supported.called)
     self.assertTrue(self.rpc.call_hotplug_device.called)
     self.assertTrue(self.rpc.call_blockdev_shutdown.called)
     self.assertTrue(self.rpc.call_blockdev_remove.called)
