Do not install init script in PREFIX/bin.
[ganeti-local] / lib / backend.py
index 7b362d5..068f964 100644 (file)
@@ -79,7 +79,7 @@ def StopMaster():
   return True
 
 
-def AddNode(dsa, dsapub, rsa, rsapub, ssh, sshpub):
+def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
   """ adds the node to the cluster
       - updates the hostkey
       - adds the ssh-key
@@ -107,7 +107,7 @@ def AddNode(dsa, dsapub, rsa, rsapub, ssh, sshpub):
     os.mkdir("/root/.ssh")
 
   f = open("/root/.ssh/id_dsa", 'w')
-  f.write(ssh)
+  f.write(sshkey)
   f.close()
 
   f = open("/root/.ssh/id_dsa.pub", 'w')
@@ -120,9 +120,8 @@ def AddNode(dsa, dsapub, rsa, rsapub, ssh, sshpub):
   finally:
     f.close()
 
-  utils.RunCmd(["/etc/init.d/ssh", "restart"])
+  utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
 
-  utils.RemoveFile("/root/.ssh/known_hosts")
   return True
 
 
@@ -171,6 +170,12 @@ def GetNodeInfo(vgname):
   if hyp_info is not None:
     outputarray.update(hyp_info)
 
+  f = open("/proc/sys/kernel/random/boot_id", 'r')
+  try:
+    outputarray["bootid"] = f.read(128).rstrip("\n")
+  finally:
+    f.close()
+
   return outputarray
 
 
@@ -370,17 +375,13 @@ def AddOSToInstance(instance, os_disk, swap_disk):
 
   create_script = inst_os.create_script
 
-  for os_device in instance.disks:
-    if os_device.iv_name == os_disk:
-      break
-  else:
+  os_device = instance.FindDisk(os_disk)
+  if os_device is None:
     logger.Error("Can't find this device-visible name '%s'" % os_disk)
     return False
 
-  for swap_device in instance.disks:
-    if swap_device.iv_name == swap_disk:
-      break
-  else:
+  swap_device = instance.FindDisk(swap_disk)
+  if swap_device is None:
     logger.Error("Can't find this device-visible name '%s'" % swap_disk)
     return False
 
@@ -401,7 +402,7 @@ def AddOSToInstance(instance, os_disk, swap_disk):
   if not os.path.exists(constants.LOG_OS_DIR):
     os.mkdir(constants.LOG_OS_DIR, 0750)
 
-  command = utils.BuildShellCmd("cd %s; %s -i %s -b %s -s %s &>%s",
+  command = utils.BuildShellCmd("cd %s && %s -i %s -b %s -s %s &>%s",
                                 inst_os.path, create_script, instance.name,
                                 real_os_dev.dev_path, real_swap_dev.dev_path,
                                 logfile)
@@ -417,6 +418,64 @@ def AddOSToInstance(instance, os_disk, swap_disk):
   return True
 
 
+def RunRenameInstance(instance, old_name, os_disk, swap_disk):
+  """Run the OS rename script for an instance.
+
+  Args:
+    instance: the instance object
+    old_name: the old name of the instance
+    os_disk: the instance-visible name of the os device
+    swap_disk: the instance-visible name of the swap device
+
+  """
+  inst_os = OSFromDisk(instance.os)
+
+  script = inst_os.rename_script
+
+  os_device = instance.FindDisk(os_disk)
+  if os_device is None:
+    logger.Error("Can't find this device-visible name '%s'" % os_disk)
+    return False
+
+  swap_device = instance.FindDisk(swap_disk)
+  if swap_device is None:
+    logger.Error("Can't find this device-visible name '%s'" % swap_disk)
+    return False
+
+  real_os_dev = _RecursiveFindBD(os_device)
+  if real_os_dev is None:
+    raise errors.BlockDeviceError("Block device '%s' is not set up" %
+                                  str(os_device))
+  real_os_dev.Open()
+
+  real_swap_dev = _RecursiveFindBD(swap_device)
+  if real_swap_dev is None:
+    raise errors.BlockDeviceError("Block device '%s' is not set up" %
+                                  str(swap_device))
+  real_swap_dev.Open()
+
+  logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
+                                           old_name,
+                                           instance.name, int(time.time()))
+  if not os.path.exists(constants.LOG_OS_DIR):
+    os.mkdir(constants.LOG_OS_DIR, 0750)
+
+  command = utils.BuildShellCmd("cd %s && %s -o %s -n %s -b %s -s %s &>%s",
+                                inst_os.path, script, old_name, instance.name,
+                                real_os_dev.dev_path, real_swap_dev.dev_path,
+                                logfile)
+
+  result = utils.RunCmd(command)
+
+  if result.failed:
+    logger.Error("os create command '%s' returned error: %s"
+                 " output: %s" %
+                 (command, result.fail_reason, result.output))
+    return False
+
+  return True
+
+
 def _GetVGInfo(vg_name):
   """Get informations about the volume group.
 
