Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 653bc0f1

History | View | Annotate | Download (13.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 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_GLUSTER_STORAGE_DIR,
50
  constants.SS_MASTER_CANDIDATES,
51
  constants.SS_MASTER_CANDIDATES_IPS,
52
  constants.SS_MASTER_CANDIDATES_CERTS,
53
  constants.SS_MASTER_IP,
54
  constants.SS_MASTER_NETDEV,
55
  constants.SS_MASTER_NETMASK,
56
  constants.SS_MASTER_NODE,
57
  constants.SS_NODE_LIST,
58
  constants.SS_NODE_PRIMARY_IPS,
59
  constants.SS_NODE_SECONDARY_IPS,
60
  constants.SS_OFFLINE_NODES,
61
  constants.SS_ONLINE_NODES,
62
  constants.SS_PRIMARY_IP_FAMILY,
63
  constants.SS_INSTANCE_LIST,
64
  constants.SS_RELEASE_VERSION,
65
  constants.SS_HYPERVISOR_LIST,
66
  constants.SS_MAINTAIN_NODE_HEALTH,
67
  constants.SS_UID_POOL,
68
  constants.SS_NODEGROUPS,
69
  constants.SS_NETWORKS,
70
  constants.SS_HVPARAMS_XEN_PVM,
71
  constants.SS_HVPARAMS_XEN_FAKE,
72
  constants.SS_HVPARAMS_XEN_HVM,
73
  constants.SS_HVPARAMS_XEN_KVM,
74
  constants.SS_HVPARAMS_XEN_CHROOT,
75
  constants.SS_HVPARAMS_XEN_LXC,
76
  ])
77

    
78
#: Maximum size for ssconf files
79
_MAX_SIZE = 128 * 1024
80

    
81

    
82
def ReadSsconfFile(filename):
83
  """Reads an ssconf file and verifies its size.
84

85
  @type filename: string
86
  @param filename: Path to file
87
  @rtype: string
88
  @return: File contents without newlines at the end
89
  @raise RuntimeError: When the file size exceeds L{_MAX_SIZE}
90

91
  """
92
  statcb = utils.FileStatHelper()
93

    
94
  data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb)
95

    
96
  if statcb.st.st_size > _MAX_SIZE:
97
    msg = ("File '%s' has a size of %s bytes (up to %s allowed)" %
98
           (filename, statcb.st.st_size, _MAX_SIZE))
99
    raise RuntimeError(msg)
100

    
101
  return data.rstrip("\n")
102

    
103

    
104
class SimpleStore(object):
105
  """Interface to static cluster data.
106

107
  This is different that the config.ConfigWriter and
108
  SimpleConfigReader classes in that it holds data that will always be
109
  present, even on nodes which don't have all the cluster data.
110

111
  Other particularities of the datastore:
112
    - keys are restricted to predefined values
113

114
  """
115
  def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
116
    if cfg_location is None:
117
      self._cfg_dir = pathutils.DATA_DIR
118
    else:
119
      self._cfg_dir = cfg_location
120

    
121
    self._lockfile = _lockfile
122

    
123
  def KeyToFilename(self, key):
124
    """Convert a given key into filename.
125

126
    """
127
    if key not in _VALID_KEYS:
128
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
129
                                   % str(key))
130

    
131
    filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
132
    return filename
133

    
134
  def _ReadFile(self, key, default=None):
135
    """Generic routine to read keys.
136

137
    This will read the file which holds the value requested. Errors
138
    will be changed into ConfigurationErrors.
139

140
    """
141
    filename = self.KeyToFilename(key)
142
    try:
143
      return ReadSsconfFile(filename)
144
    except EnvironmentError, err:
145
      if err.errno == errno.ENOENT and default is not None:
146
        return default
147
      raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
148
                                      (filename, str(err)))
149

    
150
  def ReadAll(self):
151
    """Reads all keys and returns their values.
152

153
    @rtype: dict
154
    @return: Dictionary, ssconf key as key, value as value
155

156
    """
157
    result = []
158

    
159
    for key in _VALID_KEYS:
160
      try:
161
        value = self._ReadFile(key)
162
      except errors.ConfigurationError:
163
        # Ignore non-existing files
164
        pass
165
      else:
166
        result.append((key, value))
167

    
168
    return dict(result)
169

    
170
  def WriteFiles(self, values, dry_run=False):
171
    """Writes ssconf files used by external scripts.
172

173
    @type values: dict
174
    @param values: Dictionary of (name, value)
175
    @type dry_run boolean
176
    @param dry_run: Whether to perform a dry run
177

178
    """
179
    ssconf_lock = utils.FileLock.Open(self._lockfile)
