Add hypervisors ancillary files list
[ganeti-local] / lib / backend.py
index 7728bb4..57b31a9 100644 (file)
@@ -28,7 +28,7 @@
 
 """
 
-# pylint: disable-msg=E1103
+# pylint: disable=E1103
 
 # E1103: %s %r has no %r member (but some types could not be
 # inferred), because the _TryOSFromDisk returns either (True, os_obj)
@@ -203,7 +203,7 @@ def _BuildUploadFileList():
 
   for hv_name in constants.HYPER_TYPES:
     hv_class = hypervisor.GetHypervisorClass(hv_name)
-    allowed_files.update(hv_class.GetAncillaryFiles())
+    allowed_files.update(hv_class.GetAncillaryFiles()[0])
 
   return frozenset(allowed_files)
 
@@ -244,93 +244,79 @@ def GetMasterInfo():
   return (master_netdev, master_ip, master_node, primary_ip_family)
 
 
-def StartMaster(start_daemons, no_voting):
+def ActivateMasterIp():
+  """Activate the IP address of the master daemon.
+
+  """
+  # GetMasterInfo will raise an exception if not able to return data
+  master_netdev, master_ip, _, family = GetMasterInfo()
+
+  err_msg = None
+  if netutils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
+    if netutils.IPAddress.Own(master_ip):
+      # we already have the ip:
+      logging.debug("Master IP already configured, doing nothing")
+    else:
+      err_msg = "Someone else has the master ip, not activating"
+      logging.error(err_msg)
+  else:
+    ipcls = netutils.IP4Address
+    if family == netutils.IP6Address.family:
+      ipcls = netutils.IP6Address
+
+    result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add",
+                           "%s/%d" % (master_ip, ipcls.iplen),
+                           "dev", master_netdev, "label",
+                           "%s:0" % master_netdev])
+    if result.failed:
+      err_msg = "Can't activate master IP: %s" % result.output
+      logging.error(err_msg)
+
+    # we ignore the exit code of the following cmds
+    if ipcls == netutils.IP4Address:
+      utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev, "-s",
+                    master_ip, master_ip])
+    elif ipcls == netutils.IP6Address:
+      try:
+        utils.RunCmd(["ndisc6", "-q", "-r 3", master_ip, master_netdev])
+      except errors.OpExecError:
+        # TODO: Better error reporting
+        logging.warning("Can't execute ndisc6, please install if missing")
+
+  if err_msg:
+    _Fail(err_msg)
+
+
+def StartMasterDaemons(no_voting):
   """Activate local node as master node.
 
-  The function will either try activate the IP address of the master
-  (unless someone else has it) or also start the master daemons, based
-  on the start_daemons parameter.
+  The function will start the master daemons (ganeti-masterd and ganeti-rapi).
 
-  @type start_daemons: boolean
-  @param start_daemons: whether to start the master daemons
-      (ganeti-masterd and ganeti-rapi), or (if false) activate the
-      master ip
   @type no_voting: boolean
   @param no_voting: whether to start ganeti-masterd without a node vote
