Statistics
| Branch: | Tag: | Revision:

root / tools / sanitize-config @ 514dcbda

History | View | Annotate | Download (8.2 kB)

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

    
4
# Copyright (C) 2010 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
# pylint: disable=C0103
23

    
24
"""Tool to sanitize/randomize the configuration file.
25

    
26
"""
27

    
28
import sys
29
import os
30
import os.path
31
import optparse
32

    
33
from ganeti import constants
34
from ganeti import serializer
35
from ganeti import utils
36
from ganeti import pathutils
37
from ganeti import cli
38
from ganeti.cli import cli_option
39

    
40

    
41
OPTS = [
42
  cli.VERBOSE_OPT,
43
  cli_option("--path", help="Convert this configuration file"
44
             " instead of '%s'" % pathutils.CLUSTER_CONF_FILE,
45
             default=pathutils.CLUSTER_CONF_FILE, dest="CONFIG_DATA_PATH"),
46
  cli_option("--sanitize-names", default="yes", type="bool",
47
             help="Randomize the cluster, node and instance names [yes]"),
48
  cli_option("--sanitize-ips", default="yes", type="bool",
49
             help="Randomize the cluster, node and instance IPs [yes]"),
50
  cli_option("--sanitize-lvs", default="no", type="bool",
51
             help="Randomize the LV names (for old clusters) [no]"),
52
  cli_option("--sanitize-os-names", default="yes", type="bool",
53
             help="Randomize the OS names [yes]"),
54
  cli_option("--no-randomization", default=False, action="store_true",
55
             help="Disable all name randomization (only randomize secrets)"),
56
  cli_option("--base-domain", default="example.com",
57
             help="The base domain used for new names [example.com]"),
58
  ]
59

    
60

    
61
def Error(txt, *args):
62
  """Writes a message to standard error and exits.
63

    
64
  """
65
  cli.ToStderr(txt, *args)
66
  sys.exit(1)
67

    
68

    
69
def GenerateNameMap(opts, names, base):
70
  """For a given set of names, generate a list of sane new names.
71

    
72
  """
73
  names = utils.NiceSort(names)
74
  name_map = {}
75
  for idx, old_name in enumerate(names):
76
    new_name = "%s%d.%s" % (base, idx + 1, opts.base_domain)
77
    if new_name in names:
78
      Error("Name conflict for %s: %s already exists", base, new_name)
79
    name_map[old_name] = new_name
80
  return name_map
81

    
82

    
83
def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
84
  """Cleanup configuration secrets.
85

    
86
  """
87
  cfg["cluster"]["rsahostkeypub"] = ""
88
  cfg["cluster"]["dsahostkeypub"] = ""
89
  for instance in cfg["instances"].values():
90
    for disk in instance["disks"]:
91
      RandomizeDiskSecrets(disk)
92

    
93

    
94
def SanitizeCluster(opts, cfg):
95
  """Sanitize the cluster names.
96

    
97
  """
98
  cfg["cluster"]["cluster_name"] = "cluster." + opts.base_domain
99

    
100

    
101
def SanitizeNodes(opts, cfg):
102
  """Sanitize node names.
103

    
104
  """
105
  old_names = cfg["nodes"].keys()
106
  old_map = GenerateNameMap(opts, old_names, "node")
107

    
108
  # rename nodes
109
  RenameDictKeys(cfg["nodes"], old_map, True)
110

    
111
  # update master node
112
  cfg["cluster"]["master_node"] = old_map[cfg["cluster"]["master_node"]]
113

    
114
  # update instance configuration
115
  for instance in cfg["instances"].values():
116
    instance["primary_node"] = old_map[instance["primary_node"]]
117
    for disk in instance["disks"]:
118
      RenameDiskNodes(disk, old_map)
119

    
120

    
121
def SanitizeInstances(opts, cfg):
122
  """Sanitize instance names.
123

    
124
  """
125
  old_names = cfg["instances"].keys()
126
  old_map = GenerateNameMap(opts, old_names, "instance")
127

    
128
  RenameDictKeys(cfg["instances"], old_map, True)
129

    
130

    
131
def SanitizeIps(opts, cfg): # pylint: disable=W0613
132
  """Sanitize the IP names.
133

    
134
  @note: we're interested in obscuring the old IPs, not in generating
135
      actually valid new IPs, so we chose to simply put IPv4
136
      addresses, irrelevant of whether IPv6 or IPv4 addresses existed
137
      before.
138

    
139
  """
140
  def _Get(old):
141
    if old in ip_map:
142
      return ip_map[old]
143
    idx = len(ip_map) + 1
144
    rest, d_octet = divmod(idx, 256)
145
    rest, c_octet = divmod(rest, 256)
146
    rest, b_octet = divmod(rest, 256)
147
    if rest > 0:
148
      Error("Too many IPs!")
149
    new_ip = "%d.%d.%d.%d" % (10, b_octet, c_octet, d_octet)
150
    ip_map[old] = new_ip
151
    return new_ip
152

    
153
  ip_map = {}
154

    
155
  cfg["cluster"]["master_ip"] = _Get(cfg["cluster"]["master_ip"])
156
  for node in cfg["nodes"].values():
