Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ d8819d84

History | View | Annotate | Download (19.8 kB)

1 0006af7d Michael Hanselmann
#!/usr/bin/python
2 0006af7d Michael Hanselmann
#
3 0006af7d Michael Hanselmann
4 fdb85e3d Bernardo Dal Seno
# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5 0006af7d Michael Hanselmann
#
6 0006af7d Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 0006af7d Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 0006af7d Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 0006af7d Michael Hanselmann
# (at your option) any later version.
10 0006af7d Michael Hanselmann
#
11 0006af7d Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 0006af7d Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 0006af7d Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 0006af7d Michael Hanselmann
# General Public License for more details.
15 0006af7d Michael Hanselmann
#
16 0006af7d Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 0006af7d Michael Hanselmann
# along with this program; if not, write to the Free Software
18 0006af7d Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 0006af7d Michael Hanselmann
# 02110-1301, USA.
20 0006af7d Michael Hanselmann
21 0006af7d Michael Hanselmann
22 0006af7d Michael Hanselmann
"""Tool to upgrade the configuration file.
23 0006af7d Michael Hanselmann
24 a421fdeb Iustin Pop
This code handles only the types supported by simplejson. As an
25 a421fdeb Iustin Pop
example, 'set' is a 'list'.
26 0006af7d Michael Hanselmann
27 0006af7d Michael Hanselmann
"""
28 0006af7d Michael Hanselmann
29 0006af7d Michael Hanselmann
30 0006af7d Michael Hanselmann
import os
31 0006af7d Michael Hanselmann
import os.path
32 0006af7d Michael Hanselmann
import sys
33 0006af7d Michael Hanselmann
import optparse
34 eda37a5a Michael Hanselmann
import logging
35 7939f60c Michael Hanselmann
import time
36 7939f60c Michael Hanselmann
from cStringIO import StringIO
37 0006af7d Michael Hanselmann
38 95e4a814 Michael Hanselmann
from ganeti import constants
39 95e4a814 Michael Hanselmann
from ganeti import serializer
40 319856a9 Michael Hanselmann
from ganeti import utils
41 f97c7901 Michael Hanselmann
from ganeti import cli
42 a421fdeb Iustin Pop
from ganeti import bootstrap
43 ac4d25b6 Iustin Pop
from ganeti import config
44 011974df Michael Hanselmann
from ganeti import netutils
45 09bf5d24 Michael Hanselmann
from ganeti import pathutils
46 0006af7d Michael Hanselmann
47 0006af7d Michael Hanselmann
48 319856a9 Michael Hanselmann
options = None
49 319856a9 Michael Hanselmann
args = None
50 0006af7d Michael Hanselmann
51 6f285030 Iustin Pop
52 93fd9bb1 Iustin Pop
#: Target major version we will upgrade to
53 93fd9bb1 Iustin Pop
TARGET_MAJOR = 2
54 93fd9bb1 Iustin Pop
#: Target minor version we will upgrade to
55 1fe10404 Klaus Aehlig
TARGET_MINOR = 10
56 1709435e Bernardo Dal Seno
#: Target major version for downgrade
57 1709435e Bernardo Dal Seno
DOWNGRADE_MAJOR = 2
58 1709435e Bernardo Dal Seno
#: Target minor version for downgrade
59 1fe10404 Klaus Aehlig
DOWNGRADE_MINOR = 9
60 93fd9bb1 Iustin Pop
61 93fd9bb1 Iustin Pop
62 319856a9 Michael Hanselmann
class Error(Exception):
63 319856a9 Michael Hanselmann
  """Generic exception"""
64 319856a9 Michael Hanselmann
  pass
65 0006af7d Michael Hanselmann
66 0006af7d Michael Hanselmann
67 eda37a5a Michael Hanselmann
def SetupLogging():
68 eda37a5a Michael Hanselmann
  """Configures the logging module.
69 eda37a5a Michael Hanselmann
70 eda37a5a Michael Hanselmann
  """
71 eda37a5a Michael Hanselmann
  formatter = logging.Formatter("%(asctime)s: %(message)s")
72 eda37a5a Michael Hanselmann
73 eda37a5a Michael Hanselmann
  stderr_handler = logging.StreamHandler()
74 eda37a5a Michael Hanselmann
  stderr_handler.setFormatter(formatter)
75 eda37a5a Michael Hanselmann
  if options.debug:
76 eda37a5a Michael Hanselmann
    stderr_handler.setLevel(logging.NOTSET)
77 eda37a5a Michael Hanselmann
  elif options.verbose:
78 eda37a5a Michael Hanselmann
    stderr_handler.setLevel(logging.INFO)
79 eda37a5a Michael Hanselmann
  else:
80 011974df Michael Hanselmann
    stderr_handler.setLevel(logging.WARNING)
81 eda37a5a Michael Hanselmann
82 eda37a5a Michael Hanselmann
  root_logger = logging.getLogger("")
83 eda37a5a Michael Hanselmann
  root_logger.setLevel(logging.NOTSET)
84 eda37a5a Michael Hanselmann
  root_logger.addHandler(stderr_handler)
85 eda37a5a Michael Hanselmann
86 eda37a5a Michael Hanselmann
87 011974df Michael Hanselmann
def CheckHostname(path):
88 011974df Michael Hanselmann
  """Ensures hostname matches ssconf value.
89 011974df Michael Hanselmann
90 011974df Michael Hanselmann
  @param path: Path to ssconf file
91 011974df Michael Hanselmann
92 011974df Michael Hanselmann
  """