-      (if start_daemons is True), but still non-interactively
+      but still non-interactively
   @rtype: None
 
   """
-  # GetMasterInfo will raise an exception if not able to return data
-  master_netdev, master_ip, _, family = GetMasterInfo()
-
-  err_msgs = []
-  # either start the master and rapi daemons
-  if start_daemons:
-    if no_voting:
-      masterd_args = "--no-voting --yes-do-it"
-    else:
-      masterd_args = ""
-
-    env = {
-      "EXTRA_MASTERD_ARGS": masterd_args,
-      }
 
-    result = utils.RunCmd([constants.DAEMON_UTIL, "start-master"], env=env)
-    if result.failed:
-      msg = "Can't start Ganeti master: %s" % result.output
-      logging.error(msg)
-      err_msgs.append(msg)
-  # or activate the IP
+  if no_voting:
+    masterd_args = "--no-voting --yes-do-it"
   else:
-    if netutils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
-      if netutils.IPAddress.Own(master_ip):
-        # we already have the ip:
-        logging.debug("Master IP already configured, doing nothing")
-      else:
-        msg = "Someone else has the master ip, not activating"
-        logging.error(msg)
-        err_msgs.append(msg)
-    else:
-      ipcls = netutils.IP4Address
-      if family == netutils.IP6Address.family:
-        ipcls = netutils.IP6Address
-
-      result = utils.RunCmd(["ip", "address", "add",
-                             "%s/%d" % (master_ip, ipcls.iplen),
-                             "dev", master_netdev, "label",
-                             "%s:0" % master_netdev])
-      if result.failed:
-        msg = "Can't activate master IP: %s" % result.output
-        logging.error(msg)
-        err_msgs.append(msg)
-
-      # we ignore the exit code of the following cmds
-      if ipcls == netutils.IP4Address:
-        utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev, "-s",
-                      master_ip, master_ip])
-      elif ipcls == netutils.IP6Address:
-        try:
-          utils.RunCmd(["ndisc6", "-q", "-r 3", master_ip, master_netdev])
-        except errors.OpExecError:
-          # TODO: Better error reporting
-          logging.warning("Can't execute ndisc6, please install if missing")
+    masterd_args = ""
 
-  if err_msgs:
-    _Fail("; ".join(err_msgs))
+  env = {
+    "EXTRA_MASTERD_ARGS": masterd_args,
+    }
 
+  result = utils.RunCmd([constants.DAEMON_UTIL, "start-master"], env=env)
+  if result.failed:
+    msg = "Can't start Ganeti master: %s" % result.output
+    logging.error(msg)
+    _Fail(msg)
 
-def StopMaster(stop_daemons):
-  """Deactivate this node as master.
-
-  The function will always try to deactivate the IP address of the
-  master. It will also stop the master daemons depending on the
-  stop_daemons parameter.
 
-  @type stop_daemons: boolean
-  @param stop_daemons: whether to also stop the master daemons
-      (ganeti-masterd and ganeti-rapi)
-  @rtype: None
+def DeactivateMasterIp():
+  """Deactivate the master IP on this node.
 
   """
   # TODO: log and report back to the caller the error failures; we
@@ -343,19 +329,30 @@ def StopMaster(stop_daemons):
   if family == netutils.IP6Address.family:
     ipcls = netutils.IP6Address
 
-  result = utils.RunCmd(["ip", "address", "del",
+  result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del",
                          "%s/%d" % (master_ip, ipcls.iplen),
                          "dev", master_netdev])
   if result.failed:
     logging.error("Can't remove the master IP, error: %s", result.output)
     # but otherwise ignore the failure
 
-  if stop_daemons:
-    result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"])
-    if result.failed:
-      logging.error("Could not stop Ganeti master, command %s had exitcode %s"
-                    " and error %s",
-                    result.cmd, result.exit_code, result.output)
+
+def StopMasterDaemons():
+  """Stop the master daemons on this node.
+
+  Stop the master daemons (ganeti-masterd and ganeti-rapi) on this node.
+
+  @rtype: None
+
+  """
+  # TODO: log and report back to the caller the error failures; we
+  # need to decide in which case we fail the RPC for this
+
+  result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"])
+  if result.failed:
+    logging.error("Could not stop Ganeti master, command %s had exitcode %s"
+                  " and error %s",
+                  result.cmd, result.exit_code, result.output)
 
 
 def EtcHostsModify(mode, host, ip):
@@ -412,7 +409,7 @@ def LeaveCluster(modify_ssh_setup):
     utils.RemoveFile(constants.CONFD_HMAC_KEY)
     utils.RemoveFile(constants.RAPI_CERT_FILE)
     utils.RemoveFile(constants.NODED_CERT_FILE)
-  except: # pylint: disable-msg=W0702
+  except: # pylint: disable=W0702
     logging.exception("Error while removing cluster secrets")
 
   result = utils.RunCmd([constants.DAEMON_UTIL, "stop", constants.CONFD])
@@ -439,6 +436,7 @@ def GetNodeInfo(vgname, hypervisor_type):
       - 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 = {}
@@ -520,12 +518,25 @@ def VerifyNode(what, cluster_name):
       what[constants.NV_FILELIST])
 
   if constants.NV_NODELIST in what:
-    result[constants.NV_NODELIST] = tmp = {}
-    random.shuffle(what[constants.NV_NODELIST])
-    for node in what[constants.NV_NODELIST]:
+    (nodes, bynode) = what[constants.NV_NODELIST]
+
+    # Add nodes from other groups (different for each node)
+    try:
+      nodes.extend(bynode[my_name])
+    except KeyError:
+      pass
+
+    # Use a random order
+    random.shuffle(nodes)
+
+    # Try to contact all nodes
+    val = {}
+    for node in nodes:
       success, message = _GetSshRunner(cluster_name).VerifyNodeHostname(node)
       if not success:
-        tmp[node] = message
+        val[node] = message
+
+    result[constants.NV_NODELIST] = val
 
   if constants.NV_NODENETTEST in what:
     result[constants.NV_NODENETTEST] = tmp = {}
@@ -668,7 +679,7 @@ def GetBlockDevSizes(devices):
   blockdevs = {}
 
   for devpath in devices:
-    if os.path.commonprefix([DEV_PREFIX, devpath]) != DEV_PREFIX:
+    if not utils.IsBelowDir(DEV_PREFIX, devpath):
       continue
 
     try:
@@ -730,7 +741,7 @@ def GetVolumeList(vg_names):
       # we don't want to report such volumes as existing, since they
       # don't really hold data
       continue
-    lvs[vg_name+"/"+name] = (size, inactive, online)
+    lvs[vg_name + "/" + name] = (size, inactive, online)
 
   return lvs
 
@@ -925,7 +936,7 @@ def GetAllInstancesInfo(hypervisor_list):
   return output
 
 
-def _InstanceLogName(kind, os_name, instance):
+def _InstanceLogName(kind, os_name, instance, component):
   """Compute the OS log filename for a given instance and operation.
 
   The instance name and os name are passed in as strings since not all
@@ -937,11 +948,19 @@ def _InstanceLogName(kind, os_name, instance):
   @param os_name: the os name
   @type instance: string
   @param instance: the name of the instance being imported/added/etc.
+  @type component: string or None
+  @param component: the name of the component of the instance being
+      transferred
 
   """
   # TODO: Use tempfile.mkstemp to create unique filename
-  base = ("%s-%s-%s-%s.log" %
-          (kind, os_name, instance, utils.TimestampForFilename()))
+  if component:
+    assert "/" not in component
+    c_msg = "-%s" % component
+  else:
+    c_msg = ""
+  base = ("%s-%s-%s%s-%s.log" %
+          (kind, os_name, instance, c_msg, utils.TimestampForFilename()))
   return utils.PathJoin(constants.LOG_OS_DIR, base)
 
 
@@ -963,7 +982,7 @@ def InstanceOsAdd(instance, reinstall, debug):
   if reinstall:
     create_env["INSTANCE_REINSTALL"] = "1"
 
-  logfile = _InstanceLogName("add", instance.os, instance.name)
+  logfile = _InstanceLogName("add", instance.os, instance.name, None)
 
   result = utils.RunCmd([inst_os.create_script], env=create_env,
                         cwd=inst_os.path, output=logfile, reset_env=True)
@@ -996,7 +1015,7 @@ def RunRenameInstance(instance, old_name, debug):
   rename_env["OLD_INSTANCE_NAME"] = old_name
 
   logfile = _InstanceLogName("rename", instance.os,
-                             "%s-%s" % (old_name, instance.name))
+                             "%s-%s" % (old_name, instance.name), None)
 
   result = utils.RunCmd([inst_os.rename_script], env=rename_env,
                         cwd=inst_os.path, output=logfile, reset_env=True)
