4 # Copyright (C) 2007, 2008, 2009 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 # pylint: disable=C0103,E1103
23 # C0103: invalid name NoDefault
24 # E1103: Instance of 'foor' has no 'bar' member (but some types could
28 """Tool to upgrade the configuration file.
30 This code handles only the types supported by simplejson. As an
31 example, 'set' is a 'list'.
33 @note: this has lots of duplicate content with C{cfgupgrade}. Ideally, it
46 from ganeti import constants
47 from ganeti import serializer
48 from ganeti import utils
49 from ganeti import cli
50 from ganeti import pathutils
56 # Unique object to identify calls without default value
59 # Dictionary with instance old keys, and new hypervisor keys
61 "hvm_pae": constants.HV_PAE,
62 "vnc_bind_address": constants.HV_VNC_BIND_ADDRESS,
63 "initrd_path": constants.HV_INITRD_PATH,
64 "hvm_nic_type": constants.HV_NIC_TYPE,
65 "kernel_path": constants.HV_KERNEL_PATH,
66 "hvm_acpi": constants.HV_ACPI,
67 "hvm_cdrom_image_path": constants.HV_CDROM_IMAGE_PATH,
68 "hvm_boot_order": constants.HV_BOOT_ORDER,
69 "hvm_disk_type": constants.HV_DISK_TYPE,
72 # Instance beparams changes
74 "vcpus": constants.BE_VCPUS,
75 "memory": constants.BE_MEMORY,
76 "auto_balance": constants.BE_AUTO_BALANCE,
80 F_SERIAL = "serial_no"
83 class Error(Exception):
84 """Generic exception"""
89 """Returns the file name of an (old) ssconf key.
92 return "%s/ssconf_%s" % (options.data_dir, key)
95 def ReadFile(file_name, default=NoDefault):
99 logging.debug("Reading %s", file_name)
101 fh = open(file_name, "r")
103 if default is not NoDefault and err.errno == errno.ENOENT:
113 def WriteFile(file_name, data):
114 """Writes a configuration file.
117 logging.debug("Writing %s", file_name)
118 utils.WriteFile(file_name=file_name, data=data, mode=0600,
119 dry_run=options.dry_run, backup=True)
122 def GenerateSecret(all_secrets):
123 """Generate an unique DRBD secret.
125 This is a copy from ConfigWriter.
130 secret = utils.GenerateSecret()
131 if secret not in all_secrets:
135 raise Error("Can't generate unique DRBD secret")
140 """Configures the logging module.
143 formatter = logging.Formatter("%(asctime)s: %(message)s")
145 stderr_handler = logging.StreamHandler()
146 stderr_handler.setFormatter(formatter)
148 stderr_handler.setLevel(logging.NOTSET)
149 elif options.verbose:
150 stderr_handler.setLevel(logging.INFO)
152 stderr_handler.setLevel(logging.CRITICAL)
154 root_logger = logging.getLogger("")
155 root_logger.setLevel(logging.NOTSET)
156 root_logger.addHandler(stderr_handler)
159 def Cluster12To20(cluster):
160 """Upgrades the cluster object from 1.2 to 2.0.
163 logging.info("Upgrading the cluster object")
164 # Upgrade the configuration version
165 if "config_version" in cluster:
166 del cluster["config_version"]
168 # Add old ssconf keys back to config
169 logging.info(" - importing ssconf keys")
170 for key in ("master_node", "master_ip", "master_netdev", "cluster_name"):
171 if key not in cluster:
172 cluster[key] = ReadFile(SsconfName(key)).strip()
174 if "default_hypervisor" not in cluster:
175 old_hyp = ReadFile(SsconfName("hypervisor")).strip()
176 if old_hyp == "xen-3.0":
178 elif old_hyp == "xen-hvm-3.1":
180 elif old_hyp == "fake":
183 raise Error("Unknown old hypervisor name '%s'" % old_hyp)
185 logging.info("Setting the default and enabled hypervisor")
186 cluster["default_hypervisor"] = hyp
187 cluster["enabled_hypervisors"] = [hyp]
190 if "hvparams" not in cluster:
191 logging.info(" - adding hvparams")
192 cluster["hvparams"] = constants.HVC_DEFAULTS
193 if "beparams" not in cluster:
194 logging.info(" - adding beparams")
195 cluster["beparams"] = {constants.PP_DEFAULT: constants.BEC_DEFAULTS}
198 if "file_storage_dir" not in cluster:
199 cluster["file_storage_dir"] = pathutils.DEFAULT_FILE_STORAGE_DIR
201 # candidate pool size
202 if "candidate_pool_size" not in cluster:
203 cluster["candidate_pool_size"] = constants.MASTER_POOL_SIZE_DEFAULT
206 def Node12To20(node):
207 """Upgrades a node from 1.2 to 2.0.
210 logging.info("Upgrading node %s", node['name'])
211 if F_SERIAL not in node:
213 if "master_candidate" not in node:
214 node["master_candidate"] = True
215 for key in "offline", "drained":
220 def Instance12To20(drbd_minors, secrets, hypervisor, instance):
221 """Upgrades an instance from 1.2 to 2.0.
224 if F_SERIAL not in instance:
225 instance[F_SERIAL] = 1
227 if "hypervisor" not in instance:
228 instance["hypervisor"] = hypervisor
231 if "hvparams" not in instance:
232 instance["hvparams"] = hvp = {}
233 for old, new in INST_HV_CHG.items():
235 if (instance[old] is not None and
236 instance[old] != constants.VALUE_DEFAULT and # no longer valid in 2.0
237 new in constants.HVC_DEFAULTS[hypervisor]):
238 hvp[new] = instance[old]
242 if "beparams" not in instance:
243 instance["beparams"] = bep = {}
244 for old, new in INST_BE_CHG.items():
246 if instance[old] is not None:
247 bep[new] = instance[old]
251 for disk in instance["disks"]:
252 Disk12To20(drbd_minors, secrets, disk)
254 # other instance changes
255 if "status" in instance:
256 instance["admin_up"] = instance["status"] == "up"
257 del instance["status"]
260 def Disk12To20(drbd_minors, secrets, disk):
261 """Upgrades a disk from 1.2 to 2.0.
264 if "mode" not in disk:
265 disk["mode"] = constants.DISK_RDWR
266 if disk["dev_type"] == constants.DT_DRBD8:
267 old_lid = disk["logical_id"]
268 for node in old_lid[:2]:
269 if node not in drbd_minors:
270 raise Error("Can't find node '%s' while upgrading disk" % node)
271 drbd_minors[node] += 1
272 minor = drbd_minors[node]
273 old_lid.append(minor)
274 old_lid.append(GenerateSecret(secrets))
275 del disk["physical_id"]
277 for child in disk["children"]:
278 Disk12To20(drbd_minors, secrets, child)
285 # pylint: disable=W0603
288 program = os.path.basename(sys.argv[0])
291 parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
292 parser.add_option("--dry-run", dest="dry_run",
294 help="Try to do the conversion, but don't write"
296 parser.add_option(cli.FORCE_OPT)
297 parser.add_option(cli.DEBUG_OPT)
298 parser.add_option(cli.VERBOSE_OPT)
299 parser.add_option("--path", help="Convert configuration in this"
300 " directory instead of '%s'" % pathutils.DATA_DIR,
301 default=pathutils.DATA_DIR, dest="data_dir")
302 (options, args) = parser.parse_args()
304 # We need to keep filenames locally because they might be renamed between
306 options.data_dir = os.path.abspath(options.data_dir)
307 options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
308 options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
309 options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
310 options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
316 raise Error("No arguments expected")
318 if not options.force:
319 usertext = ("%s MUST be run on the master node. Is this the master"
320 " node and are ALL instances down?" % program)
321 if not cli.AskUser(usertext):
324 # Check whether it's a Ganeti configuration directory
325 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
326 os.path.isfile(options.SERVER_PEM_PATH) or
327 os.path.isfile(options.KNOWN_HOSTS_PATH)):
328 raise Error(("%s does not seem to be a known Ganeti configuration"
329 " directory") % options.data_dir)
331 config_version = ReadFile(SsconfName("config_version"), "1.2").strip()
332 logging.info("Found configuration version %s", config_version)
334 config_data = serializer.LoadJson(ReadFile(options.CONFIG_DATA_PATH))
337 if config_version == "1.2":
338 logging.info("Found a Ganeti 1.2 configuration")
340 cluster = config_data["cluster"]
342 old_config_version = cluster.get("config_version", None)
343 logging.info("Found old configuration version %s", old_config_version)
344 if old_config_version not in (3, ):
345 raise Error("Unsupported configuration version: %s" %
347 if "version" not in config_data:
348 config_data["version"] = constants.BuildVersion(2, 0, 0)
349 if F_SERIAL not in config_data:
350 config_data[F_SERIAL] = 1
352 # Make sure no instance uses remote_raid1 anymore
353 remote_raid1_instances = []
354 for instance in config_data["instances"].values():
355 if instance["disk_template"] == "remote_raid1":
356 remote_raid1_instances.append(instance["name"])
357 if remote_raid1_instances:
358 for name in remote_raid1_instances:
359 logging.error("Instance %s still using remote_raid1 disk template",
361 raise Error("Unable to convert configuration as long as there are"
362 " instances using remote_raid1 disk template")
364 # Build content of new known_hosts file
365 cluster_name = ReadFile(SsconfName("cluster_name")).rstrip()
366 cluster_key = cluster["rsahostkeypub"]
367 known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key)
369 Cluster12To20(cluster)
371 # Add node attributes
372 logging.info("Upgrading nodes")
373 # stable-sort the names to have repeatable runs
374 for node_name in utils.NiceSort(config_data["nodes"].keys()):
375 Node12To20(config_data["nodes"][node_name])
378 logging.info("Upgrading instances")
379 drbd_minors = dict.fromkeys(config_data["nodes"], 0)
381 # stable-sort the names to have repeatable runs
382 for instance_name in utils.NiceSort(config_data["instances"].keys()):
383 Instance12To20(drbd_minors, secrets, cluster["default_hypervisor"],
384 config_data["instances"][instance_name])
387 logging.info("Found a Ganeti 2.0 configuration")
389 if "config_version" in config_data["cluster"]:
390 raise Error("Inconsistent configuration: found config_data in"
391 " configuration file")
396 logging.info("Writing configuration file")
397 WriteFile(options.CONFIG_DATA_PATH, serializer.DumpJson(config_data))
399 if known_hosts is not None:
400 logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip())
401 WriteFile(options.KNOWN_HOSTS_PATH, known_hosts)
403 if not options.dry_run:
404 if not os.path.exists(options.RAPI_CERT_FILE):
405 logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
406 utils.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
409 logging.critical("Writing configuration failed. It is probably in an"
410 " inconsistent state and needs manual intervention.")
413 logging.info("Configuration file updated.")
416 if __name__ == "__main__":