Add stricter checks for OpInstanceSetParams.{nics,disks}
[ganeti-local] / lib / hypervisor / hv_fake.py
index fdfbdef..d2cd9a5 100644 (file)
 
 import os
 import os.path
-import re
+import logging
 
 from ganeti import utils
 from ganeti import constants
 from ganeti import errors
+from ganeti import objects
 from ganeti.hypervisor import hv_base
 
 
@@ -40,12 +41,13 @@ class FakeHypervisor(hv_base.BaseHypervisor):
   a real virtualisation software installed.
 
   """
-  _ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
+  CAN_MIGRATE = True
+
+  _ROOT_DIR = constants.RUN_GANETI_DIR + "/fake-hypervisor"
 
   def __init__(self):
     hv_base.BaseHypervisor.__init__(self)
-    if not os.path.exists(self._ROOT_DIR):
-      os.mkdir(self._ROOT_DIR)
+    utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
 
   def ListInstances(self):
     """Get the list of running instances.
@@ -56,21 +58,20 @@ class FakeHypervisor(hv_base.BaseHypervisor):
   def GetInstanceInfo(self, instance_name):
     """Get instance properties.
 
-    Args:
-      instance_name: the instance name
+    @param instance_name: the instance name
+
+    @return: tuple of (name, id, memory, vcpus, stat, times)
 
-    Returns:
-      (name, id, memory, vcpus, stat, times)
     """
-    file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
+    file_name = self._InstanceFile(instance_name)
     if not os.path.exists(file_name):
       return None
     try:
-      fh = file(file_name, "r")
+      fh = open(file_name, "r")
       try:
         inst_id = fh.readline().strip()
-        memory = fh.readline().strip()
-        vcpus = fh.readline().strip()
+        memory = utils.TryConvert(int, fh.readline().strip())
+        vcpus = utils.TryConvert(int, fh.readline().strip())
         stat = "---b-"
         times = "0"
         return (instance_name, inst_id, memory, vcpus, stat, times)
@@ -83,21 +84,22 @@ class FakeHypervisor(hv_base.BaseHypervisor):
   def GetAllInstancesInfo(self):
     """Get properties of all instances.
 
-    Returns:
-      [(name, id, memory, vcpus, stat, times),...]
+    @return: list of tuples (name, id, memory, vcpus, stat, times)
+
     """
     data = []
     for file_name in os.listdir(self._ROOT_DIR):
       try:
-        fh = file(self._ROOT_DIR+"/"+file_name, "r")
+        fh = open(utils.PathJoin(self._ROOT_DIR, file_name), "r")
         inst_id = "-1"
-        memory = "0"
+        memory = 0
+        vcpus = 1
         stat = "-----"
         times = "-1"
         try:
           inst_id = fh.readline().strip()
-          memory = fh.readline().strip()
-          vcpus = fh.readline().strip()
+          memory = utils.TryConvert(int, fh.readline().strip())
+          vcpus = utils.TryConvert(int, fh.readline().strip())
           stat = "---b-"
           times = "0"
         finally:
@@ -107,7 +109,45 @@ class FakeHypervisor(hv_base.BaseHypervisor):
         raise errors.HypervisorError("Failed to list instances: %s" % err)
     return data
 
