commit c570893184d4569d4e589b6ffddf6f8e80dbf1fb
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Wed Oct 9 17:45:33 2013 +0300

    Hotplug: rpc support
    
    Introduce new RPC that eventually invoke hypervisor specific
    hotplug functions. In order to be generic it has the following
    arguments: device type, action, device, extra info, seq.
    Device type can be NIC or DISK, action can be ADD, REMOVE,
    device is the NIC or Disk object, extra info is used by Disk
    hotplug to point the device path and seq is the device index
    (from the master perspective)
    
    Add HotplugSupported() in all Hypervisors
    
    Only KVM hypervisor supports this method. The other hypervisors
    raise HypervisorError.
    
    Before trying hotplug in backend layer invoke hyper.HotplugSupported
    and abort RPC in case it is not implemented or for some other reason
    not supported (disk+chroot, nic without fdsend, etc).
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>
    Signed-off-by: Thomas Thrainer <thomasth@google.com>
    Reviewed-by: Thomas Thrainer <thomasth@google.com>

diff --git a/lib/backend.py b/lib/backend.py
index 55312c4..075ea3e 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -1966,6 +1966,43 @@ def GetMigrationStatus(instance):
     _Fail("Failed to get migration status: %s", err, exc=True)
 
 
+def HotplugDevice(instance, action, dev_type, device, extra, seq):
+  """Hotplug a device
+
+  Hotplug is currently supported only for KVM Hypervisor.
+  @type instance: L{objects.Instance}
+  @param instance: the instance to which we hotplug a device
+  @type action: string
+  @param action: the hotplug action to perform
+  @type dev_type: string
+  @param dev_type: the device type to hotplug
+  @type device: either L{objects.NIC} or L{objects.Disk}
+  @param device: the device object to hotplug
+  @type extra: string
+  @param extra: extra info used by hotplug code (e.g. disk link)
+  @type seq: int
+  @param seq: the index of the device from master perspective
+  @raise RPCFail: in case instance does not have KVM hypervisor
+
+  """
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
+  try:
+    hyper.HotplugSupported(instance, action, dev_type)
+  except (errors.HotplugError, errors.HypervisorError), err:
+    _Fail("Hotplug is not supported: %s", err)
+
+  if action == constants.HOTPLUG_ACTION_ADD:
+    fn = hyper.HotAddDevice
+  elif action == constants.HOTPLUG_ACTION_REMOVE:
+    fn = hyper.HotDelDevice
+  elif action == constants.HOTPLUG_ACTION_MODIFY:
+    fn = hyper.HotModDevice
+  else:
+    assert action in constants.HOTPLUG_ALL_ACTIONS
+
+  return fn(instance, dev_type, device, extra, seq)
+
+
 def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
   """Creates a block device for an instance.
 
diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index 54764ab..91c0ead 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -563,3 +563,28 @@ class BaseHypervisor(object):
       return "; ".join(msgs)
     else:
       return None
+
+  def HotAddDevice(self, instance, dev_type, device, extra, seq):
+    """Hot-add a device.
+
+    """
+    pass
+
+  def HotDelDevice(self, instance, dev_type, device, extra, seq):
+    """Hot-del a device.
+
+    """
+    pass
+
+  def HotModDevice(self, instance, dev_type, device, extra, seq):
+    """Hot-mod a device.
+
+    """
+    pass
+
+  def HotplugSupported(self, instance, action, dev_type):
+    """Whether hotplug is supported.
+
+    Depends on instance's hvparam, the action. and the dev_type
+    """
+    raise NotImplementedError
diff --git a/lib/hypervisor/hv_chroot.py b/lib/hypervisor/hv_chroot.py
index bfdedba..fc54b74 100644
--- a/lib/hypervisor/hv_chroot.py
+++ b/lib/hypervisor/hv_chroot.py
@@ -329,3 +329,9 @@ class ChrootManager(hv_base.BaseHypervisor):
 
     """
     raise HypervisorError("Migration not supported by the chroot hypervisor")
+
+  def HotplugSupported(self, instance, action, dev_type):
+    """Whether hotplug is supported.
+
+    """
+    raise HypervisorError("Hotplug not supported by the chroot hypervisor")
diff --git a/lib/hypervisor/hv_fake.py b/lib/hypervisor/hv_fake.py
index b5abf7f..46fd63b 100644
--- a/lib/hypervisor/hv_fake.py
+++ b/lib/hypervisor/hv_fake.py
@@ -354,3 +354,9 @@ class FakeHypervisor(hv_base.BaseHypervisor):
 
     """
     return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
+
+  def HotplugSupported(self, instance, action, dev_type):
+    """Whether hotplug is supported.
+
+    """
+    raise errors.HypervisorError("Hotplug not supported by the fake hypervisor")
diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index e3f3d1e..cea407d 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -477,3 +477,9 @@ class LXCHypervisor(hv_base.BaseHypervisor):
 
     """
     raise HypervisorError("Migration is not supported by the LXC hypervisor")
