Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 65b526e7

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