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
55 # Unique object to identify calls without default value
58 # Dictionary with instance old keys, and new hypervisor keys
60 "hvm_pae": constants.HV_PAE,
61 "vnc_bind_address": constants.HV_VNC_BIND_ADDRESS,
62 "initrd_path": constants.HV_INITRD_PATH,
63 "hvm_nic_type": constants.HV_NIC_TYPE,
64 "kernel_path": constants.HV_KERNEL_PATH,
65 "hvm_acpi": constants.HV_ACPI,
66 "hvm_cdrom_image_path": constants.HV_CDROM_IMAGE_PATH,
67 "hvm_boot_order": constants.HV_BOOT_ORDER,
68 "hvm_disk_type": constants.HV_DISK_TYPE,
71 # Instance beparams changes
73 "vcpus": constants.BE_VCPUS,
74 "memory": constants.BE_MEMORY,
75 "auto_balance": constants.BE_AUTO_BALANCE,
79 F_SERIAL = "serial_no"
82 class Error(Exception):
83 """Generic exception"""
88 """Returns the file name of an (old) ssconf key.
91 return "%s/ssconf_%s" % (options.data_dir, key)
94 def ReadFile(file_name, default=NoDefault):
98 logging.debug("Reading %s", file_name)
100 fh = open(file_name, "r")
102 if default is not NoDefault and err.errno == errno.ENOENT:
112 def WriteFile(file_name, data):
113 """Writes a configuration file.
116 logging.debug("Writing %s", file_name)
117 utils.WriteFile(file_name=file_name, data=data, mode=0600,
118 dry_run=options.dry_run, backup=True)
121 def GenerateSecret(all_secrets):
122 """Generate an unique DRBD secret.
124 This is a copy from ConfigWriter.
129 secret = utils.GenerateSecret()
130 if secret not in all_secrets:
134 raise Error("Can't generate unique DRBD secret")
139 """Configures the logging module.
142 formatter = logging.Formatter("%(asctime)s: %(message)s")
144 stderr_handler = logging.StreamHandler()
145 stderr_handler.setFormatter(formatter)
147 stderr_handler.setLevel(logging.NOTSET)
148 elif options.verbose:
149 stderr_handler.setLevel(logging.INFO)
151 stderr_handler.setLevel(logging.CRITICAL)
153 root_logger = logging.getLogger("")
154 root_logger.setLevel(logging.NOTSET)
155 root_logger.addHandler(stderr_handler)
158 def Cluster12To20(cluster):
159 """Upgrades the cluster object from 1.2 to 2.0.
162 logging.info("Upgrading the cluster object")
163 # Upgrade the configuration version
164 if "config_version" in cluster:
165 del cluster["config_version"]
167 # Add old ssconf keys back to config
168 logging.info(" - importing ssconf keys")
169 for key in ("master_node", "master_ip", "master_netdev", "cluster_name"):
170 if key not in cluster:
171 cluster[key] = ReadFile(SsconfName(key)).strip()
173 if "default_hypervisor" not in cluster:
174 old_hyp = ReadFile(SsconfName("hypervisor")).strip()
175 if old_hyp == "xen-3.0":
177 elif old_hyp == "xen-hvm-3.1":
179 elif old_hyp == "fake":
182 raise Error("Unknown old hypervisor name '%s'" % old_hyp)
184 logging.info("Setting the default and enabled hypervisor")
185 cluster["default_hypervisor"] = hyp
186 cluster["enabled_hypervisors"] = [hyp]
189 if "hvparams" not in cluster:
190 logging.info(" - adding hvparams")
191 cluster["hvparams"] = constants.HVC_DEFAULTS
192 if "beparams" not in cluster:
193 logging.info(" - adding beparams")
194 cluster["beparams"] = {constants.PP_DEFAULT: constants.BEC_DEFAULTS}
197 if "file_storage_dir" not in cluster:
198 cluster["file_storage_dir"] = constants.DEFAULT_FILE_STORAGE_DIR
200 # candidate pool size
201 if "candidate_pool_size" not in cluster:
202 cluster["candidate_pool_size"] = constants.MASTER_POOL_SIZE_DEFAULT
205 def Node12To20(node):
206 """Upgrades a node from 1.2 to 2.0.
209 logging.info("Upgrading node %s", node['name'])
210 if F_SERIAL not in node:
212 if "master_candidate" not in node:
213 node["master_candidate"] = True
214 for key in "offline", "drained":
219 def Instance12To20(drbd_minors, secrets, hypervisor, instance):
220 """Upgrades an instance from 1.2 to 2.0.
223 if F_SERIAL not in instance:
224 instance[F_SERIAL] = 1
226 if "hypervisor" not in instance:
227 instance["hypervisor"] = hypervisor
230 if "hvparams" not in instance:
231 instance["hvparams"] = hvp = {}
232 for old, new in INST_HV_CHG.items():
234 if (instance[old] is not None and
235 instance[old] != constants.VALUE_DEFAULT and # no longer valid in 2.0
236 new in constants.HVC_DEFAULTS[hypervisor]):
237 hvp[new] = instance[old]
241 if "beparams" not in instance:
242 instance["beparams"] = bep = {}
243 for old, new in INST_BE_CHG.items():
245 if instance[old] is not None:
246 bep[new] = instance[old]
250 for disk in instance["disks"]:
251 Disk12To20(drbd_minors, secrets, disk)
253 # other instance changes
254 if "status" in instance:
255 instance["admin_up"] = instance["status"] == "up"
256 del instance["status"]
259 def Disk12To20(drbd_minors, secrets, disk):
260 """Upgrades a disk from 1.2 to 2.0.
263 if "mode" not in disk:
264 disk["mode"] = constants.DISK_RDWR
265 if disk["dev_type"] == constants.LD_DRBD8:
266 old_lid = disk["logical_id"]
267 for node in old_lid[:2]:
268 if node not in drbd_minors:
269 raise Error("Can't find node '%s' while upgrading disk" % node)
270 drbd_minors[node] += 1
271 minor = drbd_minors[node]
272 old_lid.append(minor)
273 old_lid.append(GenerateSecret(secrets))
274 del disk["physical_id"]
276 for child in disk["children"]:
277 Disk12To20(drbd_minors, secrets, child)
284 # pylint: disable=W0603
287 program = os.path.basename(sys.argv[0])
290 parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
291 parser.add_option("--dry-run", dest="dry_run",
293 help="Try to do the conversion, but don't write"
295 parser.add_option(cli.FORCE_OPT)
296 parser.add_option(cli.DEBUG_OPT)
297 parser.add_option(cli.VERBOSE_OPT)
298 parser.add_option("--path", help="Convert configuration in this"
299 " directory instead of '%s'" % constants.DATA_DIR,
300 default=constants.DATA_DIR, dest="data_dir")
301 (options, args) = parser.parse_args()
303 # We need to keep filenames locally because they might be renamed between
305 options.data_dir = os.path.abspath(options.data_dir)
306 options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
307 options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
308 options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
309 options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
315 raise Error("No arguments expected")
317 if not options.force:
318 usertext = ("%s MUST be run on the master node. Is this the master"
319 " node and are ALL instances down?" % program)
320 if not cli.AskUser(usertext):
323 # Check whether it's a Ganeti configuration directory
324 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
325 os.path.isfile(options.SERVER_PEM_PATH) or
326 os.path.isfile(options.KNOWN_HOSTS_PATH)):
327 raise Error(("%s does not seem to be a known Ganeti configuration"
328 " directory") % options.data_dir)
330 config_version = ReadFile(SsconfName("config_version"), "1.2").strip()
331 logging.info("Found configuration version %s", config_version)
333 config_data = serializer.LoadJson(ReadFile(options.CONFIG_DATA_PATH))
336 if config_version == "1.2":
337 logging.info("Found a Ganeti 1.2 configuration")
339 cluster = config_data["cluster"]
341 old_config_version = cluster.get("config_version", None)
342 logging.info("Found old configuration version %s", old_config_version)
343 if old_config_version not in (3, ):
344 raise Error("Unsupported configuration version: %s" %
346 if "version" not in config_data:
347 config_data["version"] = constants.BuildVersion(2, 0, 0)
348 if F_SERIAL not in config_data:
349 config_data[F_SERIAL] = 1
351 # Make sure no instance uses remote_raid1 anymore
352 remote_raid1_instances = []
353 for instance in config_data["instances"].values():
354 if instance["disk_template"] == "remote_raid1":
355 remote_raid1_instances.append(instance["name"])
356 if remote_raid1_instances:
357 for name in remote_raid1_instances:
358 logging.error("Instance %s still using remote_raid1 disk template",
360 raise Error("Unable to convert configuration as long as there are"
361 " instances using remote_raid1 disk template")
363 # Build content of new known_hosts file
364 cluster_name = ReadFile(SsconfName("cluster_name")).rstrip()
365 cluster_key = cluster["rsahostkeypub"]
366 known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key)
368 Cluster12To20(cluster)
370 # Add node attributes
371 logging.info("Upgrading nodes")
372 # stable-sort the names to have repeatable runs
373 for node_name in utils.NiceSort(config_data["nodes"].keys()):
374 Node12To20(config_data["nodes"][node_name])
377 logging.info("Upgrading instances")
378 drbd_minors = dict.fromkeys(config_data["nodes"], 0)
380 # stable-sort the names to have repeatable runs
381 for instance_name in utils.NiceSort(config_data["instances"].keys()):
382 Instance12To20(drbd_minors, secrets, cluster["default_hypervisor"],
383 config_data["instances"][instance_name])
386 logging.info("Found a Ganeti 2.0 configuration")
388 if "config_version" in config_data["cluster"]:
389 raise Error("Inconsistent configuration: found config_data in"
390 " configuration file")
395 logging.info("Writing configuration file")
396 WriteFile(options.CONFIG_DATA_PATH, serializer.DumpJson(config_data))
398 if known_hosts is not None:
399 logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip())
400 WriteFile(options.KNOWN_HOSTS_PATH, known_hosts)
402 if not options.dry_run:
403 if not os.path.exists(options.RAPI_CERT_FILE):
404 logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
405 utils.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
408 logging.critical("Writing configuration failed. It is probably in an"
409 " inconsistent state and needs manual intervention.")
412 logging.info("Configuration file updated.")
415 if __name__ == "__main__":