Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 1c4910f7

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 isinstance(value, (list, tuple)):
186
          value = "\n".join(value)
187
        if value and not value.endswith("\n"):
188
          value += "\n"
189

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

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

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

204
    This is used for computing node replication data.
205

206
    """
207
    return [self.KeyToFilename(key) for key in _VALID_KEYS]
208

    
209
  def GetClusterName(self):
210
    """Get the cluster name.
211

212
    """
213
    return self._ReadFile(constants.SS_CLUSTER_NAME)
214

    
215
  def GetFileStorageDir(self):
216
    """Get the file storage dir.
217

218
    """
219
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
220

    
221
  def GetSharedFileStorageDir(self):
222
    """Get the shared file storage dir.
223

224
    """
225
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
226

    
227
  def GetGlusterStorageDir(self):
228
    """Get the Gluster storage dir.
229

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

    
233
  def GetMasterCandidates(self):
234
    """Return the list of master candidates.
235

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

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

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

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

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

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

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

268
    """
269
    return self._ReadFile(constants.SS_MASTER_IP)
270

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

274
    """
275
    return self._ReadFile(constants.SS_MASTER_NETDEV)
276

    
277
  def GetMasterNetmask(self):
278
    """Get the master netmask.
279

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

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

291
    """
292
    return self._ReadFile(constants.SS_MASTER_NODE)
293

    
294
  def GetNodeList(self):
295
    """Return the list of cluster nodes.
296

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

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

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

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

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

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

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

    
326
  def GetNodegroupList(self):
327
    """Return the list of nodegroups.
328

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

    
334
  def GetNetworkList(self):
335
    """Return the list of networks.
336

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

    
342
  def GetClusterTags(self):
343
    """Return the cluster tags.
344

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

    
350
  def GetHypervisorList(self):
351
    """Return the list of enabled hypervisors.
352

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

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

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

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

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

378
    @rtype: dict of dict of strings
379
    @returns: dictionary mapping hypervisor names to hvparams
380

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

    
387
  def GetMaintainNodeHealth(self):
388
    """Return the value of the maintain_node_health option.
389

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

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

398
    The separator character is a newline.
399

400
    The return value can be parsed using uidpool.ParseUidPool()::
401

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

405
    """
406
    data = self._ReadFile(constants.SS_UID_POOL)
407
    return data
408

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

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

    
420

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

424
  Wrapper around L{SimpleStore.WriteFiles}.
425

426
  """
427
  SimpleStore().WriteFiles(values, dry_run=dry_run)
428

    
429

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

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

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

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

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

    
449

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

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

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

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

    
471

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

475
  @type name: string
476
  @param name: Cluster name
477

478
  """
479
  sstore = SimpleStore(cfg_location=_cfg_location)
480

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

    
489

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

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

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