93 011974df Michael Hanselmann
  ssconf_master_node = utils.ReadOneLineFile(path)
94 011974df Michael Hanselmann
  hostname = netutils.GetHostname().name
95 011974df Michael Hanselmann
96 011974df Michael Hanselmann
  if ssconf_master_node == hostname:
97 011974df Michael Hanselmann
    return True
98 011974df Michael Hanselmann
99 011974df Michael Hanselmann
  logging.warning("Warning: ssconf says master node is '%s', but this"
100 011974df Michael Hanselmann
                  " machine's name is '%s'; this tool must be run on"
101 011974df Michael Hanselmann
                  " the master node", ssconf_master_node, hostname)
102 011974df Michael Hanselmann
  return False
103 011974df Michael Hanselmann
104 3c286190 Dimitris Aragiorgis
105 e94fc80c Bernardo Dal Seno
def _FillIPolicySpecs(default_ipolicy, ipolicy):
106 e94fc80c Bernardo Dal Seno
  if "minmax" in ipolicy:
107 41044e04 Bernardo Dal Seno
    for (key, spec) in ipolicy["minmax"][0].items():
108 41044e04 Bernardo Dal Seno
      for (par, val) in default_ipolicy["minmax"][0][key].items():
109 e94fc80c Bernardo Dal Seno
        if par not in spec:
110 e94fc80c Bernardo Dal Seno
          spec[par] = val
111 e94fc80c Bernardo Dal Seno
112 e94fc80c Bernardo Dal Seno
113 e94fc80c Bernardo Dal Seno
def UpgradeIPolicy(ipolicy, default_ipolicy, isgroup):
114 0b94cda8 Bernardo Dal Seno
  minmax_keys = ["min", "max"]
115 0b94cda8 Bernardo Dal Seno
  if any((k in ipolicy) for k in minmax_keys):
116 0b94cda8 Bernardo Dal Seno
    minmax = {}
117 0b94cda8 Bernardo Dal Seno
    for key in minmax_keys:
118 0b94cda8 Bernardo Dal Seno
      if key in ipolicy:
119 e94fc80c Bernardo Dal Seno
        if ipolicy[key]:
120 e94fc80c Bernardo Dal Seno
          minmax[key] = ipolicy[key]
121 0b94cda8 Bernardo Dal Seno
        del ipolicy[key]
122 e94fc80c Bernardo Dal Seno
    if minmax:
123 41044e04 Bernardo Dal Seno
      ipolicy["minmax"] = [minmax]
124 e94fc80c Bernardo Dal Seno
  if isgroup and "std" in ipolicy:
125 e94fc80c Bernardo Dal Seno
    del ipolicy["std"]
126 e94fc80c Bernardo Dal Seno
  _FillIPolicySpecs(default_ipolicy, ipolicy)
127 0b94cda8 Bernardo Dal Seno
128 0b94cda8 Bernardo Dal Seno
129 58bf877f Dimitris Aragiorgis
def UpgradeNetworks(config_data):
130 58bf877f Dimitris Aragiorgis
  networks = config_data.get("networks", None)
131 58bf877f Dimitris Aragiorgis
  if not networks:
132 58bf877f Dimitris Aragiorgis
    config_data["networks"] = {}
133 58bf877f Dimitris Aragiorgis
134 58bf877f Dimitris Aragiorgis
135 0b94cda8 Bernardo Dal Seno
def UpgradeCluster(config_data):
136 0b94cda8 Bernardo Dal Seno
  cluster = config_data.get("cluster", None)
137 0b94cda8 Bernardo Dal Seno
  if cluster is None:
138 0b94cda8 Bernardo Dal Seno
    raise Error("Cannot find cluster")
139 e94fc80c Bernardo Dal Seno
  ipolicy = cluster.setdefault("ipolicy", None)
140 0b94cda8 Bernardo Dal Seno
  if ipolicy:
141 e94fc80c Bernardo Dal Seno
    UpgradeIPolicy(ipolicy, constants.IPOLICY_DEFAULTS, False)
142 0b94cda8 Bernardo Dal Seno
143 0b94cda8 Bernardo Dal Seno
144 58bf877f Dimitris Aragiorgis
def UpgradeGroups(config_data):
145 e94fc80c Bernardo Dal Seno
  cl_ipolicy = config_data["cluster"].get("ipolicy")
146 58bf877f Dimitris Aragiorgis
  for group in config_data["nodegroups"].values():
147 58bf877f Dimitris Aragiorgis
    networks = group.get("networks", None)
148 58bf877f Dimitris Aragiorgis
    if not networks:
149 58bf877f Dimitris Aragiorgis
      group["networks"] = {}
150 0b94cda8 Bernardo Dal Seno
    ipolicy = group.get("ipolicy", None)
151 0b94cda8 Bernardo Dal Seno
    if ipolicy:
152 e94fc80c Bernardo Dal Seno
      if cl_ipolicy is None:
153 e94fc80c Bernardo Dal Seno
        raise Error("A group defines an instance policy but there is no"
154 e94fc80c Bernardo Dal Seno
                    " instance policy at cluster level")
155 e94fc80c Bernardo Dal Seno
      UpgradeIPolicy(ipolicy, cl_ipolicy, True)
