Merge branch 'stable-2.12' into stable-2.13
authorKlaus Aehlig <aehlig@google.com>
Thu, 14 Jan 2016 13:07:02 +0000 (14:07 +0100)
committerKlaus Aehlig <aehlig@google.com>
Thu, 14 Jan 2016 13:34:16 +0000 (14:34 +0100)
* stable-2.12
  Increase minimal sizes of test online nodes
  Also log the high-level upgrade steps
  Add function to provide logged user feedback
  Run renew-crypto in upgrades in debug mode
  Unconditionally log upgrades at debug level
  Document healthy-majority restriction on master-failover
  Check for healthy majority on master failover with voting
  Add a predicate testing that a majority of nodes is healthy
  Fix outdated comment
  Pass arguments to correct daemons during master-failover
  Fix documentation for master-failover

* stable-2.11
  (no changes)

* stable-2.10
  KVM: explicitly configure routed NICs late

Signed-off-by: Klaus Aehlig <aehlig@google.com>
Reviewed-by: Helga Velroyen <helgav@google.com>

lib/backend.py
lib/bootstrap.py
lib/cli.py
lib/client/gnt_cluster.py
lib/hypervisor/hv_kvm/__init__.py
man/gnt-cluster.rst
test/hs/Test/Ganeti/HTools/Node.hs
tools/post-upgrade

index 646fc70..a10ddb1 100644 (file)
@@ -436,12 +436,13 @@ def StartMasterDaemons(no_voting):
   """
 
   if no_voting:
-    masterd_args = "--no-voting --yes-do-it"
+    daemon_args = "--no-voting --yes-do-it"
   else:
-    masterd_args = ""
+    daemon_args = ""
 
   env = {
-    "EXTRA_MASTERD_ARGS": masterd_args,
+    "EXTRA_LUXID_ARGS": daemon_args,
+    "EXTRA_WCONFD_ARGS": daemon_args,
     }
 
   result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-master"], env=env)
index 4b6564c..66fddb9 100644 (file)
@@ -1156,8 +1156,7 @@ def GatherMasterVotes(node_names):
   (if some nodes vote for another master).
 
   @type node_names: list
-  @param node_names: the list of nodes to query for master info; the current
-      node will be removed if it is in the list
+  @param node_names: the list of nodes to query for master info
   @rtype: list
   @return: list of (node, votes)
 
@@ -1193,3 +1192,24 @@ def GatherMasterVotes(node_names):
   vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
 
   return vote_list
+
+
+def MajorityHealthy():
+  """Check if the majority of nodes is healthy
+
+  Gather master votes from all nodes known to this node;
+  return True if a strict majority of nodes is reachable and
+  has some opinion on which node is master. Note that this will
+  not guarantee any node to win an election but it ensures that
+  a standard master-failover is still possible.
+
+  """
+  node_names = ssconf.SimpleStore().GetNodeList()
+  node_count = len(node_names)
+  vote_list = GatherMasterVotes(node_names)
+  if vote_list is None:
+    return False
+  total_votes = sum([count for (node, count) in vote_list if node is not None])
+  logging.info("Total %d nodes, %d votes: %s", node_count, total_votes,
+               vote_list)
+  return 2 * total_votes > node_count
index 3fcfd98..9c29764 100644 (file)
@@ -86,6 +86,7 @@ __all__ = [
   "UsesRPC",
   # Formatting functions
   "ToStderr", "ToStdout",
+  "ToStdoutAndLoginfo",
   "FormatError",
   "FormatQueryResult",
   "FormatParamsDictInfo",
@@ -2353,6 +2354,12 @@ def ToStdout(txt, *args):
   _ToStream(sys.stdout, txt, *args)
 
 
+def ToStdoutAndLoginfo(txt, *args):
+  """Write a message to stdout and additionally log it at INFO level"""
+  ToStdout(txt, *args)
+  logging.info(txt, *args)
+
+
 def ToStderr(txt, *args):
   """Write a message to stderr only, bypassing the logging system
 
