Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ effc1b86

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