Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 0006af7d

History | View | Annotate | Download (4.9 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Tool to upgrade the configuration file.
23

    
24
The upgrade is done by unpickling the configuration file into custom classes
25
derivating from dict. We then update the configuration by modifying these
26
dicts. To save the configuration, it's pickled into a buffer and unpickled
27
again using the Ganeti objects before being finally pickled into a file.
28

    
29
Not using the custom classes wouldn't allow us to rename or remove attributes
30
between versions without loosing their values.
31

    
32
"""
33

    
34

    
35
import os
36
import os.path
37
import sys
38
import optparse
39
import cPickle
40
import tempfile
41
from cStringIO import StringIO
42

    
43
from ganeti import objects
44

    
45
class Error(Exception):
46
  """Generic exception"""
47
  pass
48

    
49

    
50
def _BaseFindGlobal(module, name):
51
  """Helper function for the other FindGlobal functions.
52

    
53
  """
54
  return getattr(sys.modules[module], name)
55

    
56

    
57
# Internal config representation
58
class UpgradeDict(dict):
59
  """Base class for internal config classes.
60

    
61
  """
62
  def __setstate__(self, state):
63
    self.update(state)
64

    
65
  def __getstate__(self):
66
    return self.copy()
67

    
68

    
69
class UpgradeConfigData(UpgradeDict): pass
70
class UpgradeCluster(UpgradeDict): pass
71
class UpgradeNode(UpgradeDict): pass
72
class UpgradeInstance(UpgradeDict): pass
73
class UpgradeDisk(UpgradeDict): pass
74
class UpgradeNIC(UpgradeDict): pass
75
class UpgradeOS(UpgradeDict): pass
76

    
77

    
78
_ClassMap = {
79
  objects.ConfigData: UpgradeConfigData,
80
  objects.Cluster: UpgradeCluster,
81
  objects.Node: UpgradeNode,
82
  objects.Instance: UpgradeInstance,
83
  objects.Disk: UpgradeDisk,
84
  objects.NIC: UpgradeNIC,
85
  objects.OS: UpgradeOS,
86
}
87

    
88
# Build mapping dicts
89
WriteMapping = dict()
90
ReadMapping = dict()
91
for key, value in _ClassMap.iteritems():
92
  WriteMapping[value.__name__] = key
93
  ReadMapping[key.__name__] = value
94

    
95

    
96
# Read config
97
def _ReadFindGlobal(module, name):
98
  """Wraps Ganeti config classes to internal ones.
99

    
100
  """
101
  if module == "ganeti.objects" and name in ReadMapping:
102
    return ReadMapping[name]
103

    
104
  return _BaseFindGlobal(module, name)
105

    
106

    
107
def ReadConfig(path):
108
  """Reads configuration file.
109

    
110
  """
111
  f = open(path, 'r')
112
  try:
113
    loader = cPickle.Unpickler(f)
114
    loader.find_global = _ReadFindGlobal
115
    data = loader.load()
116
  finally:
117
    f.close()
118

    
119
  return data
120

    
121

    
122
# Write config
123
def _WriteFindGlobal(module, name):
124
  """Maps our internal config classes to Ganeti's.
125

    
126
  """
127
  if module == "__main__" and name in WriteMapping:
128
    return WriteMapping[name]
129

    
130
  return _BaseFindGlobal(module, name)
131

    
132

    
133
def WriteConfig(path, data):
134
  """Writes the configuration file.
135

    
136
  """
137
  buf = StringIO()
138

    
139
  # Write intermediate representation
140
  dumper = cPickle.Pickler(buf, cPickle.HIGHEST_PROTOCOL)
141
  dumper.dump(data)
142
  del dumper
143

    
144
  # Convert back to Ganeti objects
145
  buf.seek(0)
146
  loader = cPickle.Unpickler(buf)
147
  loader.find_global = _WriteFindGlobal
148
  data = loader.load()
149

    
150
  # Write target file
151
  (fd, name) = tempfile.mkstemp(dir=os.path.dirname(path))
152
  f = os.fdopen(fd, 'w')
153
  try:
154
    try:
155
      dumper = cPickle.Pickler(f, cPickle.HIGHEST_PROTOCOL)
156
      dumper.dump(data)
157
      f.flush()
158
      os.rename(name, path)
159
    except:
160
      os.unlink(name)
161
      raise
162
  finally:
163
    f.close()
164

    
165

    
166
def UpdateFromVersion2To3(cfg):
167
  """Updates the configuration from version 2 to 3.
168

    
169
  """
170
  if cfg['cluster']['config_version'] != 2:
171
    return
172

    
173
  # Add port pool
174
  if 'tcpudp_port_pool' not in cfg['cluster']:
175
    cfg['cluster']['tcpudp_port_pool'] = set()
176

    
177
  # Add bridge settings
178
  if 'default_bridge' not in cfg['cluster']:
179
    cfg['cluster']['default_bridge'] = 'xen-br0'
180
  for inst in cfg['instances'].values():
181
    for nic in inst['nics']:
182
      if 'bridge' not in nic:
183
        nic['bridge'] = None
184

    
185
  cfg['cluster']['config_version'] = 3
186

    
187

    
188
# Main program
189
if __name__ == "__main__":
190
  # Option parsing
191
  parser = optparse.OptionParser()
192
  parser.add_option('--verbose', dest='verbose',
193
                    action="store_true",
194
                    help="Verbose output")
195
  (options, args) = parser.parse_args()
196

    
197
  # Option checking
198
  if args:
199
    cfg_file = args[0]
200
  else:
201
    raise Error, ("Configuration file not specified")
202

    
203
  config = ReadConfig(cfg_file)
204

    
205
  UpdateFromVersion2To3(config)
206

    
207
  if options.verbose:
208
    import pprint
209
    pprint.pprint(config)
210

    
211
  WriteConfig(cfg_file, config)