commit e7a77eb81962b2b12d6d0f77e8cf8b4f98ebce49
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Thu Nov 21 14:27:10 2013 +0200

    (2.10) Introduce --hotplug-if-possible option
    
    This will be useful for an external entity using RAPI that
    wants to modify devices of instances.
    
    The common use case for that is:
    "I want to add a NIC/disk to an instance. If it is running
    then try to hotplug the device. If not, then just add it to config."
    
    Another use case is that if we are unaware of the instance's
    hypervisor: "I want do add a NIC/disk to an instance and if
    its hypervisor is KVM try to hotplug it."
    
    For all those cases just print a warning in CheckPrereq() and
    disable any further hotplug related actions (and let
    modifications take place after reboot).
    
    Add NoHoplugSupport and HotplugIfPossible unittests an modify
    existing by explicitly creating successful RPC results for
    call_hotplug_supported.
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>
    Signed-off-by: Thomas Thrainer <thomasth@google.com>
    Reviewed-by: Thomas Thrainer <thomasth@google.com>
    
    Add pylint disable in CheckPrereq() of LUInstanceSetParams
    (too many local variables)
    
    Conflicts:
    	lib/cmdlib/instance.py
    	src/Ganeti/OpCodes.hs
    	src/Ganeti/OpParams.hs
    	test/py/cmdlib/instance_unittest.py
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

diff --git a/lib/cli.py b/lib/cli.py
index 3006a71..72373a6 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -96,6 +96,7 @@ __all__ = [
   "HID_OS_OPT",
   "GLOBAL_SHARED_FILEDIR_OPT",
   "HOTPLUG_OPT",
+  "HOTPLUG_IF_POSSIBLE_OPT",
   "HVLIST_OPT",
   "HVOPTS_OPT",
   "HYPERVISOR_OPT",
@@ -1643,6 +1644,12 @@ HOTPLUG_OPT = cli_option("--hotplug", dest="hotplug",
                          action="store_true", default=False,
                          help="Hotplug supported devices (NICs and Disks)")
 
+HOTPLUG_IF_POSSIBLE_OPT = cli_option("--hotplug-if-possible",
+                                     dest="hotplug_if_possible",
+                                     action="store_true", default=False,
+                                     help="Hotplug devices in case"
+                                          " hotplug is supported")
+
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
 
diff --git a/lib/client/gnt_instance.py b/lib/client/gnt_instance.py
index 5159fd9..5a08ee0 100644
--- a/lib/client/gnt_instance.py
+++ b/lib/client/gnt_instance.py
@@ -1341,6 +1341,7 @@ def SetInstanceParams(opts, args):
                                    nics=nics,
                                    disks=disks,
                                    hotplug=opts.hotplug,
+                                   hotplug_if_possible=opts.hotplug_if_possible,
                                    disk_template=opts.disk_template,
                                    remote_node=opts.node,
                                    pnode=opts.new_primary_node,
@@ -1544,7 +1545,8 @@ commands = {
      DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
      ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
-     NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT, HOTPLUG_OPT],
+     NOCONFLICTSCHECK_OPT, NEW_PRIMARY_OPT, HOTPLUG_OPT,
+     HOTPLUG_IF_POSSIBLE_OPT],
     "<instance>", "Alters the parameters of an instance"),
   "shutdown": (
     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
diff --git a/lib/cmdlib/instance.py b/lib/cmdlib/instance.py
index ac88bf6..74098c4 100644
--- a/lib/cmdlib/instance.py
+++ b/lib/cmdlib/instance.py
@@ -2675,6 +2675,8 @@ class LUInstanceSetParams(LogicalUnit):
                                            self.op.disk_template))
         raise errors.OpPrereqError(errmsg, errors.ECODE_STATE)
 
