Revision 95e4a814
b/tools/cfgupgrade | ||
---|---|---|
32 | 32 |
import sys |
33 | 33 |
import optparse |
34 | 34 |
import tempfile |
35 |
import simplejson |
|
36 | 35 |
import logging |
36 |
import errno |
|
37 | 37 |
|
38 |
from ganeti import constants |
|
39 |
from ganeti import serializer |
|
38 | 40 |
from ganeti import utils |
39 | 41 |
from ganeti import cli |
40 | 42 |
|
41 | 43 |
|
44 |
# We need to keep filenames locally because they might be renamed between |
|
45 |
# versions. |
|
46 |
CONFIG_DATA_PATH = constants.DATA_DIR + "/config.data" |
|
47 |
SERVER_PEM_PATH = constants.DATA_DIR + "/server.pem" |
|
48 |
KNOWN_HOSTS_PATH = constants.DATA_DIR + "/known_hosts" |
|
49 |
SSCONF_CLUSTER_NAME_PATH = constants.DATA_DIR + "/ssconf_cluster_name" |
|
50 |
SSCONF_CONFIG_VERSION_PATH = constants.DATA_DIR + "/ssconf_config_version" |
|
51 |
|
|
42 | 52 |
options = None |
43 | 53 |
args = None |
44 | 54 |
|
55 |
# Unique object to identify calls without default value |
|
56 |
NoDefault = object() |
|
57 |
|
|
45 | 58 |
|
46 | 59 |
class Error(Exception): |
47 | 60 |
"""Generic exception""" |
48 | 61 |
pass |
49 | 62 |
|
50 | 63 |
|
51 |
def ReadConfig(path):
|
|
52 |
"""Reads configuration file.
|
|
64 |
def ReadFile(file_name, default=NoDefault):
|
|
65 |
"""Reads a file.
|
|
53 | 66 |
|
54 | 67 |
""" |
55 |
f = open(path, 'r')
|
|
68 |
logging.debug("Reading %s", file_name)
|
|
56 | 69 |
try: |
57 |
return simplejson.load(f) |
|
58 |
finally: |
|
59 |
f.close() |
|
60 |
|
|
61 |
|
|
62 |
def WriteConfig(path, data): |
|
63 |
"""Writes the configuration file. |
|
64 |
|
|
65 |
""" |
|
66 |
if not options.dry_run: |
|
67 |
utils.CreateBackup(path) |
|
70 |
fh = open(file_name, 'r') |
|
71 |
except IOError, err: |
|
72 |
if default is not NoDefault and err.errno == errno.ENOENT: |
|
73 |
return default |
|
74 |
raise |
|
68 | 75 |
|
69 |
(fd, name) = tempfile.mkstemp(dir=os.path.dirname(path)) |
|
70 |
f = os.fdopen(fd, 'w') |
|
71 | 76 |
try: |
72 |
try: |
|
73 |
simplejson.dump(data, f) |
|
74 |
f.flush() |
|
75 |
if options.dry_run: |
|
76 |
os.unlink(name) |
|
77 |
else: |
|
78 |
os.rename(name, path) |
|
79 |
except: |
|
80 |
os.unlink(name) |
|
81 |
raise |
|
77 |
return fh.read() |
|
82 | 78 |
finally: |
83 |
f.close() |
|
79 |
fh.close()
|
|
84 | 80 |
|
85 | 81 |
|
86 |
def UpdateFromVersion2To3(cfg):
|
|
87 |
"""Updates the configuration from version 2 to 3.
|
|
82 |
def WriteFile(file_name, data):
|
|
83 |
"""Writes a configuration file.
|
|
88 | 84 |
|
89 | 85 |
""" |
90 |
if cfg['cluster']['config_version'] != 2: |
|
91 |
return |
|
92 |
|
|
93 |
# Add port pool |
|
94 |
if 'tcpudp_port_pool' not in cfg['cluster']: |
|
95 |
cfg['cluster']['tcpudp_port_pool'] = [] |
|
96 |
|
|
97 |
# Add bridge settings |
|
98 |
if 'default_bridge' not in cfg['cluster']: |
|
99 |
cfg['cluster']['default_bridge'] = 'xen-br0' |
|
100 |
for inst in cfg['instances'].values(): |
|
101 |
for nic in inst['nics']: |
|
102 |
if 'bridge' not in nic: |
|
103 |
nic['bridge'] = None |
|
104 |
|
|
105 |
cfg['cluster']['config_version'] = 3 |
|
86 |
logging.debug("Writing %s", file_name) |
|
87 |
utils.WriteFile(file_name=file_name, data=data, mode=0600, |
|
88 |
dry_run=options.dry_run, backup=True) |
|
106 | 89 |
|
107 | 90 |
|
108 | 91 |
def SetupLogging(): |
... | ... | |
134 | 117 |
program = os.path.basename(sys.argv[0]) |
135 | 118 |
|
136 | 119 |
# Option parsing |
137 |
parser = optparse.OptionParser(usage="%prog [options] <config-file>")
|
|
120 |
parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
|
|
138 | 121 |
parser.add_option('--dry-run', dest='dry_run', |
139 | 122 |
action="store_true", |
140 | 123 |
help="Try to do the conversion, but don't write" |
... | ... | |
150 | 133 |
|
151 | 134 |
# Option checking |
152 | 135 |
if args: |
153 |
cfg_file = args[0] |
|
154 |
else: |
|
155 |
raise Error("Configuration file not specified") |
|
136 |
raise Error("No arguments expected") |
|
156 | 137 |
|
157 | 138 |
if not options.force: |
158 | 139 |
usertext = ("%s MUST run on the master node. Is this the master" |
... | ... | |
160 | 141 |
if not cli.AskUser(usertext): |
161 | 142 |
sys.exit(1) |
162 | 143 |
|
163 |
config = ReadConfig(cfg_file) |
|
144 |
# Check whether it's a Ganeti configuration directory |
|
145 |
if not (os.path.isfile(CONFIG_DATA_PATH) and |
|
146 |
os.path.isfile(SERVER_PEM_PATH) or |
|
147 |
os.path.isfile(KNOWN_HOSTS_PATH)): |
|
148 |
raise Error(("%s does not seem to be a known Ganeti configuration" |
|
149 |
" directory") % constants.DATA_DIR) |
|
150 |
|
|
151 |
config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip() |
|
152 |
logging.info("Found configuration version %s", config_version) |
|
153 |
|
|
154 |
config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH)) |
|
155 |
|
|
156 |
# Ganeti 1.2? |
|
157 |
if config_version == "1.2": |
|
158 |
logging.info("Found a Ganeti 1.2 configuration") |
|
159 |
|
|
160 |
old_config_version = config_data["cluster"].get("config_version", None) |
|
161 |
logging.info("Found old configuration version %s", old_config_version) |
|
162 |
if old_config_version not in (3, ): |
|
163 |
raise Error("Unsupported configuration version: %s" % |
|
164 |
old_config_version) |
|
165 |
|
|
166 |
# Make sure no instance uses remote_raid1 anymore |
|
167 |
remote_raid1_instances = [] |
|
168 |
for instance in config_data["instances"]: |
|
169 |
if instance["disk_template"] == "remote_raid1": |
|
170 |
remote_raid1_instances.append(instance["name"]) |
|
171 |
if remote_raid1_instances: |
|
172 |
for name in remote_raid1_instances: |
|
173 |
logging.error("Instance %s still using remote_raid1 disk template") |
|
174 |
raise Error("Unable to convert configuration as long as there are" |
|
175 |
" instances using remote_raid1 disk template") |
|
176 |
|
|
177 |
# The configuration version will be stored in a ssconf file |
|
178 |
if 'config_version' in config_data['cluster']: |
|
179 |
del config_data['cluster']['config_version'] |
|
180 |
|
|
181 |
# Build content of new known_hosts file |
|
182 |
cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip() |
|
183 |
cluster_key = config_data['cluster']['rsahostkeypub'] |
|
184 |
known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key) |
|
164 | 185 |
|
165 |
if options.verbose: |
|
166 |
import pprint |
|
167 |
print "Before upgrade:" |
|
168 |
pprint.pprint(config) |
|
169 |
|
|
170 |
|
|
171 |
UpdateFromVersion2To3(config) |
|
186 |
else: |
|
187 |
logging.info("Found a Ganeti 2.0 configuration") |
|
172 | 188 |
|
173 |
if options.verbose: |
|
174 |
print "After upgrade:" |
|
175 |
pprint.pprint(config) |
|
176 |
|
|
189 |
if "config_version" in config_data["cluster"]: |
|
190 |
raise Error("Inconsistent configuration: found config_data in" |
|
191 |
" configuration file") |
|
177 | 192 |
|
178 |
WriteConfig(cfg_file, config)
|
|
193 |
known_hosts = None
|
|
179 | 194 |
|
180 |
print "The configuration file has been updated successfully. Please run" |
|
181 |
print " gnt-cluster copyfile %s" % cfg_file |
|
182 |
print "now." |
|
195 |
config_version_str = "%s\n" % constants.BuildVersion(2, 0, 0) |
|
196 |
try: |
|
197 |
logging.info("Writing configuration file") |
|
198 |
WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data)) |
|
199 |
|
|
200 |
logging.info("Writing configuration version %s", |
|
201 |
config_version_str.strip()) |
|
202 |
WriteFile(SSCONF_CONFIG_VERSION_PATH, config_version_str) |
|
203 |
|
|
204 |
if known_hosts is not None: |
|
205 |
logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip()) |
|
206 |
WriteFile(KNOWN_HOSTS_PATH, known_hosts) |
|
207 |
except: |
|
208 |
logging.critical("Writing configuration failed. It is proably in an" |
|
209 |
" inconsistent state and needs manual intervention.") |
|
210 |
raise |
|
183 | 211 |
|
184 | 212 |
|
185 | 213 |
if __name__ == "__main__": |
Also available in: Unified diff