Fix a node name vs. UUID bug in instance import
[ganeti-local] / lib / ssconf.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 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 """Global Configuration data for Ganeti.
23
24 This module provides the interface to a special case of cluster
25 configuration data, which is mostly static and available to all nodes.
26
27 """
28
29 import sys
30 import errno
31 import logging
32
33 from ganeti import compat
34 from ganeti import errors
35 from ganeti import constants
36 from ganeti import utils
37 from ganeti import netutils
38 from ganeti import pathutils
39
40
41 SSCONF_LOCK_TIMEOUT = 10
42
43 #: Valid ssconf keys
44 _VALID_KEYS = compat.UniqueFrozenset([
45   constants.SS_CLUSTER_NAME,
46   constants.SS_CLUSTER_TAGS,
47   constants.SS_FILE_STORAGE_DIR,
48   constants.SS_SHARED_FILE_STORAGE_DIR,
49   constants.SS_MASTER_CANDIDATES,
50   constants.SS_MASTER_CANDIDATES_IPS,
51   constants.SS_MASTER_IP,
52   constants.SS_MASTER_NETDEV,
53   constants.SS_MASTER_NETMASK,
54   constants.SS_MASTER_NODE,
55   constants.SS_NODE_LIST,
56   constants.SS_NODE_PRIMARY_IPS,
57   constants.SS_NODE_SECONDARY_IPS,
58   constants.SS_OFFLINE_NODES,
59   constants.SS_ONLINE_NODES,
60   constants.SS_PRIMARY_IP_FAMILY,
61   constants.SS_INSTANCE_LIST,
62   constants.SS_RELEASE_VERSION,
63   constants.SS_HYPERVISOR_LIST,
64   constants.SS_MAINTAIN_NODE_HEALTH,
65   constants.SS_UID_POOL,
66   constants.SS_NODEGROUPS,
67   constants.SS_NETWORKS,
68   constants.SS_HVPARAMS_XEN_PVM,
69   constants.SS_HVPARAMS_XEN_FAKE,
70   constants.SS_HVPARAMS_XEN_HVM,
71   constants.SS_HVPARAMS_XEN_KVM,
72   constants.SS_HVPARAMS_XEN_CHROOT,
73   constants.SS_HVPARAMS_XEN_LXC,
74   ])
75
76 #: Maximum size for ssconf files
77 _MAX_SIZE = 128 * 1024
78
79
80 def ReadSsconfFile(filename):
81   """Reads an ssconf file and verifies its size.
82
83   @type filename: string
84   @param filename: Path to file
85   @rtype: string
86   @return: File contents without newlines at the end
87   @raise RuntimeError: When the file size exceeds L{_MAX_SIZE}
88
89   """
90   statcb = utils.FileStatHelper()
91
92   data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb)
93
94   if statcb.st.st_size > _MAX_SIZE:
95     msg = ("File '%s' has a size of %s bytes (up to %s allowed)" %
96            (filename, statcb.st.st_size, _MAX_SIZE))
97     raise RuntimeError(msg)
98
99   return data.rstrip("\n")
100
101
102 class SimpleStore(object):
103   """Interface to static cluster data.
104
105   This is different that the config.ConfigWriter and
106   SimpleConfigReader classes in that it holds data that will always be
107   present, even on nodes which don't have all the cluster data.
108
109   Other particularities of the datastore:
110     - keys are restricted to predefined values
111
112   """
113   def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
114     if cfg_location is None:
115       self._cfg_dir = pathutils.DATA_DIR
116     else:
117       self._cfg_dir = cfg_location
118
119     self._lockfile = _lockfile
120
121   def KeyToFilename(self, key):
122     """Convert a given key into filename.
123
124     """
125     if key not in _VALID_KEYS:
126       raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
127                                    % str(key))
128
129     filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
130     return filename
131
132   def _ReadFile(self, key, default=None):
133     """Generic routine to read keys.
134
135     This will read the file which holds the value requested. Errors
136     will be changed into ConfigurationErrors.
137
138     """
139     filename = self.KeyToFilename(key)
140     try:
141       return ReadSsconfFile(filename)
142     except EnvironmentError, err:
143       if err.errno == errno.ENOENT and default is not None:
144         return default
145       raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
146                                       (filename, str(err)))
147
148   def ReadAll(self):
149     """Reads all keys and returns their values.
150
151     @rtype: dict
152     @return: Dictionary, ssconf key as key, value as value
153
154     """
155     result = []
156
157     for key in _VALID_KEYS:
158       try:
159         value = self._ReadFile(key)
160       except errors.ConfigurationError:
161         # Ignore non-existing files
162         pass
163       else:
164         result.append((key, value))
165
166     return dict(result)
167
168   def WriteFiles(self, values, dry_run=False):
169     """Writes ssconf files used by external scripts.
170
171     @type values: dict
172     @param values: Dictionary of (name, value)
173     @type dry_run boolean
174     @param dry_run: Whether to perform a dry run
175
176     """
177     ssconf_lock = utils.FileLock.Open(self._lockfile)
178
179     # Get lock while writing files
180     ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
181     try:
182       for name, value in values.iteritems():
183         if value and not value.endswith("\n"):
184           value += "\n"
185
186         if len(value) > _MAX_SIZE:
187           msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
188                  " allowed" % (name, len(value), _MAX_SIZE))
189           raise errors.ConfigurationError(msg)
190
191         utils.WriteFile(self.KeyToFilename(name), data=value,
192                         mode=constants.SS_FILE_PERMS,
193                         dry_run=dry_run)
194     finally:
195       ssconf_lock.Unlock()
196
197   def GetFileList(self):
198     """Return the list of all config files.
199
200     This is used for computing node replication data.
201
202     """
203     return [self.KeyToFilename(key) for key in _VALID_KEYS]
204
205   def GetClusterName(self):
206     """Get the cluster name.
207
208     """
209     return self._ReadFile(constants.SS_CLUSTER_NAME)
210
211   def GetFileStorageDir(self):
212     """Get the file storage dir.
213
214     """
215     return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
216
217   def GetSharedFileStorageDir(self):
218     """Get the shared file storage dir.
219
220     """
221     return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
222
223   def GetMasterCandidates(self):
224     """Return the list of master candidates.
225
226     """
227     data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
228     nl = data.splitlines(False)
229     return nl
230
231   def GetMasterCandidatesIPList(self):
232     """Return the list of master candidates' primary IP.
233
234     """
235     data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
236     nl = data.splitlines(False)
237     return nl
238
239   def GetMasterIP(self):
240     """Get the IP of the master node for this cluster.
241
242     """
243     return self._ReadFile(constants.SS_MASTER_IP)
244
245   def GetMasterNetdev(self):
246     """Get the netdev to which we'll add the master ip.
247
248     """
249     return self._ReadFile(constants.SS_MASTER_NETDEV)
250
251   def GetMasterNetmask(self):
252     """Get the master netmask.
253
254     """
255     try:
256       return self._ReadFile(constants.SS_MASTER_NETMASK)
257     except errors.ConfigurationError:
258       family = self.GetPrimaryIPFamily()
259       ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
260       return ipcls.iplen
261
262   def GetMasterNode(self):
263     """Get the hostname of the master node for this cluster.
264
265     """
266     return self._ReadFile(constants.SS_MASTER_NODE)
267
268   def GetNodeList(self):
269     """Return the list of cluster nodes.
270
271     """
272     data = self._ReadFile(constants.SS_NODE_LIST)
273     nl = data.splitlines(False)
274     return nl
275
276   def GetNodePrimaryIPList(self):
277     """Return the list of cluster nodes' primary IP.
278
279     """
280     data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
281     nl = data.splitlines(False)
282     return nl
283
284   def GetNodeSecondaryIPList(self):
285     """Return the list of cluster nodes' secondary IP.
286
287     """
288     data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
289     nl = data.splitlines(False)
290     return nl
291
292   def GetNodegroupList(self):
293     """Return the list of nodegroups.
294
295     """
296     data = self._ReadFile(constants.SS_NODEGROUPS)
297     nl = data.splitlines(False)
298     return nl
299
300   def GetNetworkList(self):
301     """Return the list of networks.
302
303     """
304     data = self._ReadFile(constants.SS_NETWORKS)
305     nl = data.splitlines(False)
306     return nl
307
308   def GetClusterTags(self):
309     """Return the cluster tags.
310
311     """
312     data = self._ReadFile(constants.SS_CLUSTER_TAGS)
313     nl = data.splitlines(False)
314     return nl
315
316   def GetHypervisorList(self):
317     """Return the list of enabled hypervisors.
318
319     """
320     data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
321     nl = data.splitlines(False)
322     return nl
323
324   def GetHvparamsForHypervisor(self, hvname):
325     """Return the hypervisor parameters of the given hypervisor.
326
327     @type hvname: string
328     @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
329     @rtype: dict of strings
330     @returns: dictionary with hypervisor parameters
331
332     """
333     data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname)
334     lines = data.splitlines(False)
335     hvparams = {}
336     for line in lines:
337       (key, value) = line.split("=")
338       hvparams[key] = value
339     return hvparams
340
341   def GetHvparams(self):
342     """Return the hypervisor parameters of all hypervisors.
343
344     @rtype: dict of dict of strings
345     @returns: dictionary mapping hypervisor names to hvparams
346
347     """
348     all_hvparams = {}
349     for hv in constants.HYPER_TYPES:
350       all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
351     return all_hvparams
352
353   def GetMaintainNodeHealth(self):
354     """Return the value of the maintain_node_health option.
355
356     """
357     data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
358     # we rely on the bool serialization here
359     return data == "True"
360
361   def GetUidPool(self):
362     """Return the user-id pool definition string.
363
364     The separator character is a newline.
365
366     The return value can be parsed using uidpool.ParseUidPool()::
367
368       ss = ssconf.SimpleStore()
369       uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
370
371     """
372     data = self._ReadFile(constants.SS_UID_POOL)
373     return data
374
375   def GetPrimaryIPFamily(self):
376     """Return the cluster-wide primary address family.
377
378     """
379     try:
380       return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
381                                 default=netutils.IP4Address.family))
382     except (ValueError, TypeError), err:
383       raise errors.ConfigurationError("Error while trying to parse primary IP"
384                                       " family: %s" % err)
385
386
387 def WriteSsconfFiles(values, dry_run=False):
388   """Update all ssconf files.
389
390   Wrapper around L{SimpleStore.WriteFiles}.
391
392   """
393   SimpleStore().WriteFiles(values, dry_run=dry_run)
394
395
396 def GetMasterAndMyself(ss=None):
397   """Get the master node and my own hostname.
398
399   This can be either used for a 'soft' check (compared to CheckMaster,
400   which exits) or just for computing both at the same time.
401
402   The function does not handle any errors, these should be handled in
403   the caller (errors.ConfigurationError, errors.ResolverError).
404
405   @param ss: either a sstore.SimpleConfigReader or a
406       sstore.SimpleStore instance
407   @rtype: tuple
408   @return: a tuple (master node name, my own name)
409
410   """
411   if ss is None:
412     ss = SimpleStore()
413   return ss.GetMasterNode(), netutils.Hostname.GetSysName()
414
415
416 def CheckMaster(debug, ss=None):
417   """Checks the node setup.
418
419   If this is the master, the function will return. Otherwise it will
420   exit with an exit code based on the node status.
421
422   """
423   try:
424     master_name, myself = GetMasterAndMyself(ss)
425   except errors.ConfigurationError, err:
426     print "Cluster configuration incomplete: '%s'" % str(err)
427     sys.exit(constants.EXIT_NODESETUP_ERROR)
428   except errors.ResolverError, err:
429     sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
430     sys.exit(constants.EXIT_NODESETUP_ERROR)
431
432   if myself != master_name:
433     if debug:
434       sys.stderr.write("Not master, exiting.\n")
435     sys.exit(constants.EXIT_NOTMASTER)
436
437
438 def VerifyClusterName(name, _cfg_location=None):
439   """Verifies cluster name against a local cluster name.
440
441   @type name: string
442   @param name: Cluster name
443
444   """
445   sstore = SimpleStore(cfg_location=_cfg_location)
446
447   try:
448     local_name = sstore.GetClusterName()
449   except errors.ConfigurationError, err:
450     logging.debug("Can't get local cluster name: %s", err)
451   else:
452     if name != local_name:
453       raise errors.GenericError("Current cluster name is '%s'" % local_name)
454
455
456 def VerifyKeys(keys):
457   """Raises an exception if unknown ssconf keys are given.
458
459   @type keys: sequence
460   @param keys: Key names to verify
461   @raise errors.GenericError: When invalid keys were found
462
463   """
464   invalid = frozenset(keys) - _VALID_KEYS
465   if invalid:
466     raise errors.GenericError("Invalid ssconf keys: %s" %
467                               utils.CommaJoin(sorted(invalid)))