Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 29a32ce5

History | View | Annotate | Download (10.8 kB)

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):
107
    if cfg_location is None:
108
      self._cfg_dir = pathutils.DATA_DIR
109
    else:
110
      self._cfg_dir = cfg_location
111

    
112
  def KeyToFilename(self, key):
113
    """Convert a given key into filename.
114

115
    """
116
    if key not in _VALID_KEYS:
117
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
118
                                   % str(key))
119

    
120
    filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
121
    return filename
122

    
123
  def _ReadFile(self, key, default=None):
124
    """Generic routine to read keys.
125

126
    This will read the file which holds the value requested. Errors
127
    will be changed into ConfigurationErrors.
128

129
    """
130
    filename = self.KeyToFilename(key)
131
    try:
132
      return ReadSsconfFile(filename)
133
    except EnvironmentError, err:
134
      if err.errno == errno.ENOENT and default is not None:
135
        return default
136
      raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
137
                                      (filename, str(err)))
138

    
139
  def WriteFiles(self, values):
140
    """Writes ssconf files used by external scripts.
141

142
    @type values: dict
143
    @param values: Dictionary of (name, value)
144

145
    """
146
    ssconf_lock = utils.FileLock.Open(pathutils.SSCONF_LOCK_FILE)
147

    
148
    # Get lock while writing files
149
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
150
    try:
151
      for name, value in values.iteritems():
152
        if value and not value.endswith("\n"):
153
          value += "\n"
154
        if len(value) > _MAX_SIZE:
155
          raise errors.ConfigurationError("ssconf file %s above maximum size" %
156
                                          name)
157
        utils.WriteFile(self.KeyToFilename(name), data=value,
158
                        mode=constants.SS_FILE_PERMS)
159
    finally:
160
      ssconf_lock.Unlock()
161

    
162
  def GetFileList(self):
163
    """Return the list of all config files.
164

165
    This is used for computing node replication data.
166

167
    """
168
    return [self.KeyToFilename(key) for key in _VALID_KEYS]
169

    
170
  def GetClusterName(self):
171
    """Get the cluster name.
172

173
    """
174
    return self._ReadFile(constants.SS_CLUSTER_NAME)
175

    
176
  def GetFileStorageDir(self):
177
    """Get the file storage dir.
178

179
    """
180
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
181

    
182
  def GetSharedFileStorageDir(self):
183
    """Get the shared file storage dir.
184

185
    """
186
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
187

    
188
  def GetMasterCandidates(self):
189
    """Return the list of master candidates.
190

191
    """
192
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
193
    nl = data.splitlines(False)
194
    return nl
195

    
196
  def GetMasterCandidatesIPList(self):
197
    """Return the list of master candidates' primary IP.
198

199
    """
200
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
201
    nl = data.splitlines(False)
202
    return nl
203

    
204
  def GetMasterIP(self):
205
    """Get the IP of the master node for this cluster.
206

207
    """
208
    return self._ReadFile(constants.SS_MASTER_IP)
209

    
210
  def GetMasterNetdev(self):
211
    """Get the netdev to which we'll add the master ip.
212

213
    """
214
    return self._ReadFile(constants.SS_MASTER_NETDEV)
215

    
216
  def GetMasterNetmask(self):
217
    """Get the master netmask.
218

219
    """
220
    try:
221
      return self._ReadFile(constants.SS_MASTER_NETMASK)
222
    except errors.ConfigurationError:
223
      family = self.GetPrimaryIPFamily()
224
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
225
      return ipcls.iplen
226

    
227
  def GetMasterNode(self):
228
    """Get the hostname of the master node for this cluster.
229

230
    """
231
    return self._ReadFile(constants.SS_MASTER_NODE)
232

    
233
  def GetNodeList(self):
234
    """Return the list of cluster nodes.
235

236
    """
237
    data = self._ReadFile(constants.SS_NODE_LIST)
238
    nl = data.splitlines(False)
239
    return nl
240

    
241
  def GetNodePrimaryIPList(self):
242
    """Return the list of cluster nodes' primary IP.
243

244
    """
245
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
246
    nl = data.splitlines(False)
247
    return nl
248

    
249
  def GetNodeSecondaryIPList(self):
250
    """Return the list of cluster nodes' secondary IP.
251

252
    """
253
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
254
    nl = data.splitlines(False)
255
    return nl
256

    
257
  def GetNodegroupList(self):
