qa_utils: Factorize code for getting entity name
[ganeti-local] / lib / backend.py
index 4162bb4..51a06bb 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
@@ -79,7 +79,7 @@ _IES_PID_FILE = "pid"
 _IES_CA_FILE = "ca"
 
 #: Valid LVS output line regex
-_LVSLINE_REGEX = re.compile("^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6})\|?$")
+_LVSLINE_REGEX = re.compile("^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
 
 # Actions for the master setup script
 _MASTER_START = "start"
@@ -522,44 +522,71 @@ def LeaveCluster(modify_ssh_setup):
   raise errors.QuitGanetiException(True, "Shutdown scheduled")
 
 
-def GetNodeInfo(vgname, hypervisor_type):
-  """Gives back a hash with different information about the node.
+def _GetVgInfo(name):
+  """Retrieves information about a LVM volume group.
 
-  @type vgname: C{string}
-  @param vgname: the name of the volume group to ask for disk space information
-  @type hypervisor_type: C{str}
-  @param hypervisor_type: the name of the hypervisor to ask for
-      memory information
-  @rtype: C{dict}
-  @return: dictionary with the following keys:
-      - vg_size is the size of the configured volume group in MiB
-      - vg_free is the free size of the volume group in MiB
-      - memory_dom0 is the memory allocated for domain0 in MiB
-      - memory_free is the currently available (free) ram in MiB
-      - memory_total is the total number of ram in MiB
-      - hv_version: the hypervisor version, if available
+  """
+  # TODO: GetVGInfo supports returning information for multiple VGs at once
+  vginfo = bdev.LogicalVolume.GetVGInfo([name])
+  if vginfo:
+    vg_free = int(round(vginfo[0][0], 0))
+    vg_size = int(round(vginfo[0][1], 0))
+  else:
+    vg_free = None
+    vg_size = None
+
+  return {
+    "name": name,
+    "vg_free": vg_free,
+    "vg_size": vg_size,
+    }
+
+
+def _GetHvInfo(name):
+  """Retrieves node information from a hypervisor.
+
+  The information returned depends on the hypervisor. Common items:
+
+    - vg_size is the size of the configured volume group in MiB
+    - vg_free is the free size of the volume group in MiB
+    - memory_dom0 is the memory allocated for domain0 in MiB
+    - memory_free is the currently available (free) ram in MiB
+    - memory_total is the total number of ram in MiB
+    - hv_version: the hypervisor version, if available
 
   """
-  outputarray = {}
+  return hypervisor.GetHypervisor(name).GetNodeInfo()
 
-  if vgname is not None:
-    vginfo = bdev.LogicalVolume.GetVGInfo([vgname])
-    vg_free = vg_size = None
-    if vginfo:
-      vg_free = int(round(vginfo[0][0], 0))
-      vg_size = int(round(vginfo[0][1], 0))
-    outputarray["vg_size"] = vg_size
-    outputarray["vg_free"] = vg_free
 
-  if hypervisor_type is not None:
-    hyper = hypervisor.GetHypervisor(hypervisor_type)
-    hyp_info = hyper.GetNodeInfo()
-    if hyp_info is not None:
-      outputarray.update(hyp_info)
+def _GetNamedNodeInfo(names, fn):
+  """Calls C{fn} for all names in C{names} and returns a dictionary.
 
-  outputarray["bootid"] = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
+  @rtype: None or dict
+
+  """
+  if names is None:
+    return None
+  else:
+    return map(fn, names)
+
+
+def GetNodeInfo(vg_names, hv_names):
+  """Gives back a hash with different information about the node.
+
+  @type vg_names: list of string
+  @param vg_names: Names of the volume groups to ask for disk space information
+  @type hv_names: list of string
+  @param hv_names: Names of the hypervisors to ask for node information
+  @rtype: tuple; (string, None/dict, None/dict)
+  @return: Tuple containing boot ID, volume group information and hypervisor
+    information
+
+  """
+  bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
+  vg_info = _GetNamedNodeInfo(vg_names, _GetVgInfo)
+  hv_info = _GetNamedNodeInfo(hv_names, _GetHvInfo)
 
-  return outputarray
+  return (bootid, vg_info, hv_info)
 
 
 def VerifyNode(what, cluster_name):
@@ -1349,6 +1376,27 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout):
     _Fail("Invalid reboot_type received: %s", reboot_type)
 
 
+def InstanceBalloonMemory(instance, memory):
+  """Resize an instance's memory.
+
+  @type instance: L{objects.Instance}
+  @param instance: the instance object
+  @type memory: int
+  @param memory: new memory amount in MB
+  @rtype: None
+
+  """
+  hyper = hypervisor.GetHypervisor(instance.hypervisor)
+  running = hyper.ListInstances()
+  if instance.name not in running:
+    logging.info("Instance %s is not running, cannot balloon", instance.name)
+    return
+  try:
+    hyper.BalloonInstanceMemory(instance, memory)
+  except errors.HypervisorError, err:
+    _Fail("Failed to balloon instance memory: %s", err, exc=True)
+
+
 def MigrationInfo(instance):
   """Gather information about an instance to be migrated.
 
@@ -1513,7 +1561,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
       clist.append(crdev)
 
   try:
-    device = bdev.Create(disk.dev_type, disk.physical_id, clist, disk.size)
+    device = bdev.Create(disk, clist)
   except errors.BlockDeviceError, err:
     _Fail("Can't create block device: %s", err)
 
@@ -1695,7 +1743,7 @@ def _RecursiveAssembleBD(disk, owner, as_primary):
       children.append(cdev)
 
   if as_primary or disk.AssembleOnSecondary():
-    r_dev = bdev.Assemble(disk.dev_type, disk.physical_id, children, disk.size)
+    r_dev = bdev.Assemble(disk, children)
     result = r_dev
     if as_primary or disk.OpenOnSecondary():
       r_dev.Open()
@@ -1888,7 +1936,7 @@ def _RecursiveFindBD(disk):
     for chdisk in disk.children:
       children.append(_RecursiveFindBD(chdisk))
 
-  return bdev.FindDevice(disk.dev_type, disk.physical_id, children, disk.size)
+  return bdev.FindDevice(disk, children)
 
 
 def _OpenRealBD(disk):
@@ -2074,24 +2122,6 @@ def WriteSsconfFiles(values):
   ssconf.SimpleStore().WriteFiles(values)
 
 
-def _ErrnoOrStr(err):
-  """Format an EnvironmentError exception.
-
-  If the L{err} argument has an errno attribute, it will be looked up
-  and converted into a textual C{E...} description. Otherwise the
-  string representation of the error will be returned.
-
-  @type err: L{EnvironmentError}
-  @param err: the exception to format
-
-  """
-  if hasattr(err, "errno"):
-    detail = errno.errorcode[err.errno]
-  else:
-    detail = str(err)
-  return detail
-
-
 def _OSOndiskAPIVersion(os_dir):
   """Compute and return the API version of a given OS.
 
@@ -2111,7 +2141,7 @@ def _OSOndiskAPIVersion(os_dir):
     st = os.stat(api_file)
   except EnvironmentError, err:
     return False, ("Required file '%s' not found under path %s: %s" %
-                   (constants.OS_API_FILE, os_dir, _ErrnoOrStr(err)))
+                   (constants.OS_API_FILE, os_dir, utils.ErrnoOrStr(err)))
 
   if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
     return False, ("File '%s' in %s is not a regular file" %
@@ -2121,7 +2151,7 @@ def _OSOndiskAPIVersion(os_dir):
     api_versions = utils.ReadFile(api_file).splitlines()
   except EnvironmentError, err:
     return False, ("Error while reading the API version file at %s: %s" %
-                   (api_file, _ErrnoOrStr(err)))
+                   (api_file, utils.ErrnoOrStr(err)))
 
   try:
     api_versions = [int(version.strip()) for version in api_versions]
@@ -2234,7 +2264,7 @@ def _TryOSFromDisk(name, base_dir=None):
         del os_files[filename]
         continue
       return False, ("File '%s' under path '%s' is missing (%s)" %
-                     (filename, os_dir, _ErrnoOrStr(err)))
+                     (filename, os_dir, utils.ErrnoOrStr(err)))
 
     if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
       return False, ("File '%s' under path '%s' is not a regular file" %
@@ -2254,7 +2284,7 @@ def _TryOSFromDisk(name, base_dir=None):
       # we accept missing files, but not other errors
       if err.errno != errno.ENOENT:
         return False, ("Error while reading the OS variants file at %s: %s" %
-                       (variants_file, _ErrnoOrStr(err)))
+                       (variants_file, utils.ErrnoOrStr(err)))
 
   parameters = []
   if constants.OS_PARAMETERS_FILE in os_files:
@@ -2263,7 +2293,7 @@ def _TryOSFromDisk(name, base_dir=None):
       parameters = utils.ReadFile(parameters_file).splitlines()
     except EnvironmentError, err:
       return False, ("Error while reading the OS parameters file at %s: %s" %
-                     (parameters_file, _ErrnoOrStr(err)))
+                     (parameters_file, utils.ErrnoOrStr(err)))
     parameters = [v.split(None, 1) for v in parameters]
 
   os_obj = objects.OS(name=name, path=os_dir,
@@ -2343,6 +2373,11 @@ def OSCoreEnv(os_name, inst_os, os_params, debug=0):
   for pname, pvalue in os_params.items():
     result["OSP_%s" % pname.upper()] = pvalue
 
+  # Set a default path otherwise programs called by OS scripts (or
+  # even hooks called from OS scripts) might break, and we don't want
+  # to have each script require setting a PATH variable
+  result["PATH"] = constants.HOOKS_PATH
+
   return result