cfgupgrade: Check master name, clarify question
authorMichael Hanselmann <hansmi@google.com>
Wed, 5 Jan 2011 17:41:59 +0000 (18:41 +0100)
committerMichael Hanselmann <hansmi@google.com>
Thu, 6 Jan 2011 10:06:57 +0000 (11:06 +0100)
- Check hostname and abort if it doesn't match contents of
  “ssconf_master_node”, can be overridden using “--ignore-hostname”
  parameter.
- Clarify confirmation question and don't mention instances anymore.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

test/cfgupgrade_unittest.py
tools/cfgupgrade

index 129be6f..8946708 100755 (executable)
@@ -32,13 +32,17 @@ from ganeti import constants
 from ganeti import utils
 from ganeti import errors
 from ganeti import serializer
+from ganeti import netutils
 
 import testutils
 
 
-def _RunUpgrade(path, dry_run, no_verify):
+def _RunUpgrade(path, dry_run, no_verify, ignore_hostname=True):
   cmd = [sys.executable, "%s/tools/cfgupgrade" % testutils.GetSourceDir(),
          "--debug", "--force", "--path=%s" % path]
+
+  if ignore_hostname:
+    cmd.append("--ignore-hostname")
   if dry_run:
     cmd.append("--dry-run")
   if no_verify:
@@ -60,6 +64,7 @@ class TestCfgupgrade(unittest.TestCase):
     self.known_hosts_path = utils.PathJoin(self.tmpdir, "known_hosts")
     self.confd_hmac_path = utils.PathJoin(self.tmpdir, "hmac.key")
     self.cds_path = utils.PathJoin(self.tmpdir, "cluster-domain-secret")
+    self.ss_master_node_path = utils.PathJoin(self.tmpdir, "ssconf_master_node")
 
   def tearDown(self):
     shutil.rmtree(self.tmpdir)
@@ -70,12 +75,41 @@ class TestCfgupgrade(unittest.TestCase):
   def _CreateValidConfigDir(self):
     utils.WriteFile(self.noded_cert_path, data="")
     utils.WriteFile(self.known_hosts_path, data="")
+    utils.WriteFile(self.ss_master_node_path,
+                    data="node.has.another.name.example.net")
 
   def testNoConfigDir(self):
     self.assertFalse(utils.ListVisibleFiles(self.tmpdir))
     self.assertRaises(Exception, _RunUpgrade, self.tmpdir, False, True)
     self.assertRaises(Exception, _RunUpgrade, self.tmpdir, True, True)
 
+  def testWrongHostname(self):
+    self._CreateValidConfigDir()
+
+    utils.WriteFile(self.config_path, data=serializer.DumpJson({
+      "version": constants.CONFIG_VERSION,
+      "cluster": {},
+      }))
+
+    hostname = netutils.GetHostname().name
+    assert hostname != utils.ReadOneLineFile(self.ss_master_node_path)
+
+    self.assertRaises(Exception, _RunUpgrade, self.tmpdir, False, True,
+                      ignore_hostname=False)
+
+  def testCorrectHostname(self):
+    self._CreateValidConfigDir()
+
+    utils.WriteFile(self.config_path, data=serializer.DumpJson({
+      "version": constants.CONFIG_VERSION,
+      "cluster": {},
+      }))
+
+    utils.WriteFile(self.ss_master_node_path,
+                    data="%s\n" % netutils.GetHostname().name)
+
+    _RunUpgrade(self.tmpdir, False, True, ignore_hostname=False)
+
   def testInconsistentConfig(self):
     self._CreateValidConfigDir()
     # There should be no "config_version"
index 0b135ce..7840b78 100755 (executable)
@@ -39,6 +39,7 @@ from ganeti import utils
 from ganeti import cli
 from ganeti import bootstrap
 from ganeti import config
+from ganeti import netutils
 
 
 options = None
@@ -63,13 +64,31 @@ def SetupLogging():
   elif options.verbose:
     stderr_handler.setLevel(logging.INFO)
   else:
-    stderr_handler.setLevel(logging.CRITICAL)
+    stderr_handler.setLevel(logging.WARNING)
 
   root_logger = logging.getLogger("")
   root_logger.setLevel(logging.NOTSET)
   root_logger.addHandler(stderr_handler)
 
 
+def CheckHostname(path):
+  """Ensures hostname matches ssconf value.
+
+  @param path: Path to ssconf file
+
+  """
+  ssconf_master_node = utils.ReadOneLineFile(path)
+  hostname = netutils.GetHostname().name
+
+  if ssconf_master_node == hostname:
+    return True
+
+  logging.warning("Warning: ssconf says master node is '%s', but this"
+                  " machine's name is '%s'; this tool must be run on"
+                  " the master node", ssconf_master_node, hostname)
+  return False
+
+
 def main():
   """Main program.
 
@@ -87,6 +106,9 @@ def main():
   parser.add_option(cli.FORCE_OPT)
   parser.add_option(cli.DEBUG_OPT)
   parser.add_option(cli.VERBOSE_OPT)
+  parser.add_option("--ignore-hostname", dest="ignore_hostname",
+                    action="store_true", default=False,
+                    help="Don't abort if hostname doesn't match")
   parser.add_option('--path', help="Convert configuration in this"
                     " directory instead of '%s'" % constants.DATA_DIR,
                     default=constants.DATA_DIR, dest="data_dir")
@@ -104,6 +126,7 @@ def main():
   options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
   options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
   options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
+  options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
 
   SetupLogging()
 
@@ -111,9 +134,16 @@ def main():
   if args:
     raise Error("No arguments expected")
 
+  # Check master name
+  if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
+    logging.error("Aborting due to hostname mismatch")
+    sys.exit(constants.EXIT_FAILURE)
+
   if not options.force:
-    usertext = ("%s MUST be run on the master node. Is this the master"
-                " node and are ALL instances down?" % program)
+    usertext = ("Please make sure you have read the upgrade notes for"
+                " Ganeti %s (available in the UPGRADE file and included"
+                " in other documentation formats). Continue with upgrading"
+                " configuration?" % constants.RELEASE_VERSION)
     if not cli.AskUser(usertext):
       sys.exit(constants.EXIT_FAILURE)