180

    
181
    # Get lock while writing files
182
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
183
    try:
184
      for name, value in values.iteritems():
185
        if value and not value.endswith("\n"):
186
          value += "\n"
187

    
188
        if len(value) > _MAX_SIZE:
189
          msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
190
                 " allowed" % (name, len(value), _MAX_SIZE))
191
          raise errors.ConfigurationError(msg)
192

    
193
        utils.WriteFile(self.KeyToFilename(name), data=value,
194
                        mode=constants.SS_FILE_PERMS,
195
                        dry_run=dry_run)
196
    finally:
197
      ssconf_lock.Unlock()
198

    
199
  def GetFileList(self):
200
    """Return the list of all config files.
201

202
    This is used for computing node replication data.
203

204
    """
205
    return [self.KeyToFilename(key) for key in _VALID_KEYS]
206

    
207
  def GetClusterName(self):
208
    """Get the cluster name.
209

210
    """
211
    return self._ReadFile(constants.SS_CLUSTER_NAME)
212

    
213
  def GetFileStorageDir(self):
214
    """Get the file storage dir.
215

216
    """
217
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
218

    
219
  def GetSharedFileStorageDir(self):
220
    """Get the shared file storage dir.
221

222
    """
223
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
224

    
225
  def GetGlusterStorageDir(self):
226
    """Get the Gluster storage dir.
227

228
    """
229
    return self._ReadFile(constants.SS_GLUSTER_STORAGE_DIR)
230

    
231
  def GetMasterCandidates(self):
232
    """Return the list of master candidates.
233

234
    """
235
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
236
    nl = data.splitlines(False)
237
    return nl
238

    
239
  def GetMasterCandidatesIPList(self):
240
    """Return the list of master candidates' primary IP.
241

242
    """
243
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
244
    nl = data.splitlines(False)
245
    return nl
246

    
247
  def GetMasterCandidatesCertMap(self):
248
    """Returns the map of master candidate UUIDs to ssl cert.
249

250
    @rtype: dict of string to string
251
    @return: dictionary mapping the master candidates' UUIDs
252
      to their SSL certificate digests
253

254
    """
255
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_CERTS)
256
    lines = data.splitlines(False)
257
    certs = {}
258
    for line in lines:
259
      (node_uuid, cert_digest) = line.split("=")
260
      certs[node_uuid] = cert_digest
261
    return certs
262

    
263
  def GetMasterIP(self):
264
    """Get the IP of the master node for this cluster.
265

266
    """
267
    return self._ReadFile(constants.SS_MASTER_IP)
268

    
269
  def GetMasterNetdev(self):
270
    """Get the netdev to which we'll add the master ip.
271

272
    """
273
    return self._ReadFile(constants.SS_MASTER_NETDEV)
274

    
275
  def GetMasterNetmask(self):
276
    """Get the master netmask.
277

278
    """
279
    try:
280
      return self._ReadFile(constants.SS_MASTER_NETMASK)
281
    except errors.ConfigurationError:
282
      family = self.GetPrimaryIPFamily()
283
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
284
      return ipcls.iplen
285

    
286
  def GetMasterNode(self):
287
    """Get the hostname of the master node for this cluster.
288

289
    """
290
    return self._ReadFile(constants.SS_MASTER_NODE)
291

    
292
  def GetNodeList(self):
293
    """Return the list of cluster nodes.
294

295
    """
296
    data = self._ReadFile(constants.SS_NODE_LIST)
297
    nl = data.splitlines(False)
298
    return nl
299

    
300
  def GetOnlineNodeList(self):
301
    """Return the list of online cluster nodes.
302

303
    """
304
    data = self._ReadFile(constants.SS_ONLINE_NODES)
305
    nl = data.splitlines(False)
306
    return nl
307

    
308
  def GetNodePrimaryIPList(self):
309
    """Return the list of cluster nodes' primary IP.
310

311
    """
312
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
313
    nl = data.splitlines(False)
314
    return nl
315

    
316
  def GetNodeSecondaryIPList(self):
317
    """Return the list of cluster nodes' secondary IP.
318

319
    """
320
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
321
    nl = data.splitlines(False)
322
    return nl
323

    
324
  def GetNodegroupList(self):
325
    """Return the list of nodegroups.
326

327
    """
328
    data = self._ReadFile(constants.SS_NODEGROUPS)
329
    nl = data.splitlines(False)
330
    return nl
331

    
332
  def GetNetworkList(self):
333
    """Return the list of networks.
334

335
    """
336
    data = self._ReadFile(constants.SS_NETWORKS)
337
    nl = data.splitlines(False)
338
    return nl
339

    
340
  def GetClusterTags(self):
