Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 11e90588

History | View | Annotate | Download (19.8 kB)

1 0006af7d Michael Hanselmann
#!/usr/bin/python
2 0006af7d Michael Hanselmann
#
3 0006af7d Michael Hanselmann
4 fdb85e3d Bernardo Dal Seno
# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5 0006af7d Michael Hanselmann
#
6 0006af7d Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 0006af7d Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 0006af7d Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 0006af7d Michael Hanselmann
# (at your option) any later version.
10 0006af7d Michael Hanselmann
#
11 0006af7d Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 0006af7d Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 0006af7d Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 0006af7d Michael Hanselmann
# General Public License for more details.
15 0006af7d Michael Hanselmann
#
16 0006af7d Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 0006af7d Michael Hanselmann
# along with this program; if not, write to the Free Software
18 0006af7d Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 0006af7d Michael Hanselmann
# 02110-1301, USA.
20 0006af7d Michael Hanselmann
21 0006af7d Michael Hanselmann
22 0006af7d Michael Hanselmann
"""Tool to upgrade the configuration file.
23 0006af7d Michael Hanselmann
24 a421fdeb Iustin Pop
This code handles only the types supported by simplejson. As an
25 a421fdeb Iustin Pop
example, 'set' is a 'list'.
26 0006af7d Michael Hanselmann
27 0006af7d Michael Hanselmann
"""
28 0006af7d Michael Hanselmann
29 0006af7d Michael Hanselmann
30 0006af7d Michael Hanselmann
import os
31 0006af7d Michael Hanselmann
import os.path
32 0006af7d Michael Hanselmann
import sys
33 0006af7d Michael Hanselmann
import optparse
34 eda37a5a Michael Hanselmann
import logging
35 7939f60c Michael Hanselmann
import time
36 7939f60c Michael Hanselmann
from cStringIO import StringIO
37 11e90588 Dimitris Aragiorgis
from bitarray import bitarray
38 11e90588 Dimitris Aragiorgis
from base64 import b64encode, b64decode
39 0006af7d Michael Hanselmann
40 95e4a814 Michael Hanselmann
from ganeti import constants
41 95e4a814 Michael Hanselmann
from ganeti import serializer
42 319856a9 Michael Hanselmann
from ganeti import utils
43 f97c7901 Michael Hanselmann
from ganeti import cli
44 a421fdeb Iustin Pop
from ganeti import bootstrap
45 ac4d25b6 Iustin Pop
from ganeti import config
46 011974df Michael Hanselmann
from ganeti import netutils
47 09bf5d24 Michael Hanselmann
from ganeti import pathutils
48 0006af7d Michael Hanselmann
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 33bff17b Michele Tartara
TARGET_MINOR = 8
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 1709435e Bernardo Dal Seno
DOWNGRADE_MINOR = 7
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 11e90588 Dimitris Aragiorgis
# pylint: disable=E1101
132 58bf877f Dimitris Aragiorgis
def UpgradeNetworks(config_data):
133 11e90588 Dimitris Aragiorgis
  networks = config_data.get("networks", {})
134 58bf877f Dimitris Aragiorgis
  if not networks:
135 58bf877f Dimitris Aragiorgis
    config_data["networks"] = {}
136 11e90588 Dimitris Aragiorgis
  for nobj in networks.values():
137 11e90588 Dimitris Aragiorgis
    for key in ("reservations", "ext_reservations"):
138 11e90588 Dimitris Aragiorgis
      r = nobj[key]
139 11e90588 Dimitris Aragiorgis
      if options.tob64:
140 11e90588 Dimitris Aragiorgis
        try:
141 11e90588 Dimitris Aragiorgis
          b = bitarray(r)
142 11e90588 Dimitris Aragiorgis
          nobj[key] = b64encode(b.tobytes())
143 11e90588 Dimitris Aragiorgis
        except ValueError:
144 11e90588 Dimitris Aragiorgis
          print("No 01 network found! Probably already in base64.")
145 11e90588 Dimitris Aragiorgis
      if options.to01:
146 11e90588 Dimitris Aragiorgis
        try:
147 11e90588 Dimitris Aragiorgis
          b = bitarray(r)
148 11e90588 Dimitris Aragiorgis
          print("01 network found! Do nothing.")
149 11e90588 Dimitris Aragiorgis
        except ValueError:
150 11e90588 Dimitris Aragiorgis
          b = bitarray()
151 11e90588 Dimitris Aragiorgis
          b.frombytes(b64decode(r))
152 11e90588 Dimitris Aragiorgis
          nobj[key] = b.to01()
