Merge branch 'stable-2.16' into stable-2.17
[ganeti-github.git] / lib / ssconf.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Global Configuration data for Ganeti.
32
33 This module provides the interface to a special case of cluster
34 configuration data, which is mostly static and available to all nodes.
35
36 """
37
38 import sys
39 import errno
40 import logging
41
42 from ganeti import errors
43 from ganeti import constants
44 from ganeti import utils
45 from ganeti import netutils
46 from ganeti import pathutils
47
48
49 SSCONF_LOCK_TIMEOUT = 10
50
51 #: Valid ssconf keys
52 _VALID_KEYS = constants.VALID_SS_KEYS
53
54 #: Maximum size for ssconf files
55 _MAX_SIZE = 128 * 1024
56
57
58 def ReadSsconfFile(filename):
59 """Reads an ssconf file and verifies its size.
60
61 @type filename: string
62 @param filename: Path to file
63 @rtype: string
64 @return: File contents without newlines at the end
65 @raise RuntimeError: When the file size exceeds L{_MAX_SIZE}
66
67 """
68 statcb = utils.FileStatHelper()
69
70 data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb)
71
72 if statcb.st.st_size > _MAX_SIZE:
73 msg = ("File '%s' has a size of %s bytes (up to %s allowed)" %
74 (filename, statcb.st.st_size, _MAX_SIZE))
75 raise RuntimeError(msg)
76
77 return data.rstrip("\n")
78
79
80 class SimpleStore(object):
81 """Interface to static cluster data.
82
83 This is different that the config.ConfigWriter and
84 SimpleConfigReader classes in that it holds data that will always be
85 present, even on nodes which don't have all the cluster data.
86
87 Other particularities of the datastore:
88 - keys are restricted to predefined values
89
90 """
91 def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
92 if cfg_location is None:
93 self._cfg_dir = pathutils.DATA_DIR
94 else:
95 self._cfg_dir = cfg_location
96
97 self._lockfile = _lockfile
98
99 def KeyToFilename(self, key):
100 """Convert a given key into filename.
101
102 """
103 if key not in _VALID_KEYS:
104 raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
105 % str(key))
106
107 filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
108 return filename
109
110 def _ReadFile(self, key, default=None):
111 """Generic routine to read keys.
112
113 This will read the file which holds the value requested. Errors
114 will be changed into ConfigurationErrors.
115
116 """
117 filename = self.KeyToFilename(key)
118 try:
119 return ReadSsconfFile(filename)
120 except EnvironmentError, err:
121 if err.errno == errno.ENOENT and default is not None:
122 return default
123 raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
124 (filename, str(err)))
125
126 def ReadAll(self):
127 """Reads all keys and returns their values.
128
129 @rtype: dict
130 @return: Dictionary, ssconf key as key, value as value
131
132 """
133 result = []
134
135 for key in _VALID_KEYS:
136 try:
137 value = self._ReadFile(key)
138 except errors.ConfigurationError:
139 # Ignore non-existing files
140 pass
141 else:
142 result.append((key, value))
143
144 return dict(result)
145
146 def WriteFiles(self, values, dry_run=False):
147 """Writes ssconf files used by external scripts.
148
149 @type values: dict
150 @param values: Dictionary of (name, value)
151 @type dry_run boolean
152 @param dry_run: Whether to perform a dry run
153
154 """
155 ssconf_lock = utils.FileLock.Open(self._lockfile)
156
157 # Get lock while writing files
158 ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
159 try:
160 for name, value in values.iteritems():
161 if isinstance(value, (list, tuple)):
162 value = "\n".join(value)
163 if value and not value.endswith("\n"):
164 value += "\n"
165
166 if len(value) > _MAX_SIZE:
167 msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
168 " allowed" % (name, len(value), _MAX_SIZE))
169 raise errors.ConfigurationError(msg)
170
171 utils.WriteFile(self.KeyToFilename(name), data=value,
172 mode=constants.SS_FILE_PERMS,
173 dry_run=dry_run)
174 finally:
175 ssconf_lock.Unlock()
176
177 def GetFileList(self):
178 """Return the list of all config files.
179
180 This is used for computing node replication data.
181
182 """
183 return [self.KeyToFilename(key) for key in _VALID_KEYS]
184
185 def GetClusterName(self):
186 """Get the cluster name.
187
188 """
189 return self._ReadFile(constants.SS_CLUSTER_NAME)
190
191 def GetFileStorageDir(self):
192 """Get the file storage dir.
193
194 """
195 return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
196
197 def GetSharedFileStorageDir(self):
198 """Get the shared file storage dir.
199
200 """
201 return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
202
203 def GetGlusterStorageDir(self):
204 """Get the Gluster storage dir.
205
206 """
207 return self._ReadFile(constants.SS_GLUSTER_STORAGE_DIR)
208
209 def GetMasterCandidates(self):
210 """Return the list of master candidates.
211
212 """
213 data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
214 nl = data.splitlines(False)
215 return nl
216
217 def GetMasterCandidatesIPList(self):
218 """Return the list of master candidates' primary IP.
219
220 """
221 data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
222 nl = data.splitlines(False)
223 return nl
224
225 def _GetDictOfSsconfMap(self, ss_file_key):
226 """Reads a file with lines like key=value and returns a dict.
227
228 This utility function reads a file containing ssconf values of
229 the form "key=value", splits the lines at "=" and returns a
230 dictionary mapping the keys to the values.
231
232 @type ss_file_key: string
233 @param ss_file_key: the constant referring to an ssconf file
234 @rtype: dict of string to string
235 @return: a dictionary mapping the keys to the values
236
237 """
238 data = self._ReadFile(ss_file_key)
239 lines = data.splitlines(False)
240 mapping = {}
241 for line in lines:
242 (key, value) = line.split("=")
243 mapping[key] = value
244 return mapping
245
246 def GetMasterCandidatesCertMap(self):
247 """Returns the map of master candidate UUIDs to ssl cert.
248
249 @rtype: dict of string to string
250 @return: dictionary mapping the master candidates' UUIDs
251 to their SSL certificate digests
252
253 """
254 return self._GetDictOfSsconfMap(constants.SS_MASTER_CANDIDATES_CERTS)
255
256 def GetSshPortMap(self):
257 """Returns the map of node names to SSH port.
258
259 @rtype: dict of string to string
260 @return: dictionary mapping the node names to their SSH port
261
262 """
263 return dict([(node_name, int(ssh_port)) for
264 node_name, ssh_port in
265 self._GetDictOfSsconfMap(constants.SS_SSH_PORTS).items()])
266
267 def GetMasterIP(self):
268 """Get the IP of the master node for this cluster.
269
270 """
271 return self._ReadFile(constants.SS_MASTER_IP)
272
273 def GetMasterNetdev(self):
274 """Get the netdev to which we'll add the master ip.
275
276 """
277 return self._ReadFile(constants.SS_MASTER_NETDEV)
278
279 def GetMasterNetmask(self):
280 """Get the master netmask.
281
282 """
283 try:
284 return self._ReadFile(constants.SS_MASTER_NETMASK)
285 except errors.ConfigurationError:
286 family = self.GetPrimaryIPFamily()
287 ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
288 return ipcls.iplen
289
290 def GetMasterNode(self):
291 """Get the hostname of the master node for this cluster.
292
293 """
294 return self._ReadFile(constants.SS_MASTER_NODE)
295
296 def GetNodeList(self):
297 """Return the list of cluster nodes.
298
299 """
300 data = self._ReadFile(constants.SS_NODE_LIST)
301 nl = data.splitlines(False)
302 return nl
303
304 def GetOnlineNodeList(self):
305 """Return the list of online cluster nodes.
306
307 """
308 data = self._ReadFile(constants.SS_ONLINE_NODES)
309 nl = data.splitlines(False)
310 return nl
311
312 def GetNodePrimaryIPList(self):
313 """Return the list of cluster nodes' primary IP.
314
315 """
316 data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
317 nl = data.splitlines(False)
318 return nl
319
320 def GetNodeSecondaryIPList(self):
321 """Return the list of cluster nodes' secondary IP.
322
323 """
324 data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
325 nl = data.splitlines(False)
326 return nl
327
328 def GetNodesVmCapable(self):
329 """Return the cluster nodes' vm capable value.
330
331 @rtype: dict of string to bool
332 @return: mapping of node names to vm capable values
333
334 """
335 data = self._ReadFile(constants.SS_NODE_VM_CAPABLE)
336 vm_capable = {}
337 for line in data.splitlines(False):
338 (node_uuid, node_vm_capable) = line.split("=")
339 vm_capable[node_uuid] = node_vm_capable == "True"
340 return vm_capable
341
342 def GetNodegroupList(self):
343 """Return the list of nodegroups.
344
345 """
346 data = self._ReadFile(constants.SS_NODEGROUPS)
347 nl = data.splitlines(False)
348 return nl
349
350 def GetNetworkList(self):
351 """Return the list of networks.
352
353 """
354 data = self._ReadFile(constants.SS_NETWORKS)
355 nl = data.splitlines(False)
356 return nl
357
358 def GetClusterTags(self):
359 """Return the cluster tags.
360
361 """
362 data = self._ReadFile(constants.SS_CLUSTER_TAGS)
363 nl = data.splitlines(False)
364 return nl
365
366 def GetHypervisorList(self):
367 """Return the list of enabled hypervisors.
368
369 """
370 data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
371 nl = data.splitlines(False)
372 return nl
373
374 def GetHvparamsForHypervisor(self, hvname):
375 """Return the hypervisor parameters of the given hypervisor.
376
377 @type hvname: string
378 @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
379 @rtype: dict of strings
380 @returns: dictionary with hypervisor parameters
381
382 """
383 return self._GetDictOfSsconfMap(constants.SS_HVPARAMS_PREF + hvname)
384
385 def GetHvparams(self):
386 """Return the hypervisor parameters of all hypervisors.
387
388 @rtype: dict of dict of strings
389 @returns: dictionary mapping hypervisor names to hvparams
390
391 """
392 all_hvparams = {}
393 for hv in constants.HYPER_TYPES:
394 all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
395 return all_hvparams
396
397 def GetMaintainNodeHealth(self):
398 """Return the value of the maintain_node_health option.
399
400 """
401 data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
402 # we rely on the bool serialization here
403 return data == "True"
404
405 def GetUidPool(self):
406 """Return the user-id pool definition string.
407
408 The separator character is a newline.
409
410 The return value can be parsed using uidpool.ParseUidPool()::
411
412 ss = ssconf.SimpleStore()
413 uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
414
415 """
416 data = self._ReadFile(constants.SS_UID_POOL)
417 return data
418
419 def GetPrimaryIPFamily(self):
420 """Return the cluster-wide primary address family.
421
422 """
423 try:
424 return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
425 default=netutils.IP4Address.family))
426 except (ValueError, TypeError), err:
427 raise errors.ConfigurationError("Error while trying to parse primary IP"
428 " family: %s" % err)
429
430 def GetEnabledUserShutdown(self):
431 """Return whether user shutdown is enabled.
432
433 @rtype: bool
434 @return: 'True' if user shutdown is enabled, 'False' otherwise
435
436 """
437 return self._ReadFile(constants.SS_ENABLED_USER_SHUTDOWN) == "True"
438
439
440 def WriteSsconfFiles(values, dry_run=False):
441 """Update all ssconf files.
442
443 Wrapper around L{SimpleStore.WriteFiles}.
444
445 """
446 SimpleStore().WriteFiles(values, dry_run=dry_run)
447
448
449 def GetMasterAndMyself(ss=None):
450 """Get the master node and my own hostname.
451
452 This can be either used for a 'soft' check (compared to CheckMaster,
453 which exits) or just for computing both at the same time.
454
455 The function does not handle any errors, these should be handled in
456 the caller (errors.ConfigurationError, errors.ResolverError).
457
458 @param ss: either a sstore.SimpleConfigReader or a
459 sstore.SimpleStore instance
460 @rtype: tuple
461 @return: a tuple (master node name, my own name)
462
463 """
464 if ss is None:
465 ss = SimpleStore()
466 return ss.GetMasterNode(), netutils.Hostname.GetSysName()
467
468
469 def CheckMaster(debug, ss=None):
470 """Checks the node setup.
471
472 If this is the master, the function will return. Otherwise it will
473 exit with an exit code based on the node status.
474
475 """
476 try:
477 master_name, myself = GetMasterAndMyself(ss)
478 except errors.ConfigurationError, err:
479 print "Cluster configuration incomplete: '%s'" % str(err)
480 sys.exit(constants.EXIT_NODESETUP_ERROR)
481 except errors.ResolverError, err:
482 sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
483 sys.exit(constants.EXIT_NODESETUP_ERROR)
484
485 if myself != master_name:
486 if debug:
487 sys.stderr.write("Not master, exiting.\n")
488 sys.exit(constants.EXIT_NOTMASTER)
489
490
491 def VerifyClusterName(name, _cfg_location=None):
492 """Verifies cluster name against a local cluster name.
493
494 @type name: string
495 @param name: Cluster name
496
497 """
498 sstore = SimpleStore(cfg_location=_cfg_location)
499
500 try:
501 local_name = sstore.GetClusterName()
502 except errors.ConfigurationError, err:
503 logging.debug("Can't get local cluster name: %s", err)
504 else:
505 if name != local_name:
506 raise errors.GenericError("Current cluster name is '%s'" % local_name)
507
508
509 def VerifyKeys(keys):
510 """Raises an exception if unknown ssconf keys are given.
511
512 @type keys: sequence
513 @param keys: Key names to verify
514 @raise errors.GenericError: When invalid keys were found
515
516 """
517 invalid = frozenset(keys) - _VALID_KEYS
518 if invalid:
519 raise errors.GenericError("Invalid ssconf keys: %s" %
520 utils.CommaJoin(sorted(invalid)))