341
    """Return the cluster tags.
342

343
    """
344
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
345
    nl = data.splitlines(False)
346
    return nl
347

    
348
  def GetHypervisorList(self):
349
    """Return the list of enabled hypervisors.
350

351
    """
352
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
353
    nl = data.splitlines(False)
354
    return nl
355

    
356
  def GetHvparamsForHypervisor(self, hvname):
357
    """Return the hypervisor parameters of the given hypervisor.
358

359
    @type hvname: string
360
    @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
361
    @rtype: dict of strings
362
    @returns: dictionary with hypervisor parameters
363

364
    """
365
    data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname)
366
    lines = data.splitlines(False)
367
    hvparams = {}
368
    for line in lines:
369
      (key, value) = line.split("=")
370
      hvparams[key] = value
371
    return hvparams
372

    
373
  def GetHvparams(self):
374
    """Return the hypervisor parameters of all hypervisors.
375

376
    @rtype: dict of dict of strings
377
    @returns: dictionary mapping hypervisor names to hvparams
378

379
    """
380
    all_hvparams = {}
381
    for hv in constants.HYPER_TYPES:
382
      all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
383
    return all_hvparams
384

    
385
  def GetMaintainNodeHealth(self):
386
    """Return the value of the maintain_node_health option.
387

388
    """
389
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
390
    # we rely on the bool serialization here
391
    return data == "True"
392

    
393
  def GetUidPool(self):
394
    """Return the user-id pool definition string.
395

396
    The separator character is a newline.
397

398
    The return value can be parsed using uidpool.ParseUidPool()::
399

400
      ss = ssconf.SimpleStore()
401
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
402

403
    """
404
    data = self._ReadFile(constants.SS_UID_POOL)
405
    return data
406

    
407
  def GetPrimaryIPFamily(self):
408
    """Return the cluster-wide primary address family.
409

410
    """
411
    try:
412
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
413
                                default=netutils.IP4Address.family))
414
    except (ValueError, TypeError), err:
415
      raise errors.ConfigurationError("Error while trying to parse primary IP"
416
                                      " family: %s" % err)
417

    
418

    
419
def WriteSsconfFiles(values, dry_run=False):
420
  """Update all ssconf files.
421

422
  Wrapper around L{SimpleStore.WriteFiles}.
423

424
  """
425
  SimpleStore().WriteFiles(values, dry_run=dry_run)
426

    
427

    
428
def GetMasterAndMyself(ss=None):
429
  """Get the master node and my own hostname.
430

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

434
  The function does not handle any errors, these should be handled in
435
  the caller (errors.ConfigurationError, errors.ResolverError).
436

437
  @param ss: either a sstore.SimpleConfigReader or a
438
      sstore.SimpleStore instance
439
  @rtype: tuple
440
  @return: a tuple (master node name, my own name)
441

442
  """
443
  if ss is None:
444
    ss = SimpleStore()
445
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
446

    
447

    
448
def CheckMaster(debug, ss=None):
449
  """Checks the node setup.
450

451
  If this is the master, the function will return. Otherwise it will
452
  exit with an exit code based on the node status.
453

454
  """
455
  try:
456
    master_name, myself = GetMasterAndMyself(ss)
457
  except errors.ConfigurationError, err:
458
    print "Cluster configuration incomplete: '%s'" % str(err)
459
    sys.exit(constants.EXIT_NODESETUP_ERROR)
460
  except errors.ResolverError, err:
461
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
462
    sys.exit(constants.EXIT_NODESETUP_ERROR)
463

    
464
  if myself != master_name:
465
    if debug:
466
      sys.stderr.write("Not master, exiting.\n")
467
    sys.exit(constants.EXIT_NOTMASTER)
468

    
469

    
470
def VerifyClusterName(name, _cfg_location=None):
471
  """Verifies cluster name against a local cluster name.
472

473
  @type name: string
474
  @param name: Cluster name
475

476
  """
477
  sstore = SimpleStore(cfg_location=_cfg_location)
478

    
479
  try:
480
    local_name = sstore.GetClusterName()
481
  except errors.ConfigurationError, err:
482
    logging.debug("Can't get local cluster name: %s", err)
483
  else:
484
    if name != local_name:
485
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
486

    
487

    
488
def VerifyKeys(keys):
489
  """Raises an exception if unknown ssconf keys are given.
490

491
  @type keys: sequence
492
  @param keys: Key names to verify
493
  @raise errors.GenericError: When invalid keys were found
494

495
  """
496
  invalid = frozenset(keys) - _VALID_KEYS
497
  if invalid:
498
    raise errors.GenericError("Invalid ssconf keys: %s" %
499
                              utils.CommaJoin(sorted(invalid)))