-  def StartInstance(self, instance, block_devices, extra_args):
+  @classmethod
+  def _InstanceFile(cls, instance_name):
+    """Compute the instance file for an instance name.
+
+    """
+    return utils.PathJoin(cls._ROOT_DIR, instance_name)
+
+  def _IsAlive(self, instance_name):
+    """Checks if an instance is alive.
+
+    """
+    file_name = self._InstanceFile(instance_name)
+    return os.path.exists(file_name)
+
+  def _MarkUp(self, instance, memory):
+    """Mark the instance as running.
+
+    This does no checks, which should be done by its callers.
+
+    """
+    file_name = self._InstanceFile(instance.name)
+    fh = file(file_name, "w")
+    try:
+      fh.write("0\n%d\n%d\n" %
+               (memory,
+                instance.beparams[constants.BE_VCPUS]))
+    finally:
+      fh.close()
+
+  def _MarkDown(self, instance_name):
+    """Mark the instance as running.
+
+    This does no checks, which should be done by its callers.
+
+    """
+    file_name = self._InstanceFile(instance_name)
+    utils.RemoveFile(file_name)
+
+  def StartInstance(self, instance, block_devices, startup_paused):
     """Start an instance.
 
     For the fake hypervisor, it just creates a file in the base dir,
@@ -115,32 +155,28 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     handle race conditions properly, since these are *FAKE* instances.
 
     """
-    file_name = self._ROOT_DIR + "/%s" % instance.name
-    if os.path.exists(file_name):
+    if self._IsAlive(instance.name):
       raise errors.HypervisorError("Failed to start instance %s: %s" %
                                    (instance.name, "already running"))
     try:
-      fh = file(file_name, "w")
-      try:
-        fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
-      finally:
-        fh.close()
+      self._MarkUp(instance, self._InstanceStartupMemory(instance))
     except IOError, err:
       raise errors.HypervisorError("Failed to start instance %s: %s" %
                                    (instance.name, err))
 
-  def StopInstance(self, instance, force=False):
+  def StopInstance(self, instance, force=False, retry=False, name=None):
     """Stop an instance.
 
     For the fake hypervisor, this just removes the file in the base
     dir, if it exist, otherwise we raise an exception.
 
     """
-    file_name = self._ROOT_DIR + "/%s" % instance.name
-    if not os.path.exists(file_name):
+    if name is None:
+      name = instance.name
+    if not self._IsAlive(name):
       raise errors.HypervisorError("Failed to stop instance %s: %s" %
-                                   (instance.name, "not running"))
-    utils.RemoveFile(file_name)
+                                   (name, "not running"))
+    self._MarkDown(name)
 
   def RebootInstance(self, instance):
     """Reboot an instance.
@@ -150,65 +186,51 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     """
     return
 
-  def GetNodeInfo(self):
-    """Return information about the node.
+  def BalloonInstanceMemory(self, instance, mem):
+    """Balloon an instance memory to a certain value.
 
