Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 61e062dd

History | View | Annotate | Download (11.5 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, _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)))