Prepare version numbers for 2.10 release cycle
[ganeti-local] / tools / sanitize-config
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()