Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ bbc6620d

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