153 11e90588 Dimitris Aragiorgis
      print("%s: %s -> %s" % (nobj["name"], r, nobj[key]))
154 58bf877f Dimitris Aragiorgis
155 58bf877f Dimitris Aragiorgis
156 0b94cda8 Bernardo Dal Seno
def UpgradeCluster(config_data):
157 0b94cda8 Bernardo Dal Seno
  cluster = config_data.get("cluster", None)
158 0b94cda8 Bernardo Dal Seno
  if cluster is None:
159 0b94cda8 Bernardo Dal Seno
    raise Error("Cannot find cluster")
160 e94fc80c Bernardo Dal Seno
  ipolicy = cluster.setdefault("ipolicy", None)
161 0b94cda8 Bernardo Dal Seno
  if ipolicy:
162 e94fc80c Bernardo Dal Seno
    UpgradeIPolicy(ipolicy, constants.IPOLICY_DEFAULTS, False)
163 0b94cda8 Bernardo Dal Seno
164 0b94cda8 Bernardo Dal Seno
165 58bf877f Dimitris Aragiorgis
def UpgradeGroups(config_data):
166 e94fc80c Bernardo Dal Seno
  cl_ipolicy = config_data["cluster"].get("ipolicy")
167 58bf877f Dimitris Aragiorgis
  for group in config_data["nodegroups"].values():
168 58bf877f Dimitris Aragiorgis
    networks = group.get("networks", None)
169 58bf877f Dimitris Aragiorgis
    if not networks:
170 58bf877f Dimitris Aragiorgis
      group["networks"] = {}
171 0b94cda8 Bernardo Dal Seno
    ipolicy = group.get("ipolicy", None)
172 0b94cda8 Bernardo Dal Seno
    if ipolicy:
173 e94fc80c Bernardo Dal Seno
      if cl_ipolicy is None:
174 e94fc80c Bernardo Dal Seno
        raise Error("A group defines an instance policy but there is no"
175 e94fc80c Bernardo Dal Seno
                    " instance policy at cluster level")
176 e94fc80c Bernardo Dal Seno
      UpgradeIPolicy(ipolicy, cl_ipolicy, True)
177 58bf877f Dimitris Aragiorgis
178 011974df Michael Hanselmann
179 f032d55c Dimitris Aragiorgis
def UpgradeInstances(config_data):
180 f032d55c Dimitris Aragiorgis
  network2uuid = dict((n["name"], n["uuid"])
181 f032d55c Dimitris Aragiorgis
                      for n in config_data["networks"].values())
182 bb553e5a Bernardo Dal Seno
  if "instances" not in config_data:
183 bb553e5a Bernardo Dal Seno
    raise Error("Can't find the 'instances' key in the configuration!")
184 bb553e5a Bernardo Dal Seno
185 bb553e5a Bernardo Dal Seno
  for instance, iobj in config_data["instances"].items():
186 bb553e5a Bernardo Dal Seno
    for nic in iobj["nics"]:
187 f032d55c Dimitris Aragiorgis
      name = nic.get("network", None)
188 f032d55c Dimitris Aragiorgis
      if name:
189 f032d55c Dimitris Aragiorgis
        uuid = network2uuid.get(name, None)
190 f032d55c Dimitris Aragiorgis
        if uuid:
191 f032d55c Dimitris Aragiorgis
          print("NIC with network name %s found."
192 f032d55c Dimitris Aragiorgis
                " Substituting with uuid %s." % (name, uuid))
193 f032d55c Dimitris Aragiorgis
          nic["network"] = uuid
194 0500f6fd Dimitris Aragiorgis
      try:
195 0500f6fd Dimitris Aragiorgis
        del nic["idx"]
196 0500f6fd Dimitris Aragiorgis
        print("Deleting deprecated idx")
197 0500f6fd Dimitris Aragiorgis
      except KeyError:
198 0500f6fd Dimitris Aragiorgis
        pass
199 f032d55c Dimitris Aragiorgis
200 bb553e5a Bernardo Dal Seno
    if "disks" not in iobj:
201 bb553e5a Bernardo Dal Seno
      raise Error("Instance '%s' doesn't have a disks entry?!" % instance)
202 bb553e5a Bernardo Dal Seno
    disks = iobj["disks"]
203 bb553e5a Bernardo Dal Seno
    for idx, dobj in enumerate(disks):
204 bb553e5a Bernardo Dal Seno
      expected = "disk/%s" % idx
205 bb553e5a Bernardo Dal Seno
      current = dobj.get("iv_name", "")
206 bb553e5a Bernardo Dal Seno
      if current != expected:
207 bb553e5a Bernardo Dal Seno
        logging.warning("Updating iv_name for instance %s/disk %s"
208 bb553e5a Bernardo Dal Seno
                        " from '%s' to '%s'",
209 bb553e5a Bernardo Dal Seno
                        instance, idx, current, expected)
210 bb553e5a Bernardo Dal Seno
        dobj["iv_name"] = expected
211 0500f6fd Dimitris Aragiorgis
      try:
212 0500f6fd Dimitris Aragiorgis
        del dobj["idx"]
213 0500f6fd Dimitris Aragiorgis
        print("Deleting deprecated idx")
214 0500f6fd Dimitris Aragiorgis
      except KeyError:
215 0500f6fd Dimitris Aragiorgis
        pass
216 0500f6fd Dimitris Aragiorgis
217 0500f6fd Dimitris Aragiorgis
    for attr in ("dev_idxs", "hotplug_info", "hotplugs", "pci_reservations"):
218 0500f6fd Dimitris Aragiorgis
      try:
219 0500f6fd Dimitris Aragiorgis
        del iobj[attr]
220 0500f6fd Dimitris Aragiorgis
        print("Deleting deprecated %s" % attr)
221 0500f6fd Dimitris Aragiorgis
      except KeyError:
222 0500f6fd Dimitris Aragiorgis
        pass
