Revision ac4d25b6 tools/cfgupgrade
b/tools/cfgupgrade | ||
---|---|---|
40 | 40 |
from ganeti import utils |
41 | 41 |
from ganeti import cli |
42 | 42 |
from ganeti import bootstrap |
43 |
from ganeti import config |
|
43 | 44 |
|
44 | 45 |
|
45 |
# We need to keep filenames locally because they might be renamed between |
|
46 |
# versions. |
|
47 |
CONFIG_DATA_PATH = constants.DATA_DIR + "/config.data" |
|
48 |
SERVER_PEM_PATH = constants.DATA_DIR + "/server.pem" |
|
49 |
KNOWN_HOSTS_PATH = constants.DATA_DIR + "/known_hosts" |
|
50 |
SSCONF_CLUSTER_NAME_PATH = constants.DATA_DIR + "/ssconf_cluster_name" |
|
51 |
SSCONF_CONFIG_VERSION_PATH = constants.DATA_DIR + "/ssconf_config_version" |
|
52 |
|
|
53 | 46 |
options = None |
54 | 47 |
args = None |
55 | 48 |
|
56 | 49 |
# Unique object to identify calls without default value |
57 | 50 |
NoDefault = object() |
58 | 51 |
|
52 |
# Dictionary with instance old keys, and new hypervisor keys |
|
53 |
INST_HV_CHG = { |
|
54 |
'hvm_pae': constants.HV_PAE, |
|
55 |
'vnc_bind_address': constants.HV_VNC_BIND_ADDRESS, |
|
56 |
'initrd_path': constants.HV_INITRD_PATH, |
|
57 |
'hvm_nic_type': constants.HV_NIC_TYPE, |
|
58 |
'kernel_path': constants.HV_KERNEL_PATH, |
|
59 |
'hvm_acpi': constants.HV_ACPI, |
|
60 |
'hvm_cdrom_image_path': constants.HV_CDROM_IMAGE_PATH, |
|
61 |
'hvm_boot_order': constants.HV_BOOT_ORDER, |
|
62 |
'hvm_disk_type': constants.HV_DISK_TYPE, |
|
63 |
} |
|
64 |
|
|
65 |
# Instance beparams changes |
|
66 |
INST_BE_CHG = { |
|
67 |
'vcpus': constants.BE_VCPUS, |
|
68 |
'memory': constants.BE_MEMORY, |
|
69 |
'auto_balance': constants.BE_AUTO_BALANCE, |
|
70 |
} |
|
59 | 71 |
|
60 | 72 |
class Error(Exception): |
61 | 73 |
"""Generic exception""" |
... | ... | |
66 | 78 |
"""Returns the file name of an (old) ssconf key. |
67 | 79 |
|
68 | 80 |
""" |
69 |
return "%s/ssconf_%s" % (constants.DATA_DIR, key)
|
|
81 |
return "%s/ssconf_%s" % (options.data_dir, key)
|
|
70 | 82 |
|
71 | 83 |
|
72 | 84 |
def ReadFile(file_name, default=NoDefault): |
... | ... | |
96 | 108 |
dry_run=options.dry_run, backup=True) |
97 | 109 |
|
98 | 110 |
|
111 |
def GenerateSecret(all_secrets): |
|
112 |
"""Generate an unique DRBD secret. |
|
113 |
|
|
114 |
This is a copy from ConfigWriter. |
|
115 |
|
|
116 |
""" |
|
117 |
retries = 64 |
|
118 |
while retries > 0: |
|
119 |
secret = utils.GenerateSecret() |
|
120 |
if secret not in all_secrets: |
|
121 |
break |
|
122 |
retries -= 1 |
|
123 |
else: |
|
124 |
raise Error("Can't generate unique DRBD secret") |
|
125 |
return secret |
|
126 |
|
|
127 |
|
|
99 | 128 |
def SetupLogging(): |
100 | 129 |
"""Configures the logging module. |
101 | 130 |
|
... | ... | |
158 | 187 |
if 'file_storage_dir' not in cluster: |
159 | 188 |
cluster['file_storage_dir'] = constants.DEFAULT_FILE_STORAGE_DIR |
160 | 189 |
|
190 |
# candidate pool size |
|
191 |
if 'candidate_pool_size' not in cluster: |
|
192 |
cluster['candidate_pool_size'] = constants.MASTER_POOL_SIZE_DEFAULT |
|
193 |
|
|
161 | 194 |
|
162 | 195 |
def Node12To20(node): |
163 | 196 |
"""Upgrades a node from 1.2 to 2.0. |
... | ... | |
173 | 206 |
node[key] = False |
174 | 207 |
|
175 | 208 |
|
209 |
def Instance12To20(drbd_minors, secrets, hypervisor, instance): |
|
210 |
"""Upgrades an instance from 1.2 to 2.0. |
|
211 |
|
|
212 |
""" |
|
213 |
if 'hypervisor' not in instance: |
|
214 |
instance['hypervisor'] = hypervisor |
|
215 |
|
|
216 |
# hvparams changes |
|
217 |
if 'hvparams' not in instance: |
|
218 |
instance['hvparams'] = hvp = {} |
|
219 |
for old, new in INST_HV_CHG.items(): |
|
220 |
if old in instance: |
|
221 |
if (instance[old] is not None and |
|
222 |
instance[old] != constants.VALUE_DEFAULT and # no longer valid in 2.0 |
|
223 |
new in constants.HVC_DEFAULTS[hypervisor]): |
|
224 |
hvp[new] = instance[old] |
|
225 |
del instance[old] |
|
226 |
|
|
227 |
# beparams changes |
|
228 |
if 'beparams' not in instance: |
|
229 |
instance['beparams'] = bep = {} |
|
230 |
for old, new in INST_BE_CHG.items(): |
|
231 |
if old in instance: |
|
232 |
if instance[old] is not None: |
|
233 |
bep[new] = instance[old] |
|
234 |
del instance[old] |
|
235 |
|
|
236 |
# disk changes |
|
237 |
for disk in instance['disks']: |
|
238 |
Disk12To20(drbd_minors, secrets, disk) |
|
239 |
|
|
240 |
# other instance changes |
|
241 |
if 'status' in instance: |
|
242 |
instance['admin_up'] = instance['status'] == 'up' |
|
243 |
del instance['status'] |
|
244 |
|
|
245 |
|
|
246 |
def Disk12To20(drbd_minors, secrets, disk): |
|
247 |
"""Upgrades a disk from 1.2 to 2.0. |
|
248 |
|
|
249 |
""" |
|
250 |
if 'mode' not in disk: |
|
251 |
disk['mode'] = constants.DISK_RDWR |
|
252 |
if disk['dev_type'] == constants.LD_DRBD8: |
|
253 |
old_lid = disk['logical_id'] |
|
254 |
for node in old_lid[:2]: |
|
255 |
if node not in drbd_minors: |
|
256 |
raise Error("Can't find node '%s' while upgrading disk" % node) |
|
257 |
drbd_minors[node] += 1 |
|
258 |
minor = drbd_minors[node] |
|
259 |
old_lid.append(minor) |
|
260 |
old_lid.append(GenerateSecret(secrets)) |
|
261 |
del disk['physical_id'] |
|
262 |
if disk['children']: |
|
263 |
for child in disk['children']: |
|
264 |
Disk12To20(drbd_minors, secrets, child) |
|
265 |
|
|
266 |
|
|
176 | 267 |
def main(): |
177 | 268 |
"""Main program. |
178 | 269 |
|
... | ... | |
192 | 283 |
parser.add_option('-v', '--verbose', dest='verbose', |
193 | 284 |
action="store_true", |
194 | 285 |
help="Verbose output") |
286 |
parser.add_option('--path', help="Convert configuration in this" |
|
287 |
" directory instead of '%s'" % constants.DATA_DIR, |
|
288 |
default=constants.DATA_DIR, dest="data_dir") |
|
195 | 289 |
(options, args) = parser.parse_args() |
196 | 290 |
|
291 |
# We need to keep filenames locally because they might be renamed between |
|
292 |
# versions. |
|
293 |
options.CONFIG_DATA_PATH = options.data_dir + "/config.data" |
|
294 |
options.SERVER_PEM_PATH = options.data_dir + "/server.pem" |
|
295 |
options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts" |
|
296 |
options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem" |
|
297 |
|
|
197 | 298 |
SetupLogging() |
198 | 299 |
|
199 | 300 |
# Option checking |
... | ... | |
207 | 308 |
sys.exit(1) |
208 | 309 |
|
209 | 310 |
# Check whether it's a Ganeti configuration directory |
210 |
if not (os.path.isfile(CONFIG_DATA_PATH) and |
|
211 |
os.path.isfile(SERVER_PEM_PATH) or |
|
212 |
os.path.isfile(KNOWN_HOSTS_PATH)): |
|
311 |
if not (os.path.isfile(options.CONFIG_DATA_PATH) and
|
|
312 |
os.path.isfile(options.SERVER_PEM_PATH) or
|
|
313 |
os.path.isfile(options.KNOWN_HOSTS_PATH)):
|
|
213 | 314 |
raise Error(("%s does not seem to be a known Ganeti configuration" |
214 |
" directory") % constants.DATA_DIR)
|
|
315 |
" directory") % options.data_dir)
|
|
215 | 316 |
|
216 |
config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip()
|
|
317 |
config_version = ReadFile(SsconfName('config_version'), "1.2").strip()
|
|
217 | 318 |
logging.info("Found configuration version %s", config_version) |
218 | 319 |
|
219 |
config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH)) |
|
320 |
config_data = serializer.LoadJson(ReadFile(options.CONFIG_DATA_PATH))
|
|
220 | 321 |
|
221 | 322 |
# Ganeti 1.2? |
222 | 323 |
if config_version == "1.2": |
... | ... | |
236 | 337 |
|
237 | 338 |
# Make sure no instance uses remote_raid1 anymore |
238 | 339 |
remote_raid1_instances = [] |
239 |
for instance in config_data["instances"]: |
|
340 |
for instance in config_data["instances"].values():
|
|
240 | 341 |
if instance["disk_template"] == "remote_raid1": |
241 | 342 |
remote_raid1_instances.append(instance["name"]) |
242 | 343 |
if remote_raid1_instances: |
... | ... | |
246 | 347 |
" instances using remote_raid1 disk template") |
247 | 348 |
|
248 | 349 |
# Build content of new known_hosts file |
249 |
cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip()
|
|
350 |
cluster_name = ReadFile(SsconfName('cluster_name')).rstrip()
|
|
250 | 351 |
cluster_key = cluster['rsahostkeypub'] |
251 | 352 |
known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key) |
252 | 353 |
|
... | ... | |
258 | 359 |
for node_name in utils.NiceSort(config_data['nodes'].keys()): |
259 | 360 |
Node12To20(config_data['nodes'][node_name]) |
260 | 361 |
|
261 |
# instance changes |
|
262 |
# TODO: add instance upgrade |
|
263 |
for instance in config_data['instances'].values(): |
|
264 |
pass |
|
362 |
# Instance changes |
|
363 |
logging.info("Upgrading instances") |
|
364 |
drbd_minors = dict.fromkeys(config_data['nodes'], 0) |
|
365 |
secrets = set() |
|
366 |
# stable-sort the names to have repeatable runs |
|
367 |
for instance_name in utils.NiceSort(config_data['instances'].keys()): |
|
368 |
Instance12To20(drbd_minors, secrets, cluster['default_hypervisor'], |
|
369 |
config_data['instances'][instance_name]) |
|
265 | 370 |
|
266 | 371 |
else: |
267 | 372 |
logging.info("Found a Ganeti 2.0 configuration") |
... | ... | |
274 | 379 |
|
275 | 380 |
try: |
276 | 381 |
logging.info("Writing configuration file") |
277 |
WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data)) |
|
382 |
WriteFile(options.CONFIG_DATA_PATH, serializer.DumpJson(config_data))
|
|
278 | 383 |
|
279 | 384 |
if known_hosts is not None: |
280 | 385 |
logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip()) |
281 |
WriteFile(KNOWN_HOSTS_PATH, known_hosts) |
|
386 |
WriteFile(options.KNOWN_HOSTS_PATH, known_hosts)
|
|
282 | 387 |
|
283 | 388 |
if not options.dry_run: |
284 |
if not os.path.exists(constants.RAPI_CERT_FILE):
|
|
285 |
bootstrap._GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE)
|
|
389 |
if not os.path.exists(options.RAPI_CERT_FILE):
|
|
390 |
bootstrap._GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
|
|
286 | 391 |
|
287 | 392 |
except: |
288 | 393 |
logging.critical("Writing configuration failed. It is proably in an" |
289 | 394 |
" inconsistent state and needs manual intervention.") |
290 | 395 |
raise |
291 | 396 |
|
397 |
# test loading the config file |
|
398 |
if not options.dry_run: |
|
399 |
logging.info("Testing the new config file...") |
|
400 |
cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH, |
|
401 |
offline=True) |
|
402 |
# if we reached this, it's all fine |
|
403 |
vrfy = cfg.VerifyConfig() |
|
404 |
if vrfy: |
|
405 |
logging.error("Errors after conversion:") |
|
406 |
for item in vrfy: |
|
407 |
logging.error(" - %s" % item) |
|
408 |
del cfg |
|
409 |
logging.info("File loaded successfully") |
|
410 |
|
|
292 | 411 |
|
293 | 412 |
if __name__ == "__main__": |
294 | 413 |
main() |
Also available in: Unified diff