Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ d4b81bdd

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