156 58bf877f Dimitris Aragiorgis
157 011974df Michael Hanselmann
158 c69b147d Bernardo Dal Seno
def GetExclusiveStorageValue(config_data):
159 c69b147d Bernardo Dal Seno
  """Return a conservative value of the exclusive_storage flag.
160 c69b147d Bernardo Dal Seno
161 c69b147d Bernardo Dal Seno
  Return C{True} if the cluster or at least a nodegroup have the flag set.
162 c69b147d Bernardo Dal Seno
163 c69b147d Bernardo Dal Seno
  """
164 c69b147d Bernardo Dal Seno
  ret = False
165 c69b147d Bernardo Dal Seno
  cluster = config_data["cluster"]
166 c69b147d Bernardo Dal Seno
  ndparams = cluster.get("ndparams")
167 c69b147d Bernardo Dal Seno
  if ndparams is not None and ndparams.get("exclusive_storage"):
168 c69b147d Bernardo Dal Seno
    ret = True
169 c69b147d Bernardo Dal Seno
  for group in config_data["nodegroups"].values():
170 c69b147d Bernardo Dal Seno
    ndparams = group.get("ndparams")
171 c69b147d Bernardo Dal Seno
    if ndparams is not None and ndparams.get("exclusive_storage"):
172 c69b147d Bernardo Dal Seno
      ret = True
173 c69b147d Bernardo Dal Seno
  return ret
174 c69b147d Bernardo Dal Seno
175 c69b147d Bernardo Dal Seno
176 f032d55c Dimitris Aragiorgis
def UpgradeInstances(config_data):
177 f032d55c Dimitris Aragiorgis
  network2uuid = dict((n["name"], n["uuid"])
178 f032d55c Dimitris Aragiorgis
                      for n in config_data["networks"].values())
179 bb553e5a Bernardo Dal Seno
  if "instances" not in config_data:
180 bb553e5a Bernardo Dal Seno
    raise Error("Can't find the 'instances' key in the configuration!")
181 bb553e5a Bernardo Dal Seno
182 c69b147d Bernardo Dal Seno
  missing_spindles = False
183 bb553e5a Bernardo Dal Seno
  for instance, iobj in config_data["instances"].items():
184 bb553e5a Bernardo Dal Seno
    for nic in iobj["nics"]:
185 f032d55c Dimitris Aragiorgis
      name = nic.get("network", None)
186 f032d55c Dimitris Aragiorgis
      if name:
187 f032d55c Dimitris Aragiorgis
        uuid = network2uuid.get(name, None)
188 f032d55c Dimitris Aragiorgis
        if uuid:
189 f032d55c Dimitris Aragiorgis
          print("NIC with network name %s found."
190 f032d55c Dimitris Aragiorgis
                " Substituting with uuid %s." % (name, uuid))
191 f032d55c Dimitris Aragiorgis
          nic["network"] = uuid
192 f032d55c Dimitris Aragiorgis
193 bb553e5a Bernardo Dal Seno
    if "disks" not in iobj:
194 bb553e5a Bernardo Dal Seno
      raise Error("Instance '%s' doesn't have a disks entry?!" % instance)
195 bb553e5a Bernardo Dal Seno
    disks = iobj["disks"]
196 bb553e5a Bernardo Dal Seno
    for idx, dobj in enumerate(disks):
197 bb553e5a Bernardo Dal Seno
      expected = "disk/%s" % idx
198 bb553e5a Bernardo Dal Seno
      current = dobj.get("iv_name", "")
199 bb553e5a Bernardo Dal Seno
      if current != expected:
200 bb553e5a Bernardo Dal Seno
        logging.warning("Updating iv_name for instance %s/disk %s"
201 bb553e5a Bernardo Dal Seno
                        " from '%s' to '%s'",
202 bb553e5a Bernardo Dal Seno
                        instance, idx, current, expected)
203 bb553e5a Bernardo Dal Seno
        dobj["iv_name"] = expected
204 c69b147d Bernardo Dal Seno
      if not "spindles" in dobj:
205 c69b147d Bernardo Dal Seno
        missing_spindles = True
206 c69b147d Bernardo Dal Seno
207 c69b147d Bernardo Dal Seno
  if GetExclusiveStorageValue(config_data) and missing_spindles:
208 c69b147d Bernardo Dal Seno
    # We cannot be sure that the instances that are missing spindles have
209 c69b147d Bernardo Dal Seno
    # exclusive storage enabled (the check would be more complicated), so we
210 c69b147d Bernardo Dal Seno
    # give a noncommittal message
211 c69b147d Bernardo Dal Seno
    logging.warning("Some instance disks could be needing to update the"
212 c69b147d Bernardo Dal Seno
                    " spindles parameter; you can check by running"
213 c69b147d Bernardo Dal Seno
                    " 'gnt-cluster verify', and fix any problem with"
214 c69b147d Bernardo Dal Seno
                    " 'gnt-cluster repair-disk-sizes'")
