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
52 from ganeti.utils import version
58 # Unique object to identify calls without default value
61 # Dictionary with instance old keys, and new hypervisor keys
63 "hvm_pae": constants.HV_PAE,
64 "vnc_bind_address": constants.HV_VNC_BIND_ADDRESS,
65 "initrd_path": constants.HV_INITRD_PATH,
66 "hvm_nic_type": constants.HV_NIC_TYPE,
67 "kernel_path": constants.HV_KERNEL_PATH,
68 "hvm_acpi": constants.HV_ACPI,
69 "hvm_cdrom_image_path": constants.HV_CDROM_IMAGE_PATH,
70 "hvm_boot_order": constants.HV_BOOT_ORDER,
71 "hvm_disk_type": constants.HV_DISK_TYPE,
74 # Instance beparams changes
76 "vcpus": constants.BE_VCPUS,
77 "memory": constants.BE_MEMORY,
78 "auto_balance": constants.BE_AUTO_BALANCE,
82 F_SERIAL = "serial_no"
85 class Error(Exception):
86 """Generic exception"""
91 """Returns the file name of an (old) ssconf key.
94 return "%s/ssconf_%s" % (options.data_dir, key)
97 def ReadFile(file_name, default=NoDefault):
101 logging.debug("Reading %s", file_name)
103 fh = open(file_name, "r")
105 if default is not NoDefault and err.errno == errno.ENOENT:
115 def WriteFile(file_name, data):
116 """Writes a configuration file.
119 logging.debug("Writing %s", file_name)
120 utils.WriteFile(file_name=file_name, data=data, mode=0600,
121 dry_run=options.dry_run, backup=True)
124 def GenerateSecret(all_secrets):
125 """Generate an unique DRBD secret.
127 This is a copy from ConfigWriter.
132 secret = utils.GenerateSecret()
133 if secret not in all_secrets:
137 raise Error("Can't generate unique DRBD secret")
142 """Configures the logging module.
145 formatter = logging.Formatter("%(asctime)s: %(message)s")
147 stderr_handler = logging.StreamHandler()
148 stderr_handler.setFormatter(formatter)
150 stderr_handler.setLevel(logging.NOTSET)
151 elif options.verbose:
152 stderr_handler.setLevel(logging.INFO)
154 stderr_handler.setLevel(logging.CRITICAL)
156 root_logger = logging.getLogger("")
157 root_logger.setLevel(logging.NOTSET)
158 root_logger.addHandler(stderr_handler)
161 def Cluster12To20(cluster):
162 """Upgrades the cluster object from 1.2 to 2.0.
165 logging.info("Upgrading the cluster object")
166 # Upgrade the configuration version
167 if "config_version" in cluster:
168 del cluster["config_version"]
170 # Add old ssconf keys back to config
171 logging.info(" - importing ssconf keys")
172 for key in ("master_node", "master_ip", "master_netdev", "cluster_name"):
173 if key not in cluster:
174 cluster[key] = ReadFile(SsconfName(key)).strip()
176 if "default_hypervisor" not in cluster:
177 old_hyp = ReadFile(SsconfName("hypervisor")).strip()
178 if old_hyp == "xen-3.0":
180 elif old_hyp == "xen-hvm-3.1":
182 elif old_hyp == "fake":
185 raise Error("Unknown old hypervisor name '%s'" % old_hyp)
187 logging.info("Setting the default and enabled hypervisor")
188 cluster["default_hypervisor"] = hyp
189 cluster["enabled_hypervisors"] = [hyp]
192 if "hvparams" not in cluster:
193 logging.info(" - adding hvparams")
194 cluster["hvparams"] = constants.HVC_DEFAULTS
195 if "beparams" not in cluster:
196 logging.info(" - adding beparams")
197 cluster["beparams"] = {constants.PP_DEFAULT: constants.BEC_DEFAULTS}
200 if "file_storage_dir" not in cluster:
201 cluster["file_storage_dir"] = pathutils.DEFAULT_FILE_STORAGE_DIR
203 # candidate pool size
204 if "candidate_pool_size" not in cluster:
205 cluster["candidate_pool_size"] = constants.MASTER_POOL_SIZE_DEFAULT
208 def Node12To20(node):
209 """Upgrades a node from 1.2 to 2.0.
212 logging.info("Upgrading node %s", node['name'])
213 if F_SERIAL not in node:
215 if "master_candidate" not in node:
216 node["master_candidate"] = True
217 for key in "offline", "drained":
222 def Instance12To20(drbd_minors, secrets, hypervisor, instance):
223 """Upgrades an instance from 1.2 to 2.0.
226 if F_SERIAL not in instance:
227 instance[F_SERIAL] = 1
229 if "hypervisor" not in instance:
230 instance["hypervisor"] = hypervisor
233 if "hvparams" not in instance:
234 instance["hvparams"] = hvp = {}
235 for old, new in INST_HV_CHG.items():
237 if (instance[old] is not None and
238 instance[old] != constants.VALUE_DEFAULT and # no longer valid in 2.0
239 new in constants.HVC_DEFAULTS[hypervisor]):
240 hvp[new] = instance[old]
244 if "beparams" not in instance:
245 instance["beparams"] = bep = {}
246 for old, new in INST_BE_CHG.items():
248 if instance[old] is not None:
249 bep[new] = instance[old]
253 for disk in instance["disks"]:
254 Disk12To20(drbd_minors, secrets, disk)
256 # other instance changes
257 if "status" in instance:
258 instance["admin_up"] = instance["status"] == "up"
259 del instance["status"]
262 def Disk12To20(drbd_minors, secrets, disk):
263 """Upgrades a disk from 1.2 to 2.0.
266 if "mode" not in disk:
267 disk["mode"] = constants.DISK_RDWR
268 if disk["dev_type"] == constants.DT_DRBD8:
269 old_lid = disk["logical_id"]
270 for node in old_lid[:2]:
271 if node not in drbd_minors:
272 raise Error("Can't find node '%s' while upgrading disk" % node)
273 drbd_minors[node] += 1
274 minor = drbd_minors[node]
275 old_lid.append(minor)
276 old_lid.append(GenerateSecret(secrets))
277 del disk["physical_id"]
279 for child in disk["children"]:
280 Disk12To20(drbd_minors, secrets, child)
287 # pylint: disable=W0603
290 program = os.path.basename(sys.argv[0])
293 parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
294 parser.add_option("--dry-run", dest="dry_run",
296 help="Try to do the conversion, but don't write"
298 parser.add_option(cli.FORCE_OPT)
299 parser.add_option(cli.DEBUG_OPT)
300 parser.add_option(cli.VERBOSE_OPT)
301 parser.add_option("--path", help="Convert configuration in this"
302 " directory instead of '%s'" % pathutils.DATA_DIR,
303 default=pathutils.DATA_DIR, dest="data_dir")
304 (options, args) = parser.parse_args()
306 # We need to keep filenames locally because they might be renamed between
308 options.data_dir = os.path.abspath(options.data_dir)
309 options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
310 options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
311 options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
312 options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
318 raise Error("No arguments expected")
320 if not options.force:
321 usertext = ("%s MUST be run on the master node. Is this the master"
322 " node and are ALL instances down?" % program)
323 if not cli.AskUser(usertext):
326 # Check whether it's a Ganeti configuration directory
327 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
328 os.path.isfile(options.SERVER_PEM_PATH) or
329 os.path.isfile(options.KNOWN_HOSTS_PATH)):
330 raise Error(("%s does not seem to be a known Ganeti configuration"
331 " directory") % options.data_dir)
333 config_version = ReadFile(SsconfName("config_version"), "1.2").strip()
334 logging.info("Found configuration version %s", config_version)
336 config_data = serializer.LoadJson(ReadFile(options.CONFIG_DATA_PATH))
339 if config_version == "1.2":
340 logging.info("Found a Ganeti 1.2 configuration")
342 cluster = config_data["cluster"]
344 old_config_version = cluster.get("config_version", None)
345 logging.info("Found old configuration version %s", old_config_version)
346 if old_config_version not in (3, ):
347 raise Error("Unsupported configuration version: %s" %
349 if "version" not in config_data:
350 config_data["version"] = version.BuildVersion(2, 0, 0)
351 if F_SERIAL not in config_data:
352 config_data[F_SERIAL] = 1
354 # Make sure no instance uses remote_raid1 anymore
355 remote_raid1_instances = []
356 for instance in config_data["instances"].values():
357 if instance["disk_template"] == "remote_raid1":
358 remote_raid1_instances.append(instance["name"])
359 if remote_raid1_instances:
360 for name in remote_raid1_instances:
361 logging.error("Instance %s still using remote_raid1 disk template",
363 raise Error("Unable to convert configuration as long as there are"
364 " instances using remote_raid1 disk template")
366 # Build content of new known_hosts file
367 cluster_name = ReadFile(SsconfName("cluster_name")).rstrip()
368 cluster_key = cluster["rsahostkeypub"]
369 known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key)
371 Cluster12To20(cluster)
373 # Add node attributes
374 logging.info("Upgrading nodes")
375 # stable-sort the names to have repeatable runs
376 for node_name in utils.NiceSort(config_data["nodes"].keys()):
377 Node12To20(config_data["nodes"][node_name])
380 logging.info("Upgrading instances")
381 drbd_minors = dict.fromkeys(config_data["nodes"], 0)
383 # stable-sort the names to have repeatable runs
384 for instance_name in utils.NiceSort(config_data["instances"].keys()):
385 Instance12To20(drbd_minors, secrets, cluster["default_hypervisor"],
386 config_data["instances"][instance_name])
389 logging.info("Found a Ganeti 2.0 configuration")
391 if "config_version" in config_data["cluster"]:
392 raise Error("Inconsistent configuration: found config_data in"
393 " configuration file")
398 logging.info("Writing configuration file")
399 WriteFile(options.CONFIG_DATA_PATH, serializer.DumpJson(config_data))
401 if known_hosts is not None:
402 logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip())
403 WriteFile(options.KNOWN_HOSTS_PATH, known_hosts)
405 if not options.dry_run:
406 if not os.path.exists(options.RAPI_CERT_FILE):
407 logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
408 utils.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
411 logging.critical("Writing configuration failed. It is probably in an"
412 " inconsistent state and needs manual intervention.")
415 logging.info("Configuration file updated.")
418 if __name__ == "__main__":