223 bb553e5a Bernardo Dal Seno
224 bb553e5a Bernardo Dal Seno
225 bb553e5a Bernardo Dal Seno
def UpgradeRapiUsers():
226 bb553e5a Bernardo Dal Seno
  if (os.path.isfile(options.RAPI_USERS_FILE_PRE24) and
227 bb553e5a Bernardo Dal Seno
      not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
228 bb553e5a Bernardo Dal Seno
    if os.path.exists(options.RAPI_USERS_FILE):
229 bb553e5a Bernardo Dal Seno
      raise Error("Found pre-2.4 RAPI users file at %s, but another file"
230 bb553e5a Bernardo Dal Seno
                  " already exists at %s" %
231 bb553e5a Bernardo Dal Seno
                  (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
232 bb553e5a Bernardo Dal Seno
    logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
233 bb553e5a Bernardo Dal Seno
                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
234 bb553e5a Bernardo Dal Seno
    if not options.dry_run:
235 bb553e5a Bernardo Dal Seno
      utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
236 bb553e5a Bernardo Dal Seno
                       mkdir=True, mkdir_mode=0750)
237 bb553e5a Bernardo Dal Seno
238 bb553e5a Bernardo Dal Seno
  # Create a symlink for RAPI users file
239 bb553e5a Bernardo Dal Seno
  if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
240 bb553e5a Bernardo Dal Seno
           os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
241 bb553e5a Bernardo Dal Seno
      os.path.isfile(options.RAPI_USERS_FILE)):
242 bb553e5a Bernardo Dal Seno
    logging.info("Creating symlink from %s to %s",
243 bb553e5a Bernardo Dal Seno
                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
244 bb553e5a Bernardo Dal Seno
    if not options.dry_run:
245 bb553e5a Bernardo Dal Seno
      os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
246 bb553e5a Bernardo Dal Seno
247 bb553e5a Bernardo Dal Seno
248 bb553e5a Bernardo Dal Seno
def UpgradeWatcher():
249 bb553e5a Bernardo Dal Seno
  # Remove old watcher state file if it exists
250 bb553e5a Bernardo Dal Seno
  if os.path.exists(options.WATCHER_STATEFILE):
251 bb553e5a Bernardo Dal Seno
    logging.info("Removing watcher state file %s", options.WATCHER_STATEFILE)
252 bb553e5a Bernardo Dal Seno
    if not options.dry_run:
253 bb553e5a Bernardo Dal Seno
      utils.RemoveFile(options.WATCHER_STATEFILE)
254 bb553e5a Bernardo Dal Seno
255 bb553e5a Bernardo Dal Seno
256 bb553e5a Bernardo Dal Seno
def UpgradeFileStoragePaths(config_data):
257 bb553e5a Bernardo Dal Seno
  # Write file storage paths
258 bb553e5a Bernardo Dal Seno
  if not os.path.exists(options.FILE_STORAGE_PATHS_FILE):
259 bb553e5a Bernardo Dal Seno
    cluster = config_data["cluster"]
260 bb553e5a Bernardo Dal Seno
    file_storage_dir = cluster.get("file_storage_dir")
261 bb553e5a Bernardo Dal Seno
    shared_file_storage_dir = cluster.get("shared_file_storage_dir")
262 bb553e5a Bernardo Dal Seno
    del cluster
263 bb553e5a Bernardo Dal Seno
264 bb553e5a Bernardo Dal Seno
    logging.info("Ganeti 2.7 and later only allow whitelisted directories"
265 bb553e5a Bernardo Dal Seno
                 " for file storage; writing existing configuration values"
266 bb553e5a Bernardo Dal Seno
                 " into '%s'",
267 bb553e5a Bernardo Dal Seno
                 options.FILE_STORAGE_PATHS_FILE)
268 bb553e5a Bernardo Dal Seno
269 bb553e5a Bernardo Dal Seno
    if file_storage_dir:
270 bb553e5a Bernardo Dal Seno
      logging.info("File storage directory: %s", file_storage_dir)
271 bb553e5a Bernardo Dal Seno
    if shared_file_storage_dir:
272 bb553e5a Bernardo Dal Seno
      logging.info("Shared file storage directory: %s",
273 bb553e5a Bernardo Dal Seno
                   shared_file_storage_dir)
274 bb553e5a Bernardo Dal Seno
275 bb553e5a Bernardo Dal Seno
    buf = StringIO()
276 bb553e5a Bernardo Dal Seno
    buf.write("# List automatically generated from configuration by\n")
277 bb553e5a Bernardo Dal Seno
    buf.write("# cfgupgrade at %s\n" % time.asctime())
278 bb553e5a Bernardo Dal Seno
    if file_storage_dir:
279 bb553e5a Bernardo Dal Seno
      buf.write("%s\n" % file_storage_dir)
280 bb553e5a Bernardo Dal Seno
    if shared_file_storage_dir:
281 bb553e5a Bernardo Dal Seno
      buf.write("%s\n" % shared_file_storage_dir)
282 bb553e5a Bernardo Dal Seno
    utils.WriteFile(file_name=options.FILE_STORAGE_PATHS_FILE,
283 bb553e5a Bernardo Dal Seno
                    data=buf.getvalue(),
284 bb553e5a Bernardo Dal Seno
                    mode=0600,
285 bb553e5a Bernardo Dal Seno
                    dry_run=options.dry_run,
286 bb553e5a Bernardo Dal Seno
                    backup=True)
287 bb553e5a Bernardo Dal Seno
288 bb553e5a Bernardo Dal Seno
289 bb553e5a Bernardo Dal Seno
def UpgradeAll(config_data):
290 bb553e5a Bernardo Dal Seno
  config_data["version"] = constants.BuildVersion(TARGET_MAJOR,
291 bb553e5a Bernardo Dal Seno
                                                  TARGET_MINOR, 0)
292 bb553e5a Bernardo Dal Seno
  UpgradeRapiUsers()
293 bb553e5a Bernardo Dal Seno
  UpgradeWatcher()
294 bb553e5a Bernardo Dal Seno
  UpgradeFileStoragePaths(config_data)
295 bb553e5a Bernardo Dal Seno
  UpgradeNetworks(config_data)
296 0b94cda8 Bernardo Dal Seno
  UpgradeCluster(config_data)
297 bb553e5a Bernardo Dal Seno
  UpgradeGroups(config_data)
298 bb553e5a Bernardo Dal Seno
  UpgradeInstances(config_data)
299 bb553e5a Bernardo Dal Seno
300 f032d55c Dimitris Aragiorgis
301 41044e04 Bernardo Dal Seno
def DowngradeIPolicy(ipolicy, owner):
302 0b94cda8 Bernardo Dal Seno
  # Downgrade IPolicy to 2.7 (stable)
303 0b94cda8 Bernardo Dal Seno
  minmax_keys = ["min", "max"]
304 0b94cda8 Bernardo Dal Seno
  specs_is_split = any((k in ipolicy) for k in minmax_keys)
305 0b94cda8 Bernardo Dal Seno
  if not specs_is_split:
306 0b94cda8 Bernardo Dal Seno
    if "minmax" in ipolicy:
307 41044e04 Bernardo Dal Seno
      if type(ipolicy["minmax"]) is not list:
308 41044e04 Bernardo Dal Seno
        raise Error("Invalid minmax type in %s ipolicy: %s" %
309 41044e04 Bernardo Dal Seno
                    (owner, type(ipolicy["minmax"])))
310 41044e04 Bernardo Dal Seno
      if len(ipolicy["minmax"]) > 1:
311 41044e04 Bernardo Dal Seno
        logging.warning("Discarding some limit specs values from %s policy",
312 41044e04 Bernardo Dal Seno
                        owner)
313 41044e04 Bernardo Dal Seno
      minmax = ipolicy["minmax"][0]
314 0b94cda8 Bernardo Dal Seno
      del ipolicy["minmax"]
315 0b94cda8 Bernardo Dal Seno
    else:
316 0b94cda8 Bernardo Dal Seno
      minmax = {}
317 0b94cda8 Bernardo Dal Seno
    for key in minmax_keys:
318 0b94cda8 Bernardo Dal Seno
      spec = minmax.get(key, {})
319 0b94cda8 Bernardo Dal Seno
      ipolicy[key] = spec
320 e94fc80c Bernardo Dal Seno
    if "std" not in ipolicy:
321 e94fc80c Bernardo Dal Seno
      ipolicy["std"] = {}
322 0b94cda8 Bernardo Dal Seno
323 0b94cda8 Bernardo Dal Seno
324 0b94cda8 Bernardo Dal Seno
def DowngradeGroups(config_data):
325 0b94cda8 Bernardo Dal Seno
  for group in config_data["nodegroups"].values():
326 0b94cda8 Bernardo Dal Seno
    ipolicy = group.get("ipolicy", None)
327 e94fc80c Bernardo Dal Seno
    if ipolicy is not None:
328 41044e04 Bernardo Dal Seno
      DowngradeIPolicy(ipolicy, "group \"%s\"" % group.get("name"))
329 0b94cda8 Bernardo Dal Seno
330 0b94cda8 Bernardo Dal Seno
331 2827d1ac Bernardo Dal Seno
def DowngradeEnabledTemplates(cluster):
332 2827d1ac Bernardo Dal Seno
  # Remove enabled disk templates to downgrade to 2.7
333 2827d1ac Bernardo Dal Seno
  edt_key = "enabled_disk_templates"
334 2827d1ac Bernardo Dal Seno
  if edt_key in cluster:
335 2827d1ac Bernardo Dal Seno
    logging.warning("Removing cluster's enabled disk templates; value = %s",
336 2827d1ac Bernardo Dal Seno
                    utils.CommaJoin(cluster[edt_key]))
337 2827d1ac Bernardo Dal Seno
    del cluster[edt_key]
338 1709435e Bernardo Dal Seno
339 1709435e Bernardo Dal Seno
340 1709435e Bernardo Dal Seno
def DowngradeCluster(config_data):
341 1709435e Bernardo Dal Seno
  cluster = config_data.get("cluster", None)
342 1709435e Bernardo Dal Seno
  if cluster is None:
343 1709435e Bernardo Dal Seno
    raise Error("Cannot find cluster")
344 2827d1ac Bernardo Dal Seno
  DowngradeEnabledTemplates(cluster)
345 0b94cda8 Bernardo Dal Seno
  ipolicy = cluster.get("ipolicy", None)
346 0b94cda8 Bernardo Dal Seno
  if ipolicy:
347 41044e04 Bernardo Dal Seno
    DowngradeIPolicy(ipolicy, "cluster")
348 78237f71 Apollon Oikonomopoulos
  if "dsahostkeypub" in cluster:
349 78237f71 Apollon Oikonomopoulos
    del cluster["dsahostkeypub"]
350 1709435e Bernardo Dal Seno
351 1709435e Bernardo Dal Seno
352 89c910fd Michele Tartara
def DowngradeDisk(disk):
353 89c910fd Michele Tartara
  if "uuid" in disk:
354 89c910fd Michele Tartara
    del disk["uuid"]
355 89c910fd Michele Tartara
  if "children" in disk:
356 89c910fd Michele Tartara
    for child_disk in disk["children"]:
357 89c910fd Michele Tartara
      DowngradeDisk(child_disk)
358 89c910fd Michele Tartara
359 89c910fd Michele Tartara
360 a8e07057 Thomas Thrainer
def DowngradeInstances(config_data):
361 a8e07057 Thomas Thrainer
  if "instances" not in config_data:
362 a8e07057 Thomas Thrainer
    raise Error("Can't find the 'instances' key in the configuration!")
363 a8e07057 Thomas Thrainer
364 a8e07057 Thomas Thrainer
  for _, iobj in config_data["instances"].items():
365 a8e07057 Thomas Thrainer
    if "disks_active" in iobj:
366 a8e07057 Thomas Thrainer
      del iobj["disks_active"]
367 a8e07057 Thomas Thrainer
368 89c910fd Michele Tartara
    # Remove the NICs UUIDs
369 89c910fd Michele Tartara
    for nic in iobj["nics"]:
370 89c910fd Michele Tartara
      if "uuid" in nic:
371 89c910fd Michele Tartara
        del nic["uuid"]
372 89c910fd Michele Tartara
373 89c910fd Michele Tartara
    # Downgrade the disks
374 89c910fd Michele Tartara
    for disk in iobj["disks"]:
375 89c910fd Michele Tartara
      DowngradeDisk(disk)
376 89c910fd Michele Tartara
377 a8e07057 Thomas Thrainer
378 1709435e Bernardo Dal Seno
def DowngradeAll(config_data):
379 1709435e Bernardo Dal Seno
  # Any code specific to a particular version should be labeled that way, so
380 1709435e Bernardo Dal Seno
  # it can be removed when updating to the next version.
381 77018a43 Michele Tartara
  config_data["version"] = constants.BuildVersion(DOWNGRADE_MAJOR,
382 77018a43 Michele Tartara
                                                  DOWNGRADE_MINOR, 0)
383 1709435e Bernardo Dal Seno
  DowngradeCluster(config_data)
384 0b94cda8 Bernardo Dal Seno
  DowngradeGroups(config_data)
385 a8e07057 Thomas Thrainer
  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 11e90588 Dimitris Aragiorgis
  parser.add_option("--tob64",
420 11e90588 Dimitris Aragiorgis
                    help="Change to base64 encoded networks",
421 11e90588 Dimitris Aragiorgis
                    action="store_true", dest="tob64", default=False)
422 11e90588 Dimitris Aragiorgis
  parser.add_option("--to01",
423 11e90588 Dimitris Aragiorgis
                    help="Change to non encoded networks (01 bitarrays)",
424 11e90588 Dimitris Aragiorgis
                    action="store_true", dest="to01", default=False)
425 0006af7d Michael Hanselmann
  (options, args) = parser.parse_args()
426 0006af7d Michael Hanselmann
427 ac4d25b6 Iustin Pop
  # We need to keep filenames locally because they might be renamed between
428 ac4d25b6 Iustin Pop
  # versions.
429 0cddd44d Iustin Pop
  options.data_dir = os.path.abspath(options.data_dir)
430 ac4d25b6 Iustin Pop
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
431 ac4d25b6 Iustin Pop
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
432 ac4d25b6 Iustin Pop
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
433 ac4d25b6 Iustin Pop
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
434 b6267745 Andrea Spadaccini
  options.SPICE_CERT_FILE = options.data_dir + "/spice.pem"
435 b6267745 Andrea Spadaccini
  options.SPICE_CACERT_FILE = options.data_dir + "/spice-ca.pem"
436 fdd9ac5b Michael Hanselmann
  options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
437 fdd9ac5b Michael Hanselmann
  options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
438 6b7d5878 Michael Hanselmann
  options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
439 fc0726b9 Michael Hanselmann
  options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
440 011974df Michael Hanselmann
  options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
441 a292020f Michael Hanselmann
  options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
442 7939f60c Michael Hanselmann
  options.FILE_STORAGE_PATHS_FILE = options.conf_dir + "/file-storage-paths"
443 ac4d25b6 Iustin Pop
444 eda37a5a Michael Hanselmann
  SetupLogging()
445 eda37a5a Michael Hanselmann
446 0006af7d Michael Hanselmann
  # Option checking
447 0006af7d Michael Hanselmann
  if args:
448 95e4a814 Michael Hanselmann
    raise Error("No arguments expected")
449 1709435e Bernardo Dal Seno
  if options.downgrade and not options.no_verify:
450 1709435e Bernardo Dal Seno
    options.no_verify = True
451 0006af7d Michael Hanselmann
452 011974df Michael Hanselmann
  # Check master name
453 011974df Michael Hanselmann
  if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
454 011974df Michael Hanselmann
    logging.error("Aborting due to hostname mismatch")
455 011974df Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
456 011974df Michael Hanselmann
457 319856a9 Michael Hanselmann
  if not options.force:
458 1709435e Bernardo Dal Seno
    if options.downgrade:
459 1709435e Bernardo Dal Seno
      usertext = ("The configuration is going to be DOWNGRADED to version %s.%s"
460 1709435e Bernardo Dal Seno
                  " Some configuration data might be removed if they don't fit"
461 1709435e Bernardo Dal Seno
                  " in the old format. Please make sure you have read the"
462 1709435e Bernardo Dal Seno
                  " upgrade notes (available in the UPGRADE file and included"
463 1709435e Bernardo Dal Seno
                  " in other documentation formats) to understand what they"
464 1709435e Bernardo Dal Seno
                  " are. Continue with *DOWNGRADING* the configuration?" %
465 1709435e Bernardo Dal Seno
                  (DOWNGRADE_MAJOR, DOWNGRADE_MINOR))
466 1709435e Bernardo Dal Seno
    else:
467 1709435e Bernardo Dal Seno
      usertext = ("Please make sure you have read the upgrade notes for"
468 1709435e Bernardo Dal Seno
                  " Ganeti %s (available in the UPGRADE file and included"
469 1709435e Bernardo Dal Seno
                  " in other documentation formats). Continue with upgrading"
470 1709435e Bernardo Dal Seno
                  " configuration?" % constants.RELEASE_VERSION)
471 f97c7901 Michael Hanselmann
    if not cli.AskUser(usertext):
472 a9221f09 Michael Hanselmann
      sys.exit(constants.EXIT_FAILURE)
473 319856a9 Michael Hanselmann
474 95e4a814 Michael Hanselmann
  # Check whether it's a Ganeti configuration directory
475 ac4d25b6 Iustin Pop
  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
476 30acff6c Michael Hanselmann
          os.path.isfile(options.SERVER_PEM_PATH) and
477 ac4d25b6 Iustin Pop
          os.path.isfile(options.KNOWN_HOSTS_PATH)):
478 a9221f09 Michael Hanselmann
    raise Error(("%s does not seem to be a Ganeti configuration"
479 ac4d25b6 Iustin Pop
                 " directory") % options.data_dir)
480 95e4a814 Michael Hanselmann
481 7939f60c Michael Hanselmann
  if not os.path.isdir(options.conf_dir):
482 7939f60c Michael Hanselmann
    raise Error("Not a directory: %s" % options.conf_dir)
483 7939f60c Michael Hanselmann
484 11c31f5c Michael Hanselmann
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
485 a421fdeb Iustin Pop
486 11c31f5c Michael Hanselmann
  try:
487 11c31f5c Michael Hanselmann
    config_version = config_data["version"]
488 11c31f5c Michael Hanselmann
  except KeyError:
489 11c31f5c Michael Hanselmann
    raise Error("Unable to determine configuration version")
490 0006af7d Michael Hanselmann
491 11c31f5c Michael Hanselmann
  (config_major, config_minor, config_revision) = \
492 11c31f5c Michael Hanselmann
    constants.SplitVersion(config_version)
493 319856a9 Michael Hanselmann
494 11c31f5c Michael Hanselmann
  logging.info("Found configuration version %s (%d.%d.%d)",
495 11c31f5c Michael Hanselmann
               config_version, config_major, config_minor, config_revision)
496 319856a9 Michael Hanselmann
497 11c31f5c Michael Hanselmann
  if "config_version" in config_data["cluster"]:
498 11c31f5c Michael Hanselmann
    raise Error("Inconsistent configuration: found config_version in"
499 11c31f5c Michael Hanselmann
                " configuration file")
500 95e4a814 Michael Hanselmann
501 1709435e Bernardo Dal Seno
  # Downgrade to the previous stable version
502 1709435e Bernardo Dal Seno
  if options.downgrade:
503 f2e4363c Michele Tartara
    if not ((config_major == TARGET_MAJOR and config_minor == TARGET_MINOR) or
504 f2e4363c Michele Tartara
            (config_major == DOWNGRADE_MAJOR and
505 f2e4363c Michele Tartara
             config_minor == DOWNGRADE_MINOR)):
506 1709435e Bernardo Dal Seno
      raise Error("Downgrade supported only from the latest version (%s.%s),"
507 1709435e Bernardo Dal Seno
                  " found %s (%s.%s.%s) instead" %
508 1709435e Bernardo Dal Seno
                  (TARGET_MAJOR, TARGET_MINOR, config_version, config_major,
509 1709435e Bernardo Dal Seno
                   config_minor, config_revision))
510 1709435e Bernardo Dal Seno
    DowngradeAll(config_data)
511 1709435e Bernardo Dal Seno
512 33bff17b Michele Tartara
  # Upgrade from 2.{0..7} to 2.8
513 33bff17b Michele Tartara
  elif config_major == 2 and config_minor in range(0, 9):
514 aeb0c953 Michael Hanselmann
    if config_revision != 0:
515 a9221f09 Michael Hanselmann
      logging.warning("Config revision is %s, not 0", config_revision)
516 bb553e5a Bernardo Dal Seno
    UpgradeAll(config_data)
517 904910c4 Iustin Pop
518 93fd9bb1 Iustin Pop
  elif config_major == TARGET_MAJOR and config_minor == TARGET_MINOR:
519 a9221f09 Michael Hanselmann
    logging.info("No changes necessary")
520 a9221f09 Michael Hanselmann
521 a9221f09 Michael Hanselmann
  else:
522 a9221f09 Michael Hanselmann
    raise Error("Configuration version %d.%d.%d not supported by this tool" %
523 a9221f09 Michael Hanselmann
                (config_major, config_minor, config_revision))
524 aeb0c953 Michael Hanselmann
525 11c31f5c Michael Hanselmann
  try:
526 11c31f5c Michael Hanselmann
    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
527 11c31f5c Michael Hanselmann
    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
528 11c31f5c Michael Hanselmann
                    data=serializer.DumpJson(config_data),
529 11c31f5c Michael Hanselmann
                    mode=0600,
530 11c31f5c Michael Hanselmann
                    dry_run=options.dry_run,
531 11c31f5c Michael Hanselmann
                    backup=True)