157
    node["primary_ip"] = _Get(node["primary_ip"])
158
    node["secondary_ip"] = _Get(node["secondary_ip"])
159

    
160
  for instance in cfg["instances"].values():
161
    for nic in instance["nics"]:
162
      if "ip" in nic and nic["ip"]:
163
        nic["ip"] = _Get(nic["ip"])
164

    
165

    
166
def SanitizeOsNames(opts, cfg): # pylint: disable=W0613
167
  """Sanitize the OS names.
168

    
169
  """
170
  def _Get(old):
171
    if old in os_map:
172
      return os_map[old]
173
    os_map[old] = "ganeti-os%d" % (len(os_map) + 1)
174
    return os_map[old]
175

    
176
  os_map = {}
177
  for instance in cfg["instances"].values():
178
    instance["os"] = _Get(instance["os"])
179

    
180
  if "os_hvp" in cfg["cluster"]:
181
    for os_name in cfg["cluster"]["os_hvp"]:
182
      # force population of the entire os map
183
      _Get(os_name)
184
    RenameDictKeys(cfg["cluster"]["os_hvp"], os_map, False)
185

    
186

    
187
def SanitizeDisks(opts, cfg): # pylint: disable=W0613
188
  """Cleanup disks disks.
189

    
190
  """
191
  def _Get(old):
192
    if old in lv_map:
193
      return old
194
    lv_map[old] = utils.NewUUID()
195
    return lv_map[old]
196

    
197
  def helper(disk):
198
    if "children" in disk and disk["children"]:
199
      for child in disk["children"]:
200
        helper(child)
201

    
202
    if disk["dev_type"] == constants.DT_PLAIN and opts.sanitize_lvs:
203
      disk["logical_id"][1] = _Get(disk["logical_id"][1])
204

    
205
  lv_map = {}
206

    
207
  for instance in cfg["instances"].values():
208
    for disk in instance["disks"]:
209
      helper(disk)
210

    
211

    
212
def RandomizeDiskSecrets(disk):
213
  """Randomize a disks' secrets (if any).
214

    
215
  """
216
  if "children" in disk and disk["children"]:
217
    for child in disk["children"]:
218
      RandomizeDiskSecrets(child)
219

    
220
  # only disk type to contain secrets is the drbd one
221
  if disk["dev_type"] == constants.DT_DRBD8:
222
    disk["logical_id"][5] = utils.GenerateSecret()
223

    
224

    
225
def RenameDiskNodes(disk, node_map):
226
  """Rename nodes in the disk config.
227

    
228
  """
229
  if "children" in disk and disk["children"]:
230
    for child in disk["children"]:
231
      RenameDiskNodes(child, node_map)
232

    
233
  # only disk type to contain nodes is the drbd one
234
  if disk["dev_type"] == constants.DT_DRBD8:
235
    lid = disk["logical_id"]
236
    lid[0] = node_map[lid[0]]
237
    lid[1] = node_map[lid[1]]
238

    
239

    
240
def RenameDictKeys(a_dict, name_map, update_name):
241
  """Rename the dictionary keys based on a name map.
242

    
243
  """
244
  for old_name in a_dict.keys():
245
    new_name = name_map[old_name]
246
    a_dict[new_name] = a_dict[old_name]
247
    del a_dict[old_name]
248
    if update_name:
249
      a_dict[new_name]["name"] = new_name
250

    
251

    
252
def main():
253
  """Main program.
254

    
255
  """
256
  # Option parsing
257
  parser = optparse.OptionParser(usage="%prog [--verbose] output_file")
258

    
259
  for o in OPTS:
260
    parser.add_option(o)
261

    
262
  (opts, args) = parser.parse_args()
263
  if opts.no_randomization:
264
    opts.sanitize_names = opts.sanitize_ips = opts.sanitize_os_names = \
265
        opts.sanitize_lvs = False
266

    
267
  # Option checking
268
  if len(args) != 1:
269
    Error("Usage: sanitize-config [options] {<output_file> | -}")
270

    
271
  # Check whether it's a Ganeti configuration directory
272
  if not os.path.isfile(opts.CONFIG_DATA_PATH):
273
    Error("Cannot find Ganeti configuration file %s", opts.CONFIG_DATA_PATH)
274

    
275
  config_data = serializer.LoadJson(utils.ReadFile(opts.CONFIG_DATA_PATH))
276

    
277
  # Randomize LVM names
278
  SanitizeDisks(opts, config_data)
279

    
280
  SanitizeSecrets(opts, config_data)
281

    
282
  if opts.sanitize_names:
283
    SanitizeCluster(opts, config_data)
284
    SanitizeNodes(opts, config_data)
285
    SanitizeInstances(opts, config_data)
286

    
287
  if opts.sanitize_ips:
288
    SanitizeIps(opts, config_data)
289

    
290
  if opts.sanitize_os_names:
291
    SanitizeOsNames(opts, config_data)
292

    
293
  data = serializer.DumpJson(config_data)
294
  if args[0] == "-":
295
    sys.stdout.write(data)
296
  else:
297
    utils.WriteFile(file_name=args[0],
298
                    data=data,
299
                    mode=0600,
300
                    backup=True)
301

    
302
if __name__ == "__main__":
303
  main()