index fa29f9f..954ab4b 100644 (file)
@@ -877,6 +877,14 @@ def MasterFailover(opts, args):
   @return: the desired exit code
 
   """
+  if not opts.no_voting:
+    # Verify that a majority of nodes is still healthy
+    if not bootstrap.MajorityHealthy():
+      ToStderr("Master-failover with voting is only possible if the majority"
+               " of nodes is still healthy; use the --no-voting option after"
+               " ensuring by other means that you won't end up in a dual-master"
+               " scenario.")
+      return 1
   if opts.no_voting and not opts.yes_do_it:
     usertext = ("This will perform the failover even if most other nodes"
                 " are down, or if this node is outdated. This is dangerous"
@@ -2119,7 +2127,7 @@ def _UpgradeBeforeConfigurationChange(versionstring):
   rollback.append(
     lambda: utils.RunCmd(["rm", "-f", pathutils.INTENT_TO_UPGRADE]))
 
-  ToStdout("Draining queue")
+  ToStdoutAndLoginfo("Draining queue")
   client = GetClient()
   client.SetQueueDrainFlag(True)
 
@@ -2131,11 +2139,11 @@ def _UpgradeBeforeConfigurationChange(versionstring):
     ToStderr("Failed to completely empty the queue.")
     return (False, rollback)
 
-  ToStdout("Pausing the watcher for one hour.")
+  ToStdoutAndLoginfo("Pausing the watcher for one hour.")
   rollback.append(lambda: GetClient().SetWatcherPause(None))
   GetClient().SetWatcherPause(time.time() + 60 * 60)
 
-  ToStdout("Stopping daemons on master node.")
+  ToStdoutAndLoginfo("Stopping daemons on master node.")
   if not _RunCommandAndReport([pathutils.DAEMON_UTIL, "stop-all"]):
     return (False, rollback)
 
@@ -2143,7 +2151,7 @@ def _UpgradeBeforeConfigurationChange(versionstring):
     utils.RunCmd([pathutils.DAEMON_UTIL, "start-all"])
     return (False, rollback)
 
-  ToStdout("Stopping daemons everywhere.")
+  ToStdoutAndLoginfo("Stopping daemons everywhere.")
   rollback.append(lambda: _VerifyCommand([pathutils.DAEMON_UTIL, "start-all"]))
   badnodes = _VerifyCommand([pathutils.DAEMON_UTIL, "stop-all"])
   if badnodes:
@@ -2151,7 +2159,7 @@ def _UpgradeBeforeConfigurationChange(versionstring):
     return (False, rollback)
 
   backuptar = os.path.join(pathutils.BACKUP_DIR, "ganeti%d.tar" % time.time())
-  ToStdout("Backing up configuration as %s" % backuptar)
+  ToStdoutAndLoginfo("Backing up configuration as %s", backuptar)
   if not _RunCommandAndReport(["mkdir", "-p", pathutils.BACKUP_DIR]):
     return (False, rollback)
 
@@ -2179,7 +2187,7 @@ def _VersionSpecificDowngrade():
 
   @return: True upon success
   """
-  ToStdout("Performing version-specific downgrade tasks.")
+  ToStdoutAndLoginfo("Performing version-specific downgrade tasks.")
 
   # Determine if this cluster is set up with SSH handling
   # (aka not using --no-ssh-init), check if the public