-    The return value is a dict, which has to have the following items:
-      (all values in MiB)
-      - memory_total: the total memory size on the node
-      - memory_free: the available memory on the node for instances
-      - memory_dom0: the memory used by the node itself, if available
+    @type instance: L{objects.Instance}
+    @param instance: instance to be accepted
+    @type mem: int
+    @param mem: actual memory size to use for instance runtime
 
     """
-    # global ram usage from the xm info command
-    # memory                 : 3583
-    # free_memory            : 747
-    # note: in xen 3, memory has changed to total_memory
-    try:
-      fh = file("/proc/meminfo")
-      try:
-        data = fh.readlines()
-      finally:
-        fh.close()
-    except IOError, err:
-      raise errors.HypervisorError("Failed to list node info: %s" % err)
-
-    result = {}
-    sum_free = 0
-    for line in data:
-      splitfields = line.split(":", 1)
-
-      if len(splitfields) > 1:
-        key = splitfields[0].strip()
-        val = splitfields[1].strip()
-        if key == 'MemTotal':
-          result['memory_total'] = int(val.split()[0])/1024
-        elif key in ('MemFree', 'Buffers', 'Cached'):
-          sum_free += int(val.split()[0])/1024
-        elif key == 'Active':
-          result['memory_dom0'] = int(val.split()[0])/1024
-    result['memory_free'] = sum_free
-
-    cpu_total = 0
+    if not self._IsAlive(instance.name):
+      raise errors.HypervisorError("Failed to balloon memory for %s: %s" %
+                                   (instance.name, "not running"))
     try:
-      fh = open("/proc/cpuinfo")
-      try:
-        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
-                                   fh.read()))
-      finally:
-        fh.close()
+      self._MarkUp(instance, mem)
     except EnvironmentError, err:
-      raise errors.HypervisorError("Failed to list node info: %s" % err)
-    result['cpu_total'] = cpu_total
+      raise errors.HypervisorError("Failed to balloon memory for %s: %s" %
+                                   (instance.name, utils.ErrnoOrStr(err)))
 
+  def GetNodeInfo(self):
+    """Return information about the node.
+
+    This is just a wrapper over the base GetLinuxNodeInfo method.
+
+    @return: a dict with the following keys (values in MiB):
+          - memory_total: the total memory size on the node
+          - memory_free: the available memory on the node for instances
+          - memory_dom0: the memory used by the node itself, if available
+
+    """
+    result = self.GetLinuxNodeInfo()
+    # substract running instances
+    all_instances = self.GetAllInstancesInfo()
+    result["memory_free"] -= min(result["memory_free"],
+                                 sum([row[2] for row in all_instances]))
     return result
 
-  @staticmethod
-  def GetShellCommandForConsole(instance):
-    """Return a command for connecting to the console of an instance.
+  @classmethod
+  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.
@@ -219,3 +241,98 @@ class FakeHypervisor(hv_base.BaseHypervisor):
     """
     if not os.path.exists(self._ROOT_DIR):
       return "The required directory '%s' does not exist." % self._ROOT_DIR
+
+  @classmethod
+  def PowercycleNode(cls):
+    """Fake hypervisor powercycle, just a wrapper over Linux powercycle.
+
+    """
+    cls.LinuxPowercycle()
+
+  def AcceptInstance(self, instance, info, target):
+    """Prepare to accept an instance.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance to be accepted
+    @type info: string
+    @param info: instance info, not used
+    @type target: string
+    @param target: target host (usually ip), on this node
+
+    """
+    if self._IsAlive(instance.name):
+      raise errors.HypervisorError("Can't accept instance, already running")
+
+  def MigrateInstance(self, instance, target, live):
+    """Migrate an instance.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance to be migrated
+    @type target: string
+    @param target: hostname (usually ip) of the target node
+    @type live: boolean
+    @param live: whether to do a live or non-live migration
+
+    """
+    logging.debug("Fake hypervisor migrating %s to %s (live=%s)",
+                  instance, target, live)
+
+  def FinalizeMigrationDst(self, instance, info, success):
+    """Finalize the instance migration on the target node.
+
+    For the fake hv, this just marks the instance up.
+
+    @type instance: L{objects.Instance}
+    @param instance: instance whose migration is being finalized
+    @type info: string/data (opaque)
+    @param info: migration information, from the source node
+    @type success: boolean
+    @param success: whether the migration was a success or a failure
+
+    """
+    if success:
+      self._MarkUp(instance, self._InstanceStartupMemory(instance))
+    else:
+      # ensure it's down
+      self._MarkDown(instance.name)
+
+  def PostMigrationCleanup(self, instance):
+    """Clean-up after a migration.
+
+    To be executed on the source node.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance that was migrated
+
+    """
+    pass
+
+  def FinalizeMigrationSource(self, instance, success, live):
+    """Finalize the instance migration on the source node.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance that was migrated
+    @type success: bool
+    @param success: whether the migration succeeded or not
+    @type live: bool
+    @param live: whether the user requested a live migration or not
+
+    """
+    # pylint: disable=W0613
+    if success:
+      self._MarkDown(instance.name)
+
+  def GetMigrationStatus(self, instance):
+    """Get the migration status
+
+    The fake hypervisor migration always succeeds.
+
+    @type instance: L{objects.Instance}
+    @param instance: the instance that is being migrated
+    @rtype: L{objects.MigrationStatus}
+    @return: the status of the current migration (one of
+             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
+             progress info that can be retrieved from the hypervisor
+
+    """
+    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)