@@ -1332,7 +1351,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
 
   """
   # TODO: remove the obsolete "size" argument
-  # pylint: disable-msg=W0613
+  # pylint: disable=W0613
   clist = []
   if disk.children:
     for child in disk.children:
@@ -1344,7 +1363,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
         # we need the children open in case the device itself has to
         # be assembled
         try:
-          # pylint: disable-msg=E1103
+          # pylint: disable=E1103
           crdev.Open()
         except errors.BlockDeviceError, err:
           _Fail("Can't make child '%s' read-write: %s", child, err)
@@ -1560,7 +1579,7 @@ def BlockdevAssemble(disk, owner, as_primary, idx):
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
     if isinstance(result, bdev.BlockDev):
-      # pylint: disable-msg=E1103
+      # pylint: disable=E1103
       result = result.dev_path
       if as_primary:
         _SymlinkBlockDev(owner, result, idx)
@@ -2351,7 +2370,7 @@ def FinalizeExport(instance, snap_disks):
       config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param),
                  "%s" % nic.nicparams.get(param, None))
   # TODO: redundant: on load can read nics until it doesn't exist
-  config.set(constants.INISECT_INS, "nic_count" , "%d" % nic_total)
+  config.set(constants.INISECT_INS, "nic_count", "%d" % nic_total)
 
   disk_total = 0
   for disk_count, disk in enumerate(snap_disks):
@@ -2364,7 +2383,7 @@ def FinalizeExport(instance, snap_disks):
       config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
                  ("%d" % disk.size))
 
-  config.set(constants.INISECT_INS, "disk_count" , "%d" % disk_total)
+  config.set(constants.INISECT_INS, "disk_count", "%d" % disk_total)
 
   # New-style hypervisor/backend parameters
 
@@ -2499,8 +2518,8 @@ def _TransformFileStorageDir(fs_dir):
   fs_dir = os.path.normpath(fs_dir)
   base_fstore = cfg.GetFileStorageDir()
   base_shared = cfg.GetSharedFileStorageDir()
-  if ((os.path.commonprefix([fs_dir, base_fstore]) != base_fstore) and
-      (os.path.commonprefix([fs_dir, base_shared]) != base_shared)):
+  if not (utils.IsBelowDir(base_fstore, fs_dir) or
+          utils.IsBelowDir(base_shared, fs_dir)):
     _Fail("File storage directory '%s' is not under base file"
           " storage directory '%s' or shared storage directory '%s'",
           fs_dir, base_fstore, base_shared)
@@ -2867,12 +2886,12 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs):
     if not utils.IsNormAbsPath(filename):
       _Fail("Path '%s' is not normalized or absolute", filename)
 
-    directory = os.path.normpath(os.path.dirname(filename))
+    real_filename = os.path.realpath(filename)
+    directory = os.path.dirname(real_filename)
 
-    if (os.path.commonprefix([constants.EXPORT_DIR, directory]) !=
-        constants.EXPORT_DIR):
-      _Fail("File '%s' is not under exports directory '%s'",
-            filename, constants.EXPORT_DIR)
+    if not utils.IsBelowDir(constants.EXPORT_DIR, real_filename):
+      _Fail("File '%s' is not under exports directory '%s': %s",
+            filename, constants.EXPORT_DIR, real_filename)
 
     # Create directory
     utils.Makedirs(directory, mode=0750)
@@ -3077,7 +3096,7 @@ def StartImportExportDaemon(mode, opts, host, port, instance, component,
       # Overall timeout for establishing connection while listening
       cmd.append("--connect-timeout=%s" % opts.connect_timeout)
 
-    logfile = _InstanceLogName(prefix, instance.os, instance.name)
+    logfile = _InstanceLogName(prefix, instance.os, instance.name, component)
 
     # TODO: Once _InstanceLogName uses tempfile.mkstemp, StartDaemon has
     # support for receiving a file descriptor for output
@@ -3314,7 +3333,7 @@ def PowercycleNode(hypervisor_type):
   # ensure the child is running on ram
   try:
     utils.Mlockall()
-  except Exception: # pylint: disable-msg=W0703
+  except Exception: # pylint: disable=W0703
     pass
   time.sleep(5)
   hyper.PowercycleNode()
@@ -3339,7 +3358,7 @@ class HooksRunner(object):
       hooks_base_dir = constants.HOOKS_BASE_DIR
     # yeah, _BASE_DIR is not valid for attributes, we use it like a
     # constant
-    self._BASE_DIR = hooks_base_dir # pylint: disable-msg=C0103
+    self._BASE_DIR = hooks_base_dir # pylint: disable=C0103
 
   def RunHooks(self, hpath, phase, env):
     """Run the scripts in the hooks directory.
@@ -3370,7 +3389,6 @@ class HooksRunner(object):
     else:
       _Fail("Unknown hooks phase '%s'", phase)
 
-
     subdir = "%s-%s.d" % (hpath, suffix)
     dir_name = utils.PathJoin(self._BASE_DIR, subdir)