215 bb553e5a Bernardo Dal Seno
216 bb553e5a Bernardo Dal Seno
217 bb553e5a Bernardo Dal Seno
def UpgradeRapiUsers():
218 bb553e5a Bernardo Dal Seno
  if (os.path.isfile(options.RAPI_USERS_FILE_PRE24) and
219 bb553e5a Bernardo Dal Seno
      not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
220 bb553e5a Bernardo Dal Seno
    if os.path.exists(options.RAPI_USERS_FILE):
221 bb553e5a Bernardo Dal Seno
      raise Error("Found pre-2.4 RAPI users file at %s, but another file"
222 bb553e5a Bernardo Dal Seno
                  " already exists at %s" %
223 bb553e5a Bernardo Dal Seno
                  (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
224 bb553e5a Bernardo Dal Seno
    logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
225 bb553e5a Bernardo Dal Seno
                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
226 bb553e5a Bernardo Dal Seno
    if not options.dry_run:
227 bb553e5a Bernardo Dal Seno
      utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
228 bb553e5a Bernardo Dal Seno
                       mkdir=True, mkdir_mode=0750)
229 bb553e5a Bernardo Dal Seno
230 bb553e5a Bernardo Dal Seno
  # Create a symlink for RAPI users file
231 bb553e5a Bernardo Dal Seno
  if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
232 bb553e5a Bernardo Dal Seno
           os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
233 bb553e5a Bernardo Dal Seno
      os.path.isfile(options.RAPI_USERS_FILE)):
234 bb553e5a Bernardo Dal Seno
    logging.info("Creating symlink from %s to %s",
235 bb553e5a Bernardo Dal Seno
                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
236 bb553e5a Bernardo Dal Seno
    if not options.dry_run:
237 bb553e5a Bernardo Dal Seno
      os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
238 bb553e5a Bernardo Dal Seno
239 bb553e5a Bernardo Dal Seno
240 bb553e5a Bernardo Dal Seno
def UpgradeWatcher():
241 bb553e5a Bernardo Dal Seno
  # Remove old watcher state file if it exists
242 bb553e5a Bernardo Dal Seno
  if os.path.exists(options.WATCHER_STATEFILE):
243 bb553e5a Bernardo Dal Seno
    logging.info("Removing watcher state file %s", options.WATCHER_STATEFILE)
244 bb553e5a Bernardo Dal Seno
    if not options.dry_run:
245 bb553e5a Bernardo Dal Seno
      utils.RemoveFile(options.WATCHER_STATEFILE)
246 bb553e5a Bernardo Dal Seno
247 bb553e5a Bernardo Dal Seno
248 bb553e5a Bernardo Dal Seno
def UpgradeFileStoragePaths(config_data):
249 bb553e5a Bernardo Dal Seno
  # Write file storage paths
250 bb553e5a Bernardo Dal Seno
  if not os.path.exists(options.FILE_STORAGE_PATHS_FILE):
251 bb553e5a Bernardo Dal Seno
    cluster = config_data["cluster"]
252 bb553e5a Bernardo Dal Seno
    file_storage_dir = cluster.get("file_storage_dir")
253 bb553e5a Bernardo Dal Seno
    shared_file_storage_dir = cluster.get("shared_file_storage_dir")
254 bb553e5a Bernardo Dal Seno
    del cluster
255 bb553e5a Bernardo Dal Seno
256 bb553e5a Bernardo Dal Seno
    logging.info("Ganeti 2.7 and later only allow whitelisted directories"
257 bb553e5a Bernardo Dal Seno
                 " for file storage; writing existing configuration values"
258 bb553e5a Bernardo Dal Seno
                 " into '%s'",
259 bb553e5a Bernardo Dal Seno
                 options.FILE_STORAGE_PATHS_FILE)
260 bb553e5a Bernardo Dal Seno
261 bb553e5a Bernardo Dal Seno
    if file_storage_dir:
262 bb553e5a Bernardo Dal Seno
      logging.info("File storage directory: %s", file_storage_dir)
263 bb553e5a Bernardo Dal Seno
    if shared_file_storage_dir:
264 bb553e5a Bernardo Dal Seno
      logging.info("Shared file storage directory: %s",
265 bb553e5a Bernardo Dal Seno
                   shared_file_storage_dir)
266 bb553e5a Bernardo Dal Seno
267 bb553e5a Bernardo Dal Seno
    buf = StringIO()
268 bb553e5a Bernardo Dal Seno
    buf.write("# List automatically generated from configuration by\n")
269 bb553e5a Bernardo Dal Seno
    buf.write("# cfgupgrade at %s\n" % time.asctime())
270 bb553e5a Bernardo Dal Seno
    if file_storage_dir:
271 bb553e5a Bernardo Dal Seno
      buf.write("%s\n" % file_storage_dir)
272 bb553e5a Bernardo Dal Seno
    if shared_file_storage_dir:
273 bb553e5a Bernardo Dal Seno
      buf.write("%s\n" % shared_file_storage_dir)
274 bb553e5a Bernardo Dal Seno
    utils.WriteFile(file_name=options.FILE_STORAGE_PATHS_FILE,
275 bb553e5a Bernardo Dal Seno
                    data=buf.getvalue(),
276 bb553e5a Bernardo Dal Seno
                    mode=0600,
277 bb553e5a Bernardo Dal Seno
                    dry_run=options.dry_run,
278 bb553e5a Bernardo Dal Seno
                    backup=True)
279 bb553e5a Bernardo Dal Seno
280 bb553e5a Bernardo Dal Seno
281 b555101c Thomas Thrainer
def GetNewNodeIndex(nodes_by_old_key, old_key, new_key_field):
282 b555101c Thomas Thrainer
  if old_key not in nodes_by_old_key:
283 b555101c Thomas Thrainer
    logging.warning("Can't find node '%s' in configuration, assuming that it's"
284 b555101c Thomas Thrainer
                    " already up-to-date", old_key)
285 b555101c Thomas Thrainer
    return old_key
286 b555101c Thomas Thrainer
  return nodes_by_old_key[old_key][new_key_field]
287 b555101c Thomas Thrainer
288 b555101c Thomas Thrainer
289 b555101c Thomas Thrainer
def ChangeNodeIndices(config_data, old_key_field, new_key_field):
290 b555101c Thomas Thrainer
  def ChangeDiskNodeIndices(disk):
291 b555101c Thomas Thrainer
    if disk["dev_type"] in constants.LDS_DRBD:
292 b555101c Thomas Thrainer
      for i in range(0, 2):
293 b555101c Thomas Thrainer
        disk["logical_id"][i] = GetNewNodeIndex(nodes_by_old_key,
294 b555101c Thomas Thrainer
                                                disk["logical_id"][i],
295 b555101c Thomas Thrainer
                                                new_key_field)
296 b555101c Thomas Thrainer
    if "children" in disk:
297 b555101c Thomas Thrainer
      for child in disk["children"]:
298 b555101c Thomas Thrainer
        ChangeDiskNodeIndices(child)
299 b555101c Thomas Thrainer
300 b555101c Thomas Thrainer
  nodes_by_old_key = {}
301 b555101c Thomas Thrainer
  nodes_by_new_key = {}
302 b555101c Thomas Thrainer
  for (_, node) in config_data["nodes"].items():
303 b555101c Thomas Thrainer
    nodes_by_old_key[node[old_key_field]] = node
304 b555101c Thomas Thrainer
    nodes_by_new_key[node[new_key_field]] = node
305 b555101c Thomas Thrainer
306 b555101c Thomas Thrainer
  config_data["nodes"] = nodes_by_new_key
307 b555101c Thomas Thrainer
308 b555101c Thomas Thrainer
  cluster = config_data["cluster"]
309 b555101c Thomas Thrainer
  cluster["master_node"] = GetNewNodeIndex(nodes_by_old_key,
310 b555101c Thomas Thrainer
                                           cluster["master_node"],
311 b555101c Thomas Thrainer
                                           new_key_field)
312 b555101c Thomas Thrainer
313 b555101c Thomas Thrainer
  for inst in config_data["instances"].values():
314 b555101c Thomas Thrainer
    inst["primary_node"] = GetNewNodeIndex(nodes_by_old_key,
315 b555101c Thomas Thrainer
                                           inst["primary_node"],
316 b555101c Thomas Thrainer
                                           new_key_field)
317 b555101c Thomas Thrainer
    for disk in inst["disks"]:
318 b555101c Thomas Thrainer
      ChangeDiskNodeIndices(disk)
319 b555101c Thomas Thrainer
320 b555101c Thomas Thrainer
321 4d33e134 Thomas Thrainer
def ChangeInstanceIndices(config_data, old_key_field, new_key_field):
322 4d33e134 Thomas Thrainer
  insts_by_old_key = {}
323 4d33e134 Thomas Thrainer
  insts_by_new_key = {}
324 4d33e134 Thomas Thrainer
  for (_, inst) in config_data["instances"].items():
325 4d33e134 Thomas Thrainer
    insts_by_old_key[inst[old_key_field]] = inst
326 4d33e134 Thomas Thrainer
    insts_by_new_key[inst[new_key_field]] = inst
327 4d33e134 Thomas Thrainer
328 4d33e134 Thomas Thrainer
  config_data["instances"] = insts_by_new_key
329 4d33e134 Thomas Thrainer
330 4d33e134 Thomas Thrainer
331 b555101c Thomas Thrainer
def UpgradeNodeIndices(config_data):
332 b555101c Thomas Thrainer
  ChangeNodeIndices(config_data, "name", "uuid")
333 b555101c Thomas Thrainer
334 b555101c Thomas Thrainer
335 4d33e134 Thomas Thrainer
def UpgradeInstanceIndices(config_data):
336 4d33e134 Thomas Thrainer
  ChangeInstanceIndices(config_data, "name", "uuid")
337 4d33e134 Thomas Thrainer
338 4d33e134 Thomas Thrainer
339 bb553e5a Bernardo Dal Seno
def UpgradeAll(config_data):
340 bb553e5a Bernardo Dal Seno
  config_data["version"] = constants.BuildVersion(TARGET_MAJOR,
341 bb553e5a Bernardo Dal Seno
                                                  TARGET_MINOR, 0)
342 bb553e5a Bernardo Dal Seno
  UpgradeRapiUsers()
343 bb553e5a Bernardo Dal Seno
  UpgradeWatcher()
344 bb553e5a Bernardo Dal Seno
  UpgradeFileStoragePaths(config_data)
345 bb553e5a Bernardo Dal Seno
  UpgradeNetworks(config_data)
346 0b94cda8 Bernardo Dal Seno
  UpgradeCluster(config_data)
347 bb553e5a Bernardo Dal Seno
  UpgradeGroups(config_data)
348 bb553e5a Bernardo Dal Seno
  UpgradeInstances(config_data)
349 b555101c Thomas Thrainer
  UpgradeNodeIndices(config_data)
350 4d33e134 Thomas Thrainer
  UpgradeInstanceIndices(config_data)
351 bb553e5a Bernardo Dal Seno
352 f032d55c Dimitris Aragiorgis
353 d8819d84 Sebastian Gebhard
def DowngradeInstances(config_data):
354 d8819d84 Sebastian Gebhard
  if "instances" not in config_data:
355 d8819d84 Sebastian Gebhard
    raise Error("Cannot find the 'instances' key in the configuration!")
356 d8819d84 Sebastian Gebhard
  for (iname, iobj) in config_data["instances"].items():
357 d8819d84 Sebastian Gebhard
    DowngradeNicParamsVLAN(iobj["nics"], iname)
358 d8819d84 Sebastian Gebhard
359 d8819d84 Sebastian Gebhard
360 d8819d84 Sebastian Gebhard
def DowngradeNicParamsVLAN(nics, owner):
361 d8819d84 Sebastian Gebhard
  for nic in nics:
362 d8819d84 Sebastian Gebhard
    vlan = nic["nicparams"].get("vlan", None)
363 d8819d84 Sebastian Gebhard
    if vlan:
364 d8819d84 Sebastian Gebhard
      logging.warning("Instance with name %s found. Removing VLAN information"
365 d8819d84 Sebastian Gebhard
                      " %s.", owner, vlan)
366 d8819d84 Sebastian Gebhard
      del nic["nicparams"]["vlan"]
367 d8819d84 Sebastian Gebhard
368 d8819d84 Sebastian Gebhard
369 1709435e Bernardo Dal Seno
def DowngradeAll(config_data):
370 1709435e Bernardo Dal Seno
  # Any code specific to a particular version should be labeled that way, so
371 1709435e Bernardo Dal Seno
  # it can be removed when updating to the next version.
372 77018a43 Michele Tartara
  config_data["version"] = constants.BuildVersion(DOWNGRADE_MAJOR,
373 77018a43 Michele Tartara
                                                  DOWNGRADE_MINOR, 0)
374 d8819d84 Sebastian Gebhard
  DowngradeInstances(config_data)
375 1709435e Bernardo Dal Seno
376 1709435e Bernardo Dal Seno
377 6d691282 Michael Hanselmann
def main():
378 6d691282 Michael Hanselmann
  """Main program.
379 6d691282 Michael Hanselmann
380 6d691282 Michael Hanselmann
  """
381 b459a848 Andrea Spadaccini
  global options, args # pylint: disable=W0603
382 6d691282 Michael Hanselmann
383 0006af7d Michael Hanselmann
  # Option parsing
384 95e4a814 Michael Hanselmann
  parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
385 3ccb3a64 Michael Hanselmann
  parser.add_option("--dry-run", dest="dry_run",
386 60edf71e Michael Hanselmann
                    action="store_true",
387 f4bc1f2c Michael Hanselmann
                    help="Try to do the conversion, but don't write"
388 f4bc1f2c Michael Hanselmann
                         " output file")
389 f97c7901 Michael Hanselmann
  parser.add_option(cli.FORCE_OPT)
390 eda37a5a Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
391 9cdb9578 Iustin Pop
  parser.add_option(cli.VERBOSE_OPT)
392 011974df Michael Hanselmann
  parser.add_option("--ignore-hostname", dest="ignore_hostname",
393 011974df Michael Hanselmann
                    action="store_true", default=False,
394 011974df Michael Hanselmann
                    help="Don't abort if hostname doesn't match")
395 3ccb3a64 Michael Hanselmann
  parser.add_option("--path", help="Convert configuration in this"
396 09bf5d24 Michael Hanselmann
                    " directory instead of '%s'" % pathutils.DATA_DIR,
397 09bf5d24 Michael Hanselmann
                    default=pathutils.DATA_DIR, dest="data_dir")
398 7939f60c Michael Hanselmann
  parser.add_option("--confdir",
399 7939f60c Michael Hanselmann
                    help=("Use this directory instead of '%s'" %
400 7939f60c Michael Hanselmann
                          pathutils.CONF_DIR),
401 7939f60c Michael Hanselmann
                    default=pathutils.CONF_DIR, dest="conf_dir")
402 02e1292d Michael Hanselmann
  parser.add_option("--no-verify",
403 02e1292d Michael Hanselmann
                    help="Do not verify configuration after upgrade",
404 02e1292d Michael Hanselmann
                    action="store_true", dest="no_verify", default=False)
405 1709435e Bernardo Dal Seno
  parser.add_option("--downgrade",
406 1709435e Bernardo Dal Seno
                    help="Downgrade to the previous stable version",
407 1709435e Bernardo Dal Seno
                    action="store_true", dest="downgrade", default=False)
408 0006af7d Michael Hanselmann
  (options, args) = parser.parse_args()
409 0006af7d Michael Hanselmann
410 ac4d25b6 Iustin Pop
  # We need to keep filenames locally because they might be renamed between
411 ac4d25b6 Iustin Pop
  # versions.
412 0cddd44d Iustin Pop
  options.data_dir = os.path.abspath(options.data_dir)
413 ac4d25b6 Iustin Pop
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
414 ac4d25b6 Iustin Pop
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
415 ac4d25b6 Iustin Pop
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
416 ac4d25b6 Iustin Pop
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
417 b6267745 Andrea Spadaccini
  options.SPICE_CERT_FILE = options.data_dir + "/spice.pem"
418 b6267745 Andrea Spadaccini
  options.SPICE_CACERT_FILE = options.data_dir + "/spice-ca.pem"
419 fdd9ac5b Michael Hanselmann
  options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
420 fdd9ac5b Michael Hanselmann
  options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
421 6b7d5878 Michael Hanselmann
  options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
422 fc0726b9 Michael Hanselmann
  options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
423 011974df Michael Hanselmann
  options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
424 a292020f Michael Hanselmann
  options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
425 7939f60c Michael Hanselmann
  options.FILE_STORAGE_PATHS_FILE = options.conf_dir + "/file-storage-paths"
426 ac4d25b6 Iustin Pop
427 eda37a5a Michael Hanselmann
  SetupLogging()
428 eda37a5a Michael Hanselmann
429 0006af7d Michael Hanselmann
  # Option checking
430 0006af7d Michael Hanselmann
  if args:
431 95e4a814 Michael Hanselmann
    raise Error("No arguments expected")
432 1709435e Bernardo Dal Seno
  if options.downgrade and not options.no_verify:
433 1709435e Bernardo Dal Seno
    options.no_verify = True
434 0006af7d Michael Hanselmann
435 011974df Michael Hanselmann
  # Check master name
436 011974df Michael Hanselmann
  if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
437 011974df Michael Hanselmann
    logging.error("Aborting due to hostname mismatch")
438 011974df Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
439 011974df Michael Hanselmann
440 319856a9 Michael Hanselmann
  if not options.force:
441 1709435e Bernardo Dal Seno
    if options.downgrade:
442 1709435e Bernardo Dal Seno
      usertext = ("The configuration is going to be DOWNGRADED to version %s.%s"
443 1709435e Bernardo Dal Seno
                  " Some configuration data might be removed if they don't fit"
444 1709435e Bernardo Dal Seno
                  " in the old format. Please make sure you have read the"
445 1709435e Bernardo Dal Seno
                  " upgrade notes (available in the UPGRADE file and included"
446 1709435e Bernardo Dal Seno
                  " in other documentation formats) to understand what they"
447 1709435e Bernardo Dal Seno
                  " are. Continue with *DOWNGRADING* the configuration?" %
448 1709435e Bernardo Dal Seno
                  (DOWNGRADE_MAJOR, DOWNGRADE_MINOR))
449 1709435e Bernardo Dal Seno
    else:
450 1709435e Bernardo Dal Seno
      usertext = ("Please make sure you have read the upgrade notes for"
451 1709435e Bernardo Dal Seno
                  " Ganeti %s (available in the UPGRADE file and included"
452 1709435e Bernardo Dal Seno
                  " in other documentation formats). Continue with upgrading"
453 1709435e Bernardo Dal Seno
                  " configuration?" % constants.RELEASE_VERSION)
454 f97c7901 Michael Hanselmann
    if not cli.AskUser(usertext):
455 a9221f09 Michael Hanselmann
      sys.exit(constants.EXIT_FAILURE)
456 319856a9 Michael Hanselmann
457 95e4a814 Michael Hanselmann
  # Check whether it's a Ganeti configuration directory
458 ac4d25b6 Iustin Pop
  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
459 30acff6c Michael Hanselmann
          os.path.isfile(options.SERVER_PEM_PATH) and
460 ac4d25b6 Iustin Pop
          os.path.isfile(options.KNOWN_HOSTS_PATH)):
461 a9221f09 Michael Hanselmann
    raise Error(("%s does not seem to be a Ganeti configuration"
462 ac4d25b6 Iustin Pop
                 " directory") % options.data_dir)
463 95e4a814 Michael Hanselmann
464 7939f60c Michael Hanselmann
  if not os.path.isdir(options.conf_dir):
465 7939f60c Michael Hanselmann
    raise Error("Not a directory: %s" % options.conf_dir)
466 7939f60c Michael Hanselmann
467 11c31f5c Michael Hanselmann
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
468 a421fdeb Iustin Pop
469 11c31f5c Michael Hanselmann
  try:
470 11c31f5c Michael Hanselmann
    config_version = config_data["version"]
471 11c31f5c Michael Hanselmann
  except KeyError:
472 11c31f5c Michael Hanselmann
    raise Error("Unable to determine configuration version")
473 0006af7d Michael Hanselmann
474 11c31f5c Michael Hanselmann
  (config_major, config_minor, config_revision) = \
475 11c31f5c Michael Hanselmann
    constants.SplitVersion(config_version)
476 319856a9 Michael Hanselmann
477 11c31f5c Michael Hanselmann
  logging.info("Found configuration version %s (%d.%d.%d)",
478 11c31f5c Michael Hanselmann
               config_version, config_major, config_minor, config_revision)
479 319856a9 Michael Hanselmann
480 11c31f5c Michael Hanselmann
  if "config_version" in config_data["cluster"]:
481 11c31f5c Michael Hanselmann
    raise Error("Inconsistent configuration: found config_version in"
482 11c31f5c Michael Hanselmann
                " configuration file")
483 95e4a814 Michael Hanselmann
484 1709435e Bernardo Dal Seno
  # Downgrade to the previous stable version
485 1709435e Bernardo Dal Seno
  if options.downgrade:
486 f2e4363c Michele Tartara
    if not ((config_major == TARGET_MAJOR and config_minor == TARGET_MINOR) or
487 f2e4363c Michele Tartara
            (config_major == DOWNGRADE_MAJOR and
488 f2e4363c Michele Tartara
             config_minor == DOWNGRADE_MINOR)):
489 1709435e Bernardo Dal Seno
      raise Error("Downgrade supported only from the latest version (%s.%s),"
490 1709435e Bernardo Dal Seno
                  " found %s (%s.%s.%s) instead" %
491 1709435e Bernardo Dal Seno
                  (TARGET_MAJOR, TARGET_MINOR, config_version, config_major,
492 1709435e Bernardo Dal Seno
                   config_minor, config_revision))
493 1709435e Bernardo Dal Seno
    DowngradeAll(config_data)
494 1709435e Bernardo Dal Seno
495 ea2ee4b0 Michele Tartara
  # Upgrade from 2.{0..7} to 2.9
496 ea2ee4b0 Michele Tartara
  elif config_major == 2 and config_minor in range(0, 10):
497 aeb0c953 Michael Hanselmann
    if config_revision != 0:
498 a9221f09 Michael Hanselmann
      logging.warning("Config revision is %s, not 0", config_revision)
499 bb553e5a Bernardo Dal Seno
    UpgradeAll(config_data)
500 904910c4 Iustin Pop
501 93fd9bb1 Iustin Pop
  elif config_major == TARGET_MAJOR and config_minor == TARGET_MINOR:
502 a9221f09 Michael Hanselmann
    logging.info("No changes necessary")
503 a9221f09 Michael Hanselmann
504 a9221f09 Michael Hanselmann
  else:
505 a9221f09 Michael Hanselmann
    raise Error("Configuration version %d.%d.%d not supported by this tool" %
506 a9221f09 Michael Hanselmann
                (config_major, config_minor, config_revision))
507 aeb0c953 Michael Hanselmann
508 11c31f5c Michael Hanselmann
  try:
509 11c31f5c Michael Hanselmann
    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
510 11c31f5c Michael Hanselmann
    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
511 11c31f5c Michael Hanselmann
                    data=serializer.DumpJson(config_data),
512 11c31f5c Michael Hanselmann
                    mode=0600,
513 11c31f5c Michael Hanselmann
                    dry_run=options.dry_run,
514 11c31f5c Michael Hanselmann
                    backup=True)
