Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 0500f6fd

History | View | Annotate | Download (18.8 kB)

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