Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ def6577f

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

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

    
79

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

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

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

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

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

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

    
101

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

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

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

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

    
119
    self._lockfile = _lockfile
120

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

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

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

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

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

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

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

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

154
    """
155
    result = []
156

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

    
166
    return dict(result)
167

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

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

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

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

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

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

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

200
    This is used for computing node replication data.
201

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

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

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

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

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

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

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

    
223
  def GetMasterCandidates(self):
224
    """Return the list of master candidates.
225

226
    """
227
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
228
    nl = data.splitlines(False)
229
    return nl
230

    
231
  def GetMasterCandidatesIPList(self):
232
    """Return the list of master candidates' primary IP.
233

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

    
239
  def GetMasterIP(self):
240
    """Get the IP of the master node for this cluster.
241

242
    """
243
    return self._ReadFile(constants.SS_MASTER_IP)
244

    
245
  def GetMasterNetdev(self):
246
    """Get the netdev to which we'll add the master ip.
247

248
    """
249
    return self._ReadFile(constants.SS_MASTER_NETDEV)
250

    
251
  def GetMasterNetmask(self):
252
    """Get the master netmask.
253

254
    """
255
    try:
256
      return self._ReadFile(constants.SS_MASTER_NETMASK)
257
    except errors.ConfigurationError:
258
      family = self.GetPrimaryIPFamily()
259
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
260
      return ipcls.iplen
261

    
262
  def GetMasterNode(self):
263
    """Get the hostname of the master node for this cluster.
264

265
    """
266
    return self._ReadFile(constants.SS_MASTER_NODE)
267

    
268
  def GetNodeList(self):
269
    """Return the list of cluster nodes.
270

271
    """
272
    data = self._ReadFile(constants.SS_NODE_LIST)
273
    nl = data.splitlines(False)
274
    return nl
275

    
276
  def GetNodePrimaryIPList(self):
277
    """Return the list of cluster nodes' primary IP.
278

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

    
284
  def GetNodeSecondaryIPList(self):
285
    """Return the list of cluster nodes' secondary IP.
286

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

    
292
  def GetNodegroupList(self):
293
    """Return the list of nodegroups.
294

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

    
300
  def GetNetworkList(self):
301
    """Return the list of networks.
302

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

    
308
  def GetClusterTags(self):
309
    """Return the cluster tags.
310

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

    
316
  def GetHypervisorList(self):
317
    """Return the list of enabled hypervisors.
318

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

    
324
  def GetHvparamsForHypervisor(self, hvname):
325
    """Return the hypervisor parameters of the given hypervisor.
326

327
    @type hvname: string
328
    @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
329
    @rtype: dict of strings
330
    @returns: dictionary with hypervisor parameters
331

332
    """
333
    data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname)
334
    lines = data.splitlines(False)
335
    hvparams = {}
336
    for line in lines:
337
      (key, value) = line.split("=")
338
      hvparams[key] = value
339
    return hvparams
340

    
341
  def GetHvparams(self):
342
    """Return the hypervisor parameters of all hypervisors.
343

344
    @rtype: dict of dict of strings
345
    @returns: dictionary mapping hypervisor names to hvparams
346

347
    """
348
    all_hvparams = {}
349
    for hv in constants.HYPER_TYPES:
350
      all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
351
    return all_hvparams
352

    
353
  def GetMaintainNodeHealth(self):
354
    """Return the value of the maintain_node_health option.
355

356
    """
357
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
358
    # we rely on the bool serialization here
359
    return data == "True"
360

    
361
  def GetUidPool(self):
362
    """Return the user-id pool definition string.
363

364
    The separator character is a newline.
365

366
    The return value can be parsed using uidpool.ParseUidPool()::
367

368
      ss = ssconf.SimpleStore()
369
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
370

371
    """
372
    data = self._ReadFile(constants.SS_UID_POOL)
373
    return data
374

    
375
  def GetPrimaryIPFamily(self):
376
    """Return the cluster-wide primary address family.
377

378
    """
379
    try:
380
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
381
                                default=netutils.IP4Address.family))
382
    except (ValueError, TypeError), err:
383
      raise errors.ConfigurationError("Error while trying to parse primary IP"
384
                                      " family: %s" % err)
385

    
386

    
387
def WriteSsconfFiles(values, dry_run=False):
388
  """Update all ssconf files.
389

390
  Wrapper around L{SimpleStore.WriteFiles}.
391

392
  """
393
  SimpleStore().WriteFiles(values, dry_run=dry_run)
394

    
395

    
396
def GetMasterAndMyself(ss=None):
397
  """Get the master node and my own hostname.
398

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

402
  The function does not handle any errors, these should be handled in
403
  the caller (errors.ConfigurationError, errors.ResolverError).
404

405
  @param ss: either a sstore.SimpleConfigReader or a
406
      sstore.SimpleStore instance
407
  @rtype: tuple
408
  @return: a tuple (master node name, my own name)
409

410
  """
411
  if ss is None:
412
    ss = SimpleStore()
413
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
414

    
415

    
416
def CheckMaster(debug, ss=None):
417
  """Checks the node setup.
418

419
  If this is the master, the function will return. Otherwise it will
420
  exit with an exit code based on the node status.
421

422
  """
423
  try:
424
    master_name, myself = GetMasterAndMyself(ss)
425
  except errors.ConfigurationError, err:
426
    print "Cluster configuration incomplete: '%s'" % str(err)
427
    sys.exit(constants.EXIT_NODESETUP_ERROR)
428
  except errors.ResolverError, err:
429
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
430
    sys.exit(constants.EXIT_NODESETUP_ERROR)
431

    
432
  if myself != master_name:
433
    if debug:
434
      sys.stderr.write("Not master, exiting.\n")
435
    sys.exit(constants.EXIT_NOTMASTER)
436

    
437

    
438
def VerifyClusterName(name, _cfg_location=None):
439
  """Verifies cluster name against a local cluster name.
440

441
  @type name: string
442
  @param name: Cluster name
443

444
  """
445
  sstore = SimpleStore(cfg_location=_cfg_location)
446

    
447
  try:
448
    local_name = sstore.GetClusterName()
449
  except errors.ConfigurationError, err:
450
    logging.debug("Can't get local cluster name: %s", err)
451
  else:
452
    if name != local_name:
453
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
454

    
455

    
456
def VerifyKeys(keys):
457
  """Raises an exception if unknown ssconf keys are given.
458

459
  @type keys: sequence
460
  @param keys: Key names to verify
461
  @raise errors.GenericError: When invalid keys were found
462

463
  """
464
  invalid = frozenset(keys) - _VALID_KEYS
465
  if invalid:
466
    raise errors.GenericError("Invalid ssconf keys: %s" %
467
                              utils.CommaJoin(sorted(invalid)))