532 a421fdeb Iustin Pop
533 a421fdeb Iustin Pop
    if not options.dry_run:
534 5ae4945a Iustin Pop
      bootstrap.GenerateClusterCrypto(
535 5ae4945a Iustin Pop
        False, False, False, False, False,
536 5ae4945a Iustin Pop
        nodecert_file=options.SERVER_PEM_PATH,
537 5ae4945a Iustin Pop
        rapicert_file=options.RAPI_CERT_FILE,
538 5ae4945a Iustin Pop
        spicecert_file=options.SPICE_CERT_FILE,
539 5ae4945a Iustin Pop
        spicecacert_file=options.SPICE_CACERT_FILE,
540 5ae4945a Iustin Pop
        hmackey_file=options.CONFD_HMAC_KEY,
541 5ae4945a Iustin Pop
        cds_file=options.CDS_FILE)
542 aeb0c953 Michael Hanselmann
543 a9221f09 Michael Hanselmann
  except Exception:
544 11c31f5c Michael Hanselmann
    logging.critical("Writing configuration failed. It is probably in an"
545 95e4a814 Michael Hanselmann
                     " inconsistent state and needs manual intervention.")
546 95e4a814 Michael Hanselmann
    raise
547 0006af7d Michael Hanselmann
548 ac4d25b6 Iustin Pop
  # test loading the config file
