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