Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 84ad6b78

History | View | Annotate | Download (12.8 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 GetOnlineNodeList(self):
277
    """Return the list of online cluster nodes.
278

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

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

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

    
292
  def GetNodeSecondaryIPList(self):
293
    """Return the list of cluster nodes' secondary IP.
294

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

    
300
  def GetNodegroupList(self):
301
    """Return the list of nodegroups.
302

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

    
308
  def GetNetworkList(self):
309
    """Return the list of networks.
310

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

    
316
  def GetClusterTags(self):
317
    """Return the cluster tags.
318

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

    
324
  def GetHypervisorList(self):
325
    """Return the list of enabled hypervisors.
326

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

    
332
  def GetHvparamsForHypervisor(self, hvname):
333
    """Return the hypervisor parameters of the given hypervisor.
334

335
    @type hvname: string
336
    @param hvname: name of the hypervisor, must be in C{constants.HYPER_TYPES}
337
    @rtype: dict of strings
338
    @returns: dictionary with hypervisor parameters
339

340
    """
341
    data = self._ReadFile(constants.SS_HVPARAMS_PREF + hvname)
342
    lines = data.splitlines(False)
343
    hvparams = {}
344
    for line in lines:
345
      (key, value) = line.split("=")
346
      hvparams[key] = value
347
    return hvparams
348

    
349
  def GetHvparams(self):
350
    """Return the hypervisor parameters of all hypervisors.
351

352
    @rtype: dict of dict of strings
353
    @returns: dictionary mapping hypervisor names to hvparams
354

355
    """
356
    all_hvparams = {}
357
    for hv in constants.HYPER_TYPES:
358
      all_hvparams[hv] = self.GetHvparamsForHypervisor(hv)
359
    return all_hvparams
360

    
361
  def GetMaintainNodeHealth(self):
362
    """Return the value of the maintain_node_health option.
363

364
    """
365
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
366
    # we rely on the bool serialization here
367
    return data == "True"
368

    
369
  def GetUidPool(self):
370
    """Return the user-id pool definition string.
371

372
    The separator character is a newline.
373

374
    The return value can be parsed using uidpool.ParseUidPool()::
375

376
      ss = ssconf.SimpleStore()
377
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
378

379
    """
380
    data = self._ReadFile(constants.SS_UID_POOL)
381
    return data
382

    
383
  def GetPrimaryIPFamily(self):
384
    """Return the cluster-wide primary address family.
385

386
    """
387
    try:
388
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
389
                                default=netutils.IP4Address.family))
390
    except (ValueError, TypeError), err:
391
      raise errors.ConfigurationError("Error while trying to parse primary IP"
392
                                      " family: %s" % err)
393

    
394

    
395
def WriteSsconfFiles(values, dry_run=False):
396
  """Update all ssconf files.
397

398
  Wrapper around L{SimpleStore.WriteFiles}.
399

400
  """
401
  SimpleStore().WriteFiles(values, dry_run=dry_run)
402

    
403

    
404
def GetMasterAndMyself(ss=None):
405
  """Get the master node and my own hostname.
406

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

410
  The function does not handle any errors, these should be handled in
411
  the caller (errors.ConfigurationError, errors.ResolverError).
412

413
  @param ss: either a sstore.SimpleConfigReader or a
414
      sstore.SimpleStore instance
415
  @rtype: tuple
416
  @return: a tuple (master node name, my own name)
417

418
  """
419
  if ss is None:
420
    ss = SimpleStore()
421
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
422

    
423

    
424
def CheckMaster(debug, ss=None):
425
  """Checks the node setup.
426

427
  If this is the master, the function will return. Otherwise it will
428
  exit with an exit code based on the node status.
429

430
  """
431
  try:
432
    master_name, myself = GetMasterAndMyself(ss)
433
  except errors.ConfigurationError, err:
434
    print "Cluster configuration incomplete: '%s'" % str(err)
435
    sys.exit(constants.EXIT_NODESETUP_ERROR)
436
  except errors.ResolverError, err:
437
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
438
    sys.exit(constants.EXIT_NODESETUP_ERROR)
439

    
440
  if myself != master_name:
441
    if debug:
442
      sys.stderr.write("Not master, exiting.\n")
443
    sys.exit(constants.EXIT_NOTMASTER)
444

    
445

    
446
def VerifyClusterName(name, _cfg_location=None):
447
  """Verifies cluster name against a local cluster name.
448

449
  @type name: string
450
  @param name: Cluster name
451

452
  """
453
  sstore = SimpleStore(cfg_location=_cfg_location)
454

    
455
  try:
456
    local_name = sstore.GetClusterName()
457
  except errors.ConfigurationError, err:
458
    logging.debug("Can't get local cluster name: %s", err)
459
  else:
460
    if name != local_name:
461
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
462

    
463

    
464
def VerifyKeys(keys):
465
  """Raises an exception if unknown ssconf keys are given.
466

467
  @type keys: sequence
468
  @param keys: Key names to verify
469
  @raise errors.GenericError: When invalid keys were found
470

471
  """
472
  invalid = frozenset(keys) - _VALID_KEYS
473
  if invalid:
474
    raise errors.GenericError("Invalid ssconf keys: %s" %
475
                              utils.CommaJoin(sorted(invalid)))