Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ d3e6fd0e

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

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

    
80

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

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

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

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

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

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

    
102

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

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

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

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

    
120
    self._lockfile = _lockfile
121

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

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

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

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

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

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

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

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

155
    """
156
    result = []
157

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

    
167
    return dict(result)
168

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

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

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

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

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

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

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

201
    This is used for computing node replication data.
202

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

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

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

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

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

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

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

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

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

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

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

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

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

    
246
  def GetMasterIP(self):
247
    """Get the IP of the master node for this cluster.
248

249
    """
250
    return self._ReadFile(constants.SS_MASTER_IP)
251

    
252
  def GetMasterNetdev(self):
253
    """Get the netdev to which we'll add the master ip.
254

255
    """
256
    return self._ReadFile(constants.SS_MASTER_NETDEV)
257

    
258
  def GetMasterNetmask(self):
259
    """Get the master netmask.
260

261
    """
262
    try:
263
      return self._ReadFile(constants.SS_MASTER_NETMASK)
264
    except errors.ConfigurationError:
265
      family = self.GetPrimaryIPFamily()
266
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
267
      return ipcls.iplen
268

    
269
  def GetMasterNode(self):
270
    """Get the hostname of the master node for this cluster.
271

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

    
275
  def GetNodeList(self):
276
    """Return the list of cluster nodes.
277

278
    """
279
    data = self._ReadFile(constants.SS_NODE_LIST)
280
    nl = data.splitlines(False)
281
    return nl
282

    
283
  def GetOnlineNodeList(self):
284
    """Return the list of online cluster nodes.
285

286
    """
287
    data = self._ReadFile(constants.SS_ONLINE_NODES)
288
    nl = data.splitlines(False)
289
    return nl
290

    
291
  def GetNodePrimaryIPList(self):
292
    """Return the list of cluster nodes' primary IP.
293

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

    
299
  def GetNodeSecondaryIPList(self):
300
    """Return the list of cluster nodes' secondary IP.
301

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

    
307
  def GetNodegroupList(self):
308
    """Return the list of nodegroups.
309

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

    
315
  def GetNetworkList(self):
316
    """Return the list of networks.
317

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

    
323
  def GetClusterTags(self):
324
    """Return the cluster tags.
325

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

    
331
  def GetHypervisorList(self):
332
    """Return the list of enabled hypervisors.
333

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

    
339
  def GetHvparamsForHypervisor(self, hvname):
340
    """Return the hypervisor parameters of the given hypervisor.
341

342
    @type hvname: string
343
    @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
344
    @rtype: dict of strings
345
    @returns: dictionary with hypervisor parameters
346

347
    """
348
    data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname)
349
    lines = data.splitlines(False)
350
    hvparams = {}
351
    for line in lines:
352
      (key, value) = line.split("=")
353
      hvparams[key] = value
354
    return hvparams
355

    
356
  def GetHvparams(self):
357
    """Return the hypervisor parameters of all hypervisors.
358

359
    @rtype: dict of dict of strings
360
    @returns: dictionary mapping hypervisor names to hvparams
361

362
    """
363
    all_hvparams = {}
364
    for hv in constants.HYPER_TYPES:
365
      all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
366
    return all_hvparams
367

    
368
  def GetMaintainNodeHealth(self):
369
    """Return the value of the maintain_node_health option.
370

371
    """
372
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
373
    # we rely on the bool serialization here
374
    return data == "True"
375

    
376
  def GetUidPool(self):
377
    """Return the user-id pool definition string.
378

379
    The separator character is a newline.
380

381
    The return value can be parsed using uidpool.ParseUidPool()::
382

383
      ss = ssconf.SimpleStore()
384
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
385

386
    """
387
    data = self._ReadFile(constants.SS_UID_POOL)
388
    return data
389

    
390
  def GetPrimaryIPFamily(self):
391
    """Return the cluster-wide primary address family.
392

393
    """
394
    try:
395
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
396
                                default=netutils.IP4Address.family))
397
    except (ValueError, TypeError), err:
398
      raise errors.ConfigurationError("Error while trying to parse primary IP"
399
                                      " family: %s" % err)
400

    
401

    
402
def WriteSsconfFiles(values, dry_run=False):
403
  """Update all ssconf files.
404

405
  Wrapper around L{SimpleStore.WriteFiles}.
406

407
  """
408
  SimpleStore().WriteFiles(values, dry_run=dry_run)
409

    
410

    
411
def GetMasterAndMyself(ss=None):
412
  """Get the master node and my own hostname.
413

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

417
  The function does not handle any errors, these should be handled in
418
  the caller (errors.ConfigurationError, errors.ResolverError).
419

420
  @param ss: either a sstore.SimpleConfigReader or a
421
      sstore.SimpleStore instance
422
  @rtype: tuple
423
  @return: a tuple (master node name, my own name)
424

425
  """
426
  if ss is None:
427
    ss = SimpleStore()
428
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
429

    
430

    
431
def CheckMaster(debug, ss=None):
432
  """Checks the node setup.
433

434
  If this is the master, the function will return. Otherwise it will
435
  exit with an exit code based on the node status.
436

437
  """
438
  try:
439
    master_name, myself = GetMasterAndMyself(ss)
440
  except errors.ConfigurationError, err:
441
    print "Cluster configuration incomplete: '%s'" % str(err)
442
    sys.exit(constants.EXIT_NODESETUP_ERROR)
443
  except errors.ResolverError, err:
444
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
445
    sys.exit(constants.EXIT_NODESETUP_ERROR)
446

    
447
  if myself != master_name:
448
    if debug:
449
      sys.stderr.write("Not master, exiting.\n")
450
    sys.exit(constants.EXIT_NOTMASTER)
451

    
452

    
453
def VerifyClusterName(name, _cfg_location=None):
454
  """Verifies cluster name against a local cluster name.
455

456
  @type name: string
457
  @param name: Cluster name
458

459
  """
460
  sstore = SimpleStore(cfg_location=_cfg_location)
461

    
462
  try:
463
    local_name = sstore.GetClusterName()
464
  except errors.ConfigurationError, err:
465
    logging.debug("Can't get local cluster name: %s", err)
466
  else:
467
    if name != local_name:
468
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
469

    
470

    
471
def VerifyKeys(keys):
472
  """Raises an exception if unknown ssconf keys are given.
473

474
  @type keys: sequence
475
  @param keys: Key names to verify
476
  @raise errors.GenericError: When invalid keys were found
477

478
  """
479
  invalid = frozenset(keys) - _VALID_KEYS
480
  if invalid:
481
    raise errors.GenericError("Invalid ssconf keys: %s" %
482
                              utils.CommaJoin(sorted(invalid)))