Pass debug mode to noded for OS-related calls
[ganeti-local] / tools / cfgupgrade
index 9825cf6..e7be591 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
 #!/usr/bin/python
 #
 
-# Copyright (C) 2007, 2008 Google Inc.
+# Copyright (C) 2007, 2008, 2009 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
 #
 # 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
@@ -21,8 +21,8 @@
 
 """Tool to upgrade the configuration file.
 
 
 """Tool to upgrade the configuration file.
 
-This code handles only the types supported by simplejson. As an example, "set"
-is a "list".
+This code handles only the types supported by simplejson. As an
+example, 'set' is a 'list'.
 
 """
 
 
 """
 
@@ -31,29 +31,41 @@ import os
 import os.path
 import sys
 import optparse
 import os.path
 import sys
 import optparse
-import tempfile
 import logging
 import logging
-import errno
 
 from ganeti import constants
 from ganeti import serializer
 from ganeti import utils
 from ganeti import cli
 
 from ganeti import constants
 from ganeti import serializer
 from ganeti import utils
 from ganeti import cli
+from ganeti import bootstrap
+from ganeti import config
 
 
 
 
-# We need to keep filenames locally because they might be renamed between
-# versions.
-CONFIG_DATA_PATH = constants.DATA_DIR + "/config.data"
-SERVER_PEM_PATH = constants.DATA_DIR + "/server.pem"
-KNOWN_HOSTS_PATH = constants.DATA_DIR + "/known_hosts"
-SSCONF_CLUSTER_NAME_PATH = constants.DATA_DIR + "/ssconf_cluster_name"
-SSCONF_CONFIG_VERSION_PATH = constants.DATA_DIR + "/ssconf_config_version"
-
 options = None
 args = None
 
 options = None
 args = None
 
-# Unique object to identify calls without default value
-NoDefault = object()
+# Dictionary with instance old keys, and new hypervisor keys
+INST_HV_CHG = {
+  'hvm_pae': constants.HV_PAE,
+  'vnc_bind_address': constants.HV_VNC_BIND_ADDRESS,
+  'initrd_path': constants.HV_INITRD_PATH,
+  'hvm_nic_type': constants.HV_NIC_TYPE,
+  'kernel_path': constants.HV_KERNEL_PATH,
+  'hvm_acpi': constants.HV_ACPI,
+  'hvm_cdrom_image_path': constants.HV_CDROM_IMAGE_PATH,
+  'hvm_boot_order': constants.HV_BOOT_ORDER,
+  'hvm_disk_type': constants.HV_DISK_TYPE,
+  }
+
+# Instance beparams changes
+INST_BE_CHG = {
+  'vcpus': constants.BE_VCPUS,
+  'memory': constants.BE_MEMORY,
+  'auto_balance': constants.BE_AUTO_BALANCE,
+  }
+
+# Field names
+F_SERIAL = 'serial_no'
 
 
 class Error(Exception):
 
 
 class Error(Exception):
@@ -61,33 +73,6 @@ class Error(Exception):
   pass
 
 
   pass
 
 
-def ReadFile(file_name, default=NoDefault):
-  """Reads a file.
-
-  """
-  logging.debug("Reading %s", file_name)
-  try:
-    fh = open(file_name, 'r')
-  except IOError, err:
-    if default is not NoDefault and err.errno == errno.ENOENT:
-      return default
-    raise
-
-  try:
-    return fh.read()
-  finally:
-    fh.close()
-
-
-def WriteFile(file_name, data):
-  """Writes a configuration file.
-
-  """
-  logging.debug("Writing %s", file_name)
-  utils.WriteFile(file_name=file_name, data=data, mode=0600,
-                  dry_run=options.dry_run, backup=True)
-
-
 def SetupLogging():
   """Configures the logging module.
 
 def SetupLogging():
   """Configures the logging module.
 
@@ -112,7 +97,7 @@ def main():
   """Main program.
 
   """
   """Main program.
 
   """
-  global options, args
+  global options, args # pylint: disable-msg=W0603
 
   program = os.path.basename(sys.argv[0])
 
 
   program = os.path.basename(sys.argv[0])
 
@@ -124,11 +109,20 @@ def main():
                          " output file")
   parser.add_option(cli.FORCE_OPT)
   parser.add_option(cli.DEBUG_OPT)
                          " output file")
   parser.add_option(cli.FORCE_OPT)
   parser.add_option(cli.DEBUG_OPT)
-  parser.add_option('--verbose', dest='verbose',
-                    action="store_true",
-                    help="Verbose output")
+  parser.add_option(cli.VERBOSE_OPT)
+  parser.add_option('--path', help="Convert configuration in this"
+                    " directory instead of '%s'" % constants.DATA_DIR,
+                    default=constants.DATA_DIR, dest="data_dir")
   (options, args) = parser.parse_args()
 
   (options, args) = parser.parse_args()
 
+  # We need to keep filenames locally because they might be renamed between
+  # versions.
+  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
+  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
+  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
+  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
+  options.HMAC_CLUSTER_KEY = options.data_dir + "/hmac.key"
+
   SetupLogging()
 
   # Option checking
   SetupLogging()
 
   # Option checking