+  # too many local variables
+  # pylint: disable=R0914
   def CheckPrereq(self):
     """Check prerequisites.
 
@@ -2756,10 +2758,19 @@ class LUInstanceSetParams(LogicalUnit):
                                       constants.DT_EXT),
                                      errors.ECODE_INVAL)
 
-    if self.op.hotplug:
+    if self.op.hotplug or self.op.hotplug_if_possible:
       result = self.rpc.call_hotplug_supported(self.instance.primary_node,
                                                self.instance)
-      result.Raise("Hotplug is not supported.")
+      if result.fail_msg:
+        if self.op.hotplug:
+          result.Raise("Hotplug is not possible: %s" % result.fail_msg,
+                       prereq=True)
+        else:
+          self.LogWarning(result.fail_msg)
+          self.op.hotplug = False
+          self.LogInfo("Modification will take place without hotplugging.")
+      else:
+        self.op.hotplug = True
 
     # OS change
     if self.op.os_name and not self.op.force:
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index 17aa536..541d7fa 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -2002,11 +2002,16 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     @raise errors.HypervisorError: in one of the previous cases
 
     """
-    output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
+    try:
+      output = self._CallMonitorCommand(instance.name, self._INFO_VERSION_CMD)
+    except errors.HypervisorError:
+      raise errors.HotplugError("Instance is probably down")
+
     # 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.")
+      raise errors.HotplugError("Cannot parse qemu version via monitor")
+
     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")
diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst
index 4d61d35..2e96cab 100644
--- a/man/gnt-instance.rst
+++ b/man/gnt-instance.rst
@@ -1112,6 +1112,7 @@ MODIFY
 | [\--submit]
 | [\--ignore-ipolicy]
 | [\--hotplug]
+| [\--hotplug-if-possible]
 | {*instance*}
 
 Modifies the memory size, number of vcpus, ip address, MAC address
@@ -1197,6 +1198,11 @@ userspace access mode can not be hotplugged (yet) d) if hotplug fails
 for existing NIC modification interactive verification is needed unless
 ``--force`` option is passed.
 
+If ``--hotplug-if-possible`` is given then ganeti won't abort in case
+hotplug is not supported. It will continue execution and modification
+will take place after reboot. This covers use cases where instances are
+not running or hypervisor is not KVM.
+
 See **ganeti**\(7) for a description of ``--submit`` and other common
 options.
 
diff --git a/src/Ganeti/OpCodes.hs b/src/Ganeti/OpCodes.hs
index 5fa0932..ad3bdf9 100644
--- a/src/Ganeti/OpCodes.hs
+++ b/src/Ganeti/OpCodes.hs
@@ -398,6 +398,7 @@ $(genOpCode "OpCode"
      , pOffline
      , pIpConflictsCheck
      , pHotplug
+     , pHotplugIfPossible
      ])
   , ("OpInstanceGrowDisk",
      [ pInstanceName
diff --git a/src/Ganeti/OpParams.hs b/src/Ganeti/OpParams.hs
index 324422f..278fb8f 100644
--- a/src/Ganeti/OpParams.hs
+++ b/src/Ganeti/OpParams.hs
@@ -95,6 +95,7 @@ module Ganeti.OpParams
   , pDiskState
   , pIgnoreIpolicy
   , pHotplug
+  , pHotplugIfPossible
   , pAllowRuntimeChgs
   , pInstDisks
   , pDiskTemplate
@@ -713,6 +714,9 @@ pDiskParams = optionalField $
 pHotplug :: Field
 pHotplug = defaultFalse "hotplug"
 
+pHotplugIfPossible :: Field
+pHotplugIfPossible = defaultFalse "hotplug_if_possible"
+
 -- * Parameters for node resource model
 
 -- | Set hypervisor states.
diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs
index 6e56b53..05c8132 100644
--- a/test/hs/Test/Ganeti/OpCodes.hs
+++ b/test/hs/Test/Ganeti/OpCodes.hs
@@ -265,7 +265,7 @@ instance Arbitrary OpCodes.OpCode where
           pure emptyJSObject <*> arbitrary <*> pure emptyJSObject <*>
           arbitrary <*> genMaybe genNodeNameNE <*> genMaybe genNodeNameNE <*>
           genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
-          arbitrary <*> arbitrary <*> arbitrary
+          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
       "OP_INSTANCE_GROW_DISK" ->
         OpCodes.OpInstanceGrowDisk <$> genFQDN <*> arbitrary <*>
           arbitrary <*> arbitrary <*> arbitrary