515 a421fdeb Iustin Pop
516 a421fdeb Iustin Pop
    if not options.dry_run:
517 5ae4945a Iustin Pop
      bootstrap.GenerateClusterCrypto(
518 5ae4945a Iustin Pop
        False, False, False, False, False,
519 5ae4945a Iustin Pop
        nodecert_file=options.SERVER_PEM_PATH,
520 5ae4945a Iustin Pop
        rapicert_file=options.RAPI_CERT_FILE,
521 5ae4945a Iustin Pop
        spicecert_file=options.SPICE_CERT_FILE,
522 5ae4945a Iustin Pop
        spicecacert_file=options.SPICE_CACERT_FILE,
523 5ae4945a Iustin Pop
        hmackey_file=options.CONFD_HMAC_KEY,
524 5ae4945a Iustin Pop
        cds_file=options.CDS_FILE)
525 aeb0c953 Michael Hanselmann
526 a9221f09 Michael Hanselmann
  except Exception:
527 11c31f5c Michael Hanselmann
    logging.critical("Writing configuration failed. It is probably in an"
528 95e4a814 Michael Hanselmann
                     " inconsistent state and needs manual intervention.")
529 95e4a814 Michael Hanselmann
    raise
530 0006af7d Michael Hanselmann
531 ac4d25b6 Iustin Pop
  # test loading the config file