258
    """Return the list of nodegroups.
259

260
    """
261
    data = self._ReadFile(constants.SS_NODEGROUPS)
262
    nl = data.splitlines(False)
263
    return nl
264

    
265
  def GetNetworkList(self):
266
    """Return the list of networks.
267

268
    """
269
    data = self._ReadFile(constants.SS_NETWORKS)
270
    nl = data.splitlines(False)
271
    return nl
272

    
273
  def GetClusterTags(self):
274
    """Return the cluster tags.
275

276
    """
277
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
278
    nl = data.splitlines(False)
279
    return nl
280

    
281
  def GetHypervisorList(self):
282
    """Return the list of enabled hypervisors.
283

284
    """
285
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
286
    nl = data.splitlines(False)
287
    return nl
288

    
289
  def GetMaintainNodeHealth(self):
290
    """Return the value of the maintain_node_health option.
291

292
    """
293
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
294
    # we rely on the bool serialization here
295
    return data == "True"
296

    
297
  def GetUidPool(self):
298
    """Return the user-id pool definition string.
299

300
    The separator character is a newline.
301

302
    The return value can be parsed using uidpool.ParseUidPool()::
303

304
      ss = ssconf.SimpleStore()
305
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
306

307
    """
308
    data = self._ReadFile(constants.SS_UID_POOL)
309
    return data
310

    
311
  def GetPrimaryIPFamily(self):
312
    """Return the cluster-wide primary address family.
313

314
    """
315
    try:
316
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
317
                                default=netutils.IP4Address.family))
318
    except (ValueError, TypeError), err:
319
      raise errors.ConfigurationError("Error while trying to parse primary IP"
320
                                      " family: %s" % err)
321

    
322

    
323
def WriteSsconfFiles(values):
324
  """Update all ssconf files.
325

326
  Wrapper around L{SimpleStore.WriteFiles}.
327

328
  """
329
  SimpleStore().WriteFiles(values)
330

    
331

    
332
def GetMasterAndMyself(ss=None):
333
  """Get the master node and my own hostname.
334

335
  This can be either used for a 'soft' check (compared to CheckMaster,
336
  which exits) or just for computing both at the same time.
337

338
  The function does not handle any errors, these should be handled in
339
  the caller (errors.ConfigurationError, errors.ResolverError).
340

341
  @param ss: either a sstore.SimpleConfigReader or a
342
      sstore.SimpleStore instance
343
  @rtype: tuple
344
  @return: a tuple (master node name, my own name)
345

346
  """
347
  if ss is None:
348
    ss = SimpleStore()
349
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
350

    
351

    
352
def CheckMaster(debug, ss=None):
353
  """Checks the node setup.
354

355
  If this is the master, the function will return. Otherwise it will
356
  exit with an exit code based on the node status.
357

358
  """
359
  try:
360
    master_name, myself = GetMasterAndMyself(ss)
361
  except errors.ConfigurationError, err:
362
    print "Cluster configuration incomplete: '%s'" % str(err)
363
    sys.exit(constants.EXIT_NODESETUP_ERROR)
364
  except errors.ResolverError, err:
365
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
366
    sys.exit(constants.EXIT_NODESETUP_ERROR)
367

    
368
  if myself != master_name:
369
    if debug:
370
      sys.stderr.write("Not master, exiting.\n")
371
    sys.exit(constants.EXIT_NOTMASTER)
372

    
373

    
374
def VerifyClusterName(name, _cfg_location=None):
375
  """Verifies cluster name against a local cluster name.
376

377
  @type name: string
378
  @param name: Cluster name
379

380
  """
381
  sstore = SimpleStore(cfg_location=_cfg_location)
382

    
383
  try:
384
    local_name = sstore.GetClusterName()
385
  except errors.ConfigurationError, err:
386
    logging.debug("Can't get local cluster name: %s", err)
387
  else:
388
    if name != local_name:
389
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
390

    
391

    
392
def VerifyKeys(keys):
393
  """Raises an exception if unknown ssconf keys are given.
394

395
  @type keys: sequence
396
  @param keys: Key names to verify
397
  @raise errors.GenericError: When invalid keys were found
398

399
  """
400
  invalid = frozenset(keys) - _VALID_KEYS
401
  if invalid:
402
    raise errors.GenericError("Invalid ssconf keys: %s" %
403
                              utils.CommaJoin(sorted(invalid)))