@@ -790,7 +849,7 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
     return False
 
   allowed_files = [constants.CLUSTER_CONF_FILE, "/etc/hosts",
-                   "/etc/ssh/ssh_known_hosts"]
+                   constants.SSH_KNOWN_HOSTS_FILE]
   allowed_files.extend(ssconf.SimpleStore().GetFileList())
   if file_name not in allowed_files:
     logger.Error("Filename passed to UploadFile not in allowed"
@@ -813,6 +872,7 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
     utils.RemoveFile(new_name)
   return True
 
+
 def _ErrnoOrStr(err):
   """Format an EnvironmentError exception.
 
@@ -827,31 +887,49 @@ def _ErrnoOrStr(err):
     detail = str(err)
   return detail
 
+def _OSSearch(name, search_path=None):
+  """Search for OSes with the given name in the search_path.
+
+  Args:
+    name: The name of the OS to look for
+    search_path: List of dirs to search (defaults to constants.OS_SEARCH_PATH)
+
+  Returns:
+    The base_dir the OS resides in
+
+  """
+
+  if search_path is None:
+    search_path = constants.OS_SEARCH_PATH
+
+  for dir in search_path:
+    t_os_dir = os.path.sep.join([dir, name])
+    if os.path.isdir(t_os_dir):
+        return dir
 
-def _OSOndiskVersion(name, os_dir=None):
+  return None
+
+def _OSOndiskVersion(name, os_dir):
   """Compute and return the api version of a given OS.
 
   This function will try to read the api version of the os given by
-  the 'name' parameter. By default, it wil use the constants.OS_DIR
-  as top-level directory for OSes, but this can be overriden by the
-  use of the os_dir parameter. Return value will be either an
-  integer denoting the version or None in the case when this is not
-  a valid OS name.
+  the 'name' parameter and residing in the 'os_dir' directory.
+
+  Return value will be either an integer denoting the version or None in the
+  case when this is not a valid OS name.
 
   """
-  if os_dir is None:
-    os_dir = os.path.sep.join([constants.OS_DIR, name])
 
   api_file = os.path.sep.join([os_dir, "ganeti_api_version"])
 
   try:
     st = os.stat(api_file)
   except EnvironmentError, err:
-    raise errors.InvalidOS(name, "'ganeti_api_version' file not"
+    raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file not"
                            " found (%s)" % _ErrnoOrStr(err))
 
   if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
-    raise errors.InvalidOS(name, "'ganeti_api_version' file is not"
+    raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file is not"
                            " a regular file")
 
   try:
@@ -861,22 +939,24 @@ def _OSOndiskVersion(name, os_dir=None):
     finally:
       f.close()
   except EnvironmentError, err:
-    raise errors.InvalidOS(name, "error while reading the"
+    raise errors.InvalidOS(name, os_dir, "error while reading the"
                            " API version (%s)" % _ErrnoOrStr(err))
 
   api_version = api_version.strip()
   try:
     api_version = int(api_version)
   except (TypeError, ValueError), err:
-    raise errors.InvalidOS(name, "API version is not integer (%s)" % str(err))
+    raise errors.InvalidOS(name, os_dir,
+                           "API version is not integer (%s)" % str(err))
 
   return api_version
 
-def DiagnoseOS(top_dir=None):
+
+def DiagnoseOS(top_dirs=None):
   """Compute the validity for all OSes.
 
-  For each name in the give top_dir parameter (if not given, defaults
-  to constants.OS_DIR), it will return an object. If this is a valid
+  For each name in all the given top directories (if not given defaults i
+  to constants.OS_SEARCH_PATH it will return an object. If this is a valid
   os, the object will be an instance of the object.OS class. If not,
   it will be an instance of errors.InvalidOS and this signifies that
   this name does not correspond to a valid OS.
@@ -885,26 +965,28 @@ def DiagnoseOS(top_dir=None):
     list of objects
 
   """
-  if top_dir is None:
-    top_dir = constants.OS_DIR
+  if top_dirs is None:
+    top_dirs = constants.OS_SEARCH_PATH
 
-  try:
-    f_names = os.listdir(top_dir)
-  except EnvironmentError, err:
-    logger.Error("Can't list the OS directory: %s" % str(err))
-    return False
   result = []
-  for name in f_names:
-    try:
-      os_inst = OSFromDisk(name, os.path.sep.join([top_dir, name]))
-      result.append(os_inst)
-    except errors.InvalidOS, err:
-      result.append(err)
+  for dir in top_dirs:
+    if os.path.isdir(dir):
+      try:
+        f_names = utils.ListVisibleFiles(dir)
+      except EnvironmentError, err:
+        logger.Error("Can't list the OS directory %s: %s" % (dir,str(err)))
+        break
+      for name in f_names:
+        try:
+          os_inst = OSFromDisk(name, base_dir=dir)
+          result.append(os_inst)
+        except errors.InvalidOS, err:
+          result.append(err)
 
   return result
 
 
-def OSFromDisk(name, os_dir=None):
+def OSFromDisk(name, base_dir=None):
   """Create an OS instance from disk.
 
   This function will return an OS instance if the given name is a
@@ -912,18 +994,28 @@ def OSFromDisk(name, os_dir=None):
   `errors.InvalidOS` exception, detailing why this is not a valid
   OS.
 
+  Args:
+    os_dir: Directory containing the OS scripts. Defaults to a search
+            in all the OS_SEARCH_PATH directories.
+
   """
-  if os_dir is None:
-    os_dir = os.path.sep.join([constants.OS_DIR, name])
 
+  if base_dir is None:
+    base_dir = _OSSearch(name)
+
+  if base_dir is None:
+    raise errors.InvalidOS(name, None, "OS dir not found in search path")
+
+  os_dir = os.path.sep.join([base_dir, name])
   api_version = _OSOndiskVersion(name, os_dir)
 
   if api_version != constants.OS_API_VERSION:
-    raise errors.InvalidOS(name, "API version mismatch (found %s want %s)"
+    raise errors.InvalidOS(name, os_dir, "API version mismatch"
+                           " (found %s want %s)"
                            % (api_version, constants.OS_API_VERSION))
 
   # OS Scripts dictionary, we will populate it with the actual script names
-  os_scripts = {'create': '', 'export': '', 'import': ''}
+  os_scripts = {'create': '', 'export': '', 'import': '', 'rename': ''}
 
   for script in os_scripts:
     os_scripts[script] = os.path.sep.join([os_dir, script])
@@ -931,20 +1023,23 @@ def OSFromDisk(name, os_dir=None):
     try:
       st = os.stat(os_scripts[script])
     except EnvironmentError, err:
-      raise errors.InvalidOS(name, "'%s' script missing (%s)" %
+      raise errors.InvalidOS(name, os_dir, "'%s' script missing (%s)" %
                              (script, _ErrnoOrStr(err)))
 
     if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
-      raise errors.InvalidOS(name, "'%s' script not executable" % script)
+      raise errors.InvalidOS(name, os_dir, "'%s' script not executable" %
+                             script)
 
     if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
-      raise errors.InvalidOS(name, "'%s' is not a regular file" % script)
+      raise errors.InvalidOS(name, os_dir, "'%s' is not a regular file" %
+                             script)
 
 
   return objects.OS(name=name, path=os_dir,
                     create_script=os_scripts['create'],
                     export_script=os_scripts['export'],
                     import_script=os_scripts['import'],
+                    rename_script=os_scripts['rename'],
                     api_version=api_version)
 
 
@@ -1023,13 +1118,14 @@ def ExportSnapshot(disk, dest_node, instance):
 
   comprcmd = "gzip"
 
-  remotecmd = utils.BuildShellCmd("ssh -q -oStrictHostKeyChecking=yes"
-                                  " -oBatchMode=yes -oEscapeChar=none"
-                                  " %s 'mkdir -p %s; cat > %s/%s'",
-                                  dest_node, destdir, destdir, destfile)
+  destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s",
+                                destdir, destdir, destfile)
+  remotecmd = ssh.BuildSSHCmd(dest_node, 'root', destcmd)
+
+
 
   # all commands have been checked, so we're safe to combine them
-  command = '|'.join([expcmd, comprcmd, remotecmd])
+  command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
 
   result = utils.RunCmd(command)
 
@@ -1138,17 +1234,13 @@ def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
   inst_os = OSFromDisk(instance.os)
   import_script = inst_os.import_script
 
-  for os_device in instance.disks:
-    if os_device.iv_name == os_disk:
-      break
-  else:
+  os_device = instance.FindDisk(os_disk)
+  if os_device is None:
     logger.Error("Can't find this device-visible name '%s'" % os_disk)
     return False
 
-  for swap_device in instance.disks:
-    if swap_device.iv_name == swap_disk:
-      break
-  else:
+  swap_device = instance.FindDisk(swap_disk)
+  if swap_device is None:
     logger.Error("Can't find this device-visible name '%s'" % swap_disk)
     return False
 
@@ -1169,9 +1261,8 @@ def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
   if not os.path.exists(constants.LOG_OS_DIR):
     os.mkdir(constants.LOG_OS_DIR, 0750)
 
-  remotecmd = utils.BuildShellCmd("ssh -q -oStrictHostKeyChecking=yes"
-                                  " -oBatchMode=yes -oEscapeChar=none"
-                                  " %s 'cat %s'", src_node, src_image)
+  destcmd = utils.BuildShellCmd('cat %s', src_image)
+  remotecmd = ssh.BuildSSHCmd(src_node, 'root', destcmd)
 
   comprcmd = "gunzip"
   impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)",
@@ -1179,7 +1270,7 @@ def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
                                real_os_dev.dev_path, real_swap_dev.dev_path,
                                logfile)
 
-  command = '|'.join([remotecmd, comprcmd, impcmd])
+  command = '|'.join([utils.ShellQuoteArgs(remotecmd), comprcmd, impcmd])
 
   result = utils.RunCmd(command)
 
@@ -1197,7 +1288,7 @@ def ListExports():
 
   """
   if os.path.isdir(constants.EXPORT_DIR):
-    return os.listdir(constants.EXPORT_DIR)
+    return utils.ListVisibleFiles(constants.EXPORT_DIR)
   else:
     return []
 
@@ -1307,7 +1398,7 @@ class HooksRunner(object):
     subdir = "%s-%s.d" % (hpath, suffix)
     dir_name = "%s/%s" % (self._BASE_DIR, subdir)
     try:
-      dir_contents = os.listdir(dir_name)
+      dir_contents = utils.ListVisibleFiles(dir_name)
     except OSError, err:
       # must log
       return rr