532 fdb85e3d Bernardo Dal Seno
  all_ok = True
533 02e1292d Michael Hanselmann
  if not (options.dry_run or options.no_verify):
534 ac4d25b6 Iustin Pop
    logging.info("Testing the new config file...")
535 ac4d25b6 Iustin Pop
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
536 959b6fe5 Apollon Oikonomopoulos
                              accept_foreign=options.ignore_hostname,
537 ac4d25b6 Iustin Pop
                              offline=True)
538 ac4d25b6 Iustin Pop
    # if we reached this, it's all fine
539 ac4d25b6 Iustin Pop
    vrfy = cfg.VerifyConfig()
540 ac4d25b6 Iustin Pop
    if vrfy:
541 ac4d25b6 Iustin Pop
      logging.error("Errors after conversion:")
542 ac4d25b6 Iustin Pop
      for item in vrfy:
543 07b8a2b5 Iustin Pop
        logging.error(" - %s", item)
544 fdb85e3d Bernardo Dal Seno
      all_ok = False
545 fdb85e3d Bernardo Dal Seno
    else:
546 fdb85e3d Bernardo Dal Seno
      logging.info("File loaded successfully after upgrading")
547 ac4d25b6 Iustin Pop
    del cfg
548 ac4d25b6 Iustin Pop
549 1709435e Bernardo Dal Seno
  if options.downgrade:
550 1709435e Bernardo Dal Seno
    action = "downgraded"
551 1709435e Bernardo Dal Seno
    out_ver = "%s.%s" % (DOWNGRADE_MAJOR, DOWNGRADE_MINOR)
552 1709435e Bernardo Dal Seno
  else:
553 1709435e Bernardo Dal Seno
    action = "upgraded"
554 1709435e Bernardo Dal Seno
    out_ver = constants.RELEASE_VERSION
555 fdb85e3d Bernardo Dal Seno
  if all_ok:
556 1709435e Bernardo Dal Seno
    cli.ToStderr("Configuration successfully %s to version %s.",
557 1709435e Bernardo Dal Seno
                 action, out_ver)
558 fdb85e3d Bernardo Dal Seno
  else:
559 1709435e Bernardo Dal Seno
    cli.ToStderr("Configuration %s to version %s, but there are errors."
560 1709435e Bernardo Dal Seno
                 "\nPlease review the file.", action, out_ver)
561 66a66fa7 Michael Hanselmann
562 6d691282 Michael Hanselmann
563 6d691282 Michael Hanselmann
if __name__ == "__main__":
564 6d691282 Michael Hanselmann
  main()