@@ -136,79 +130,77 @@ def main():
     raise Error("No arguments expected")
 
   if not options.force:
     raise Error("No arguments expected")
 
   if not options.force:
-    usertext = ("%s MUST run on the master node. Is this the master"
-                " node?" % program)
+    usertext = ("%s MUST be run on the master node. Is this the master"
+                " node and are ALL instances down?" % program)
     if not cli.AskUser(usertext):
       sys.exit(1)
 
   # Check whether it's a Ganeti configuration directory
     if not cli.AskUser(usertext):
       sys.exit(1)
 
   # Check whether it's a Ganeti configuration directory
-  if not (os.path.isfile(CONFIG_DATA_PATH) and
-          os.path.isfile(SERVER_PEM_PATH) or
-          os.path.isfile(KNOWN_HOSTS_PATH)):
+  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
+          os.path.isfile(options.SERVER_PEM_PATH) or
+          os.path.isfile(options.KNOWN_HOSTS_PATH)):
     raise Error(("%s does not seem to be a known Ganeti configuration"
     raise Error(("%s does not seem to be a known Ganeti configuration"
-                 " directory") % constants.DATA_DIR)
-
-  config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip()
-  logging.info("Found configuration version %s", config_version)
-
-  config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH))
-
-  # Ganeti 1.2?
-  if config_version == "1.2":
-    logging.info("Found a Ganeti 1.2 configuration")
-
-    old_config_version = config_data["cluster"].get("config_version", None)
-    logging.info("Found old configuration version %s", old_config_version)
-    if old_config_version not in (3, ):
-      raise Error("Unsupported configuration version: %s" %
-                  old_config_version)
-
-    # Make sure no instance uses remote_raid1 anymore
-    remote_raid1_instances = []
-    for instance in config_data["instances"]:
-      if instance["disk_template"] == "remote_raid1":
-        remote_raid1_instances.append(instance["name"])
-    if remote_raid1_instances:
-      for name in remote_raid1_instances:
-        logging.error("Instance %s still using remote_raid1 disk template")
-      raise Error("Unable to convert configuration as long as there are"
-                  " instances using remote_raid1 disk template")
-
-    # The configuration version will be stored in a ssconf file
-    if 'config_version' in config_data['cluster']:
-      del config_data['cluster']['config_version']
-
-    # Build content of new known_hosts file
-    cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip()
-    cluster_key = config_data['cluster']['rsahostkeypub']
-    known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key)
+                 " directory") % options.data_dir)
 
 
-  else:
-    logging.info("Found a Ganeti 2.0 configuration")
+  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
 
 
-    if "config_version" in config_data["cluster"]:
-      raise Error("Inconsistent configuration: found config_data in"
-                  " configuration file")
+  try:
+    config_version = config_data["version"]
+  except KeyError:
+    raise Error("Unable to determine configuration version")
 
 
-    known_hosts = None
+  (config_major, config_minor, config_revision) = \
+    constants.SplitVersion(config_version)
 
 
-  config_version_str = "%s\n" % constants.BuildVersion(2, 0, 0)
-  try:
-    logging.info("Writing configuration file")
-    WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data))
+  logging.info("Found configuration version %s (%d.%d.%d)",
+               config_version, config_major, config_minor, config_revision)
 
 
-    logging.info("Writing configuration version %s",
-                 config_version_str.strip())
-    WriteFile(SSCONF_CONFIG_VERSION_PATH, config_version_str)
+  if "config_version" in config_data["cluster"]:
+    raise Error("Inconsistent configuration: found config_version in"
+                " configuration file")
+
+  if config_major == 2 and config_minor == 0:
+    if config_revision != 0:
+      logging.warning("Config revision is not 0")
+
+    config_data["version"] = constants.BuildVersion(2, 1, 0)
+
+  try:
+    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
+    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
+                    data=serializer.DumpJson(config_data),
+                    mode=0600,
+                    dry_run=options.dry_run,
+                    backup=True)
+
+    if not options.dry_run:
+      if not os.path.exists(options.RAPI_CERT_FILE):
+        logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
+        bootstrap.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
+
+      if not os.path.exists(options.HMAC_CLUSTER_KEY):
+        logging.debug("Writing HMAC key to %s", options.HMAC_CLUSTER_KEY)
+        bootstrap.GenerateHmacKey(options.HMAC_CLUSTER_KEY)
 
 
-    if known_hosts is not None:
-      logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip())
-      WriteFile(KNOWN_HOSTS_PATH, known_hosts)
   except:
   except:
-    logging.critical("Writing configuration failed. It is proably in an"
+    logging.critical("Writing configuration failed. It is probably in an"
                      " inconsistent state and needs manual intervention.")
     raise
 
                      " inconsistent state and needs manual intervention.")
     raise
 
+  # test loading the config file
+  if not options.dry_run:
+    logging.info("Testing the new config file...")
+    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
+                              offline=True)
+    # if we reached this, it's all fine
+    vrfy = cfg.VerifyConfig()
+    if vrfy:
+      logging.error("Errors after conversion:")
+      for item in vrfy:
+        logging.error(" - %s", item)
+    del cfg
+    logging.info("File loaded successfully")
+
 
 if __name__ == "__main__":
   main()
 
 if __name__ == "__main__":
   main()