+
+  def HotplugSupported(self, instance, action, dev_type):
+    """Whether hotplug is supported.
+
+    """
+    raise HypervisorError("Hotplug not supported by the LXC hypervisor")
diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py
index 742c8be..ca48497 100644
--- a/lib/hypervisor/hv_xen.py
+++ b/lib/hypervisor/hv_xen.py
@@ -948,6 +948,12 @@ class XenHypervisor(hv_base.BaseHypervisor):
         raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
                                      % (constants.XEN_CMD_XL, result.stderr))
 
+  def HotplugSupported(self, instance, action, dev_type):
+    """Whether hotplug is supported.
+
+    """
+    raise errors.HypervisorError("Hotplug not supported by the xen hypervisor")
+
 
 class XenPvmHypervisor(XenHypervisor):
   """Xen PVM hypervisor interface"""
diff --git a/lib/rpc.py b/lib/rpc.py
index fa6498a..82ec988 100644
--- a/lib/rpc.py
+++ b/lib/rpc.py
@@ -790,6 +790,7 @@ class RpcRunner(_RpcClientBase,
       rpc_defs.ED_INST_DICT_HVP_BEP_DP: self._InstDictHvpBepDp,
       rpc_defs.ED_INST_DICT_OSP_DP: self._InstDictOspDp,
       rpc_defs.ED_NIC_DICT: self._NicDict,
+      rpc_defs.ED_DEVICE_DICT: self._DeviceDict,
 
       # Encoders annotating disk parameters
       rpc_defs.ED_DISKS_DICT_DP: self._DisksDictDP,
@@ -831,6 +832,12 @@ class RpcRunner(_RpcClientBase,
         n.netinfo = objects.Network.ToDict(nobj)
     return n.ToDict()
 
+  def _DeviceDict(self, _, device):
+    if isinstance(device, objects.NIC):
+      return self._NicDict(None, device)
+    elif isinstance(device, objects.Disk):
+      return _ObjectToDict(None, device)
+
   def _InstDict(self, node, instance, hvp=None, bep=None, osp=None):
     """Convert the given instance to a dict.
 
diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
index ab4a483..bbea012 100644
--- a/lib/rpc_defs.py
+++ b/lib/rpc_defs.py
@@ -72,7 +72,8 @@ ACCEPT_OFFLINE_NODE = object()
  ED_DISKS_DICT_DP,
  ED_MULTI_DISKS_DICT_DP,
  ED_SINGLE_DISK_DICT_DP,
- ED_NIC_DICT) = range(1, 16)
+ ED_NIC_DICT,
+ ED_DEVICE_DICT) = range(1, 17)
 
 
 def _Prepare(calls):
@@ -291,6 +292,14 @@ _INSTANCE_CALLS = [
     ("reinstall", None, None),
     ("debug", None, None),
     ], None, None, "Starts an instance"),
+  ("hotplug_device", SINGLE, None, constants.RPC_TMO_NORMAL, [
+    ("instance", ED_INST_DICT, "Instance object"),
+    ("action", None, "Hotplug Action"),
+    ("dev_type", None, "Device type"),
+    ("device", ED_DEVICE_DICT, "Device dict"),
+    ("extra", None, "Extra info for device (dev_path for disk)"),
+    ("seq", None, "Device seq"),
+    ], None, None, "Hoplug a device to a running instance"),
   ]
 
 _IMPEXP_CALLS = [
diff --git a/lib/server/noded.py b/lib/server/noded.py
index 138f062..9969b00 100644
--- a/lib/server/noded.py
+++ b/lib/server/noded.py
@@ -616,6 +616,21 @@ class NodeRequestHandler(http.server.HttpServerHandler):
     return backend.StartInstance(instance, startup_paused, trail)
 
   @staticmethod
+  def perspective_hotplug_device(params):
+    """Hotplugs device to a running instance.
+
+    """
+    (idict, action, dev_type, ddict, extra, seq) = params
+    instance = objects.Instance.FromDict(idict)
+    if dev_type == constants.HOTPLUG_TARGET_DISK:
+      device = objects.Disk.FromDict(ddict)
+    elif dev_type == constants.HOTPLUG_TARGET_NIC:
+      device = objects.NIC.FromDict(ddict)
+    else:
+      assert dev_type in constants.HOTPLUG_ALL_TARGETS
+    return backend.HotplugDevice(instance, action, dev_type, device, extra, seq)
+
+  @staticmethod
   def perspective_migration_info(params):
     """Gather information about an instance to be migrated.
 