549 fdb85e3d Bernardo Dal Seno
  all_ok = True
550 02e1292d Michael Hanselmann
  if not (options.dry_run or options.no_verify):
551 ac4d25b6 Iustin Pop
    logging.info("Testing the new config file...")
552 ac4d25b6 Iustin Pop
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
553 959b6fe5 Apollon Oikonomopoulos
                              accept_foreign=options.ignore_hostname,
554 ac4d25b6 Iustin Pop
                              offline=True)
555 ac4d25b6 Iustin Pop
    # if we reached this, it's all fine
556 ac4d25b6 Iustin Pop
    vrfy = cfg.VerifyConfig()
557 ac4d25b6 Iustin Pop
    if vrfy:
558 ac4d25b6 Iustin Pop
      logging.error("Errors after conversion:")
559 ac4d25b6 Iustin Pop
      for item in vrfy:
560 07b8a2b5 Iustin Pop
        logging.error(" - %s", item)
561 fdb85e3d Bernardo Dal Seno
      all_ok = False
562 fdb85e3d Bernardo Dal Seno
    else:
563 fdb85e3d Bernardo Dal Seno
      logging.info("File loaded successfully after upgrading")
564 ac4d25b6 Iustin Pop
    del cfg
565 ac4d25b6 Iustin Pop
566 1709435e Bernardo Dal Seno
  if options.downgrade:
567 1709435e Bernardo Dal Seno
    action = "downgraded"
568 1709435e Bernardo Dal Seno
    out_ver = "%s.%s" % (DOWNGRADE_MAJOR, DOWNGRADE_MINOR)
569 1709435e Bernardo Dal Seno
  else:
570 1709435e Bernardo Dal Seno
    action = "upgraded"
571 1709435e Bernardo Dal Seno
    out_ver = constants.RELEASE_VERSION
572 fdb85e3d Bernardo Dal Seno
  if all_ok:
573 1709435e Bernardo Dal Seno
    cli.ToStderr("Configuration successfully %s to version %s.",
574 1709435e Bernardo Dal Seno
                 action, out_ver)
575 fdb85e3d Bernardo Dal Seno
  else:
576 1709435e Bernardo Dal Seno
    cli.ToStderr("Configuration %s to version %s, but there are errors."
577 1709435e Bernardo Dal Seno
                 "\nPlease review the file.", action, out_ver)
578 66a66fa7 Michael Hanselmann
579 6d691282 Michael Hanselmann
580 6d691282 Michael Hanselmann
if __name__ == "__main__":
581 6d691282 Michael Hanselmann
  main()