@@ -2236,7 +2244,7 @@ def _SwitchVersionAndConfig(versionstring, downgrade):
   """
   rollback = []
   if downgrade:
-    ToStdout("Downgrading configuration")
+    ToStdoutAndLoginfo("Downgrading configuration")
     if not _RunCommandAndReport([pathutils.CFGUPGRADE, "--downgrade", "-f"]):
       return (False, rollback)
     # Note: version specific downgrades need to be done before switching
@@ -2248,7 +2256,7 @@ def _SwitchVersionAndConfig(versionstring, downgrade):
   # Configuration change is the point of no return. From then onwards, it is
   # safer to push through the up/dowgrade than to try to roll it back.
 
-  ToStdout("Switching to version %s on all nodes" % versionstring)
+  ToStdoutAndLoginfo("Switching to version %s on all nodes", versionstring)
   rollback.append(lambda: _SetGanetiVersion(constants.DIR_VERSION))
   badnodes = _SetGanetiVersion(versionstring)
   if badnodes:
@@ -2263,7 +2271,7 @@ def _SwitchVersionAndConfig(versionstring, downgrade):
   # commands using their canonical (version independent) path.
 
   if not downgrade:
-    ToStdout("Upgrading configuration")
+    ToStdoutAndLoginfo("Upgrading configuration")
     if not _RunCommandAndReport([pathutils.CFGUPGRADE, "-f"]):
       return (False, rollback)
 
@@ -2288,24 +2296,24 @@ def _UpgradeAfterConfigurationChange(oldversion):
   """
   returnvalue = 0
 
-  ToStdout("Ensuring directories everywhere.")
+  ToStdoutAndLoginfo("Ensuring directories everywhere.")
   badnodes = _VerifyCommand([pathutils.ENSURE_DIRS])
   if badnodes:
     ToStderr("Warning: failed to ensure directories on %s." %
              (", ".join(badnodes)))
     returnvalue = 1
 
-  ToStdout("Starting daemons everywhere.")
+  ToStdoutAndLoginfo("Starting daemons everywhere.")
   badnodes = _VerifyCommand([pathutils.DAEMON_UTIL, "start-all"])
   if badnodes:
     ToStderr("Warning: failed to start daemons on %s." % (", ".join(badnodes),))
     returnvalue = 1
 
-  ToStdout("Redistributing the configuration.")
+  ToStdoutAndLoginfo("Redistributing the configuration.")
   if not _RunCommandAndReport(["gnt-cluster", "redist-conf", "--yes-do-it"]):
     returnvalue = 1
 
-  ToStdout("Restarting daemons everywhere.")
+  ToStdoutAndLoginfo("Restarting daemons everywhere.")
   badnodes = _VerifyCommand([pathutils.DAEMON_UTIL, "stop-all"])
   badnodes.extend(_VerifyCommand([pathutils.DAEMON_UTIL, "start-all"]))
   if badnodes:
@@ -2313,21 +2321,21 @@ def _UpgradeAfterConfigurationChange(oldversion):
              (", ".join(list(set(badnodes))),))
     returnvalue = 1
 
-  ToStdout("Undraining the queue.")
+  ToStdoutAndLoginfo("Undraining the queue.")
   if not _RunCommandAndReport(["gnt-cluster", "queue", "undrain"]):
     returnvalue = 1
 
   _RunCommandAndReport(["rm", "-f", pathutils.INTENT_TO_UPGRADE])
 
-  ToStdout("Running post-upgrade hooks")
+  ToStdoutAndLoginfo("Running post-upgrade hooks")
   if not _RunCommandAndReport([pathutils.POST_UPGRADE, oldversion]):
     returnvalue = 1
 
-  ToStdout("Unpausing the watcher.")
+  ToStdoutAndLoginfo("Unpausing the watcher.")
   if not _RunCommandAndReport(["gnt-cluster", "watcher", "continue"]):
     returnvalue = 1
 
-  ToStdout("Verifying cluster.")
+  ToStdoutAndLoginfo("Verifying cluster.")
   if not _RunCommandAndReport(["gnt-cluster", "verify"]):
     returnvalue = 1
 
@@ -2366,6 +2374,8 @@ def UpgradeGanetiCommand(opts, args):
                  " finish it first" % (oldversion, versionstring))
         return 1
 
+  utils.SetupLogging(pathutils.LOG_COMMANDS, 'gnt-cluster upgrade', debug=1)
+
   oldversion = constants.RELEASE_VERSION
 
   if opts.resume:
index 53c473d..4b9406a 100644 (file)
@@ -1664,11 +1664,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       kvm_cmd.extend(["-qmp", "unix:%s,server,nowait" %
                       self._InstanceKvmdMonitor(instance.name)])
 
-    # Configure the network now for starting instances and bridged interfaces,
-    # during FinalizeMigration for incoming instances' routed interfaces
+    # Configure the network now for starting instances and bridged/OVS
+    # interfaces, during FinalizeMigration for incoming instances' routed
+    # interfaces.
     for nic_seq, nic in enumerate(kvm_nics):
       if (incoming and
-          nic.nicparams[constants.NIC_MODE] != constants.NIC_MODE_BRIDGED):
+          nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_ROUTED):
         continue
       self._ConfigureNIC(instance, nic_seq, nic, taps[nic_seq])
 
@@ -2133,8 +2134,8 @@ class KVMHypervisor(hv_base.BaseHypervisor):
       kvm_nics = kvm_runtime[1]
 
       for nic_seq, nic in enumerate(kvm_nics):
-        if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
-          # Bridged interfaces have already been configured
+        if nic.nicparams[constants.NIC_MODE] != constants.NIC_MODE_ROUTED:
+          # Bridged/OVS interfaces have already been configured
           continue
         try:
           tap = utils.ReadFile(self._InstanceNICFile(instance.name, nic_seq))
index a04d50c..f34677a 100644 (file)
@@ -645,15 +645,22 @@ over the master role in a 2 node cluster with the original master
 down). If the original master then comes up, it won't be able to
 start its master daemon because it won't have enough votes, but so
 won't the new master, if the master daemon ever needs a restart.
-You can pass ``--no-voting`` to **ganeti-masterd** on the new
-master to solve this problem, and run **gnt-cluster redist-conf**
-to make sure the cluster is consistent again.
+You can pass ``--no-voting`` to **ganeti-luxid** and **ganeti-wconfd**
+on the new master to solve this problem, and run
+**gnt-cluster redist-conf** to make sure the cluster is consistent
+again.
 
 The option ``--yes-do-it`` is used together with ``--no-voting``, for
 skipping the interactive checks. This is even more dangerous, and should
 only be used in conjunction with other means (e.g. a HA suite) to
 confirm that the operation is indeed safe.
 
+Note that in order for remote node agreement checks to work, a strict
+majority of nodes still needs to be functional. To avoid situations with
+daemons not starting up on the new master, master-failover without
+the ``--no-voting`` option verifies a healthy majority of nodes and refuses
+the operation otherwise.
+
 MASTER-PING
 ~~~~~~~~~~~
 
index 9177ba7..d782fe2 100644 (file)
@@ -113,10 +113,10 @@ genOnlineNode :: Gen Node.Node
 genOnlineNode =
   arbitrary `suchThat` (\n -> not (Node.offline n) &&
                               not (Node.failN1 n) &&
-                              Node.availDisk n > 0 &&
-                              Node.availMem n > 0 &&
-                              Node.availCpu n > 0 &&
-                              Node.tSpindles n > 0)
+                              Node.availDisk n > 2 * Types.unitDsk &&
+                              Node.availMem n > 2 * Types.unitMem &&
+                              Node.availCpu n > 2 &&
+                              Node.tSpindles n > 2)
 
 -- | Generate a node with exclusive storage enabled.
 genExclStorNode :: Gen Node.Node
index 530124b..f611974 100644 (file)
@@ -55,7 +55,7 @@ def main():
 
   if utils.version.IsBefore(version, 2, 12, 5):
     result = utils.RunCmd(["gnt-cluster", "renew-crypto",
-                           "--new-node-certificates", "-f"])
+                           "--new-node-certificates", "-f", "-d"])
     if result.failed:
       cli.ToStderr("Failed to create node certificates: %s; Output %s" %
                    (result.fail_reason, result.output))