Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 7af7da68

History | View | Annotate | Download (11.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_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
  ])
69

    
70
#: Maximum size for ssconf files
71
_MAX_SIZE = 128 * 1024
72

    
73

    
74
def ReadSsconfFile(filename):
75
  """Reads an ssconf file and verifies its size.
76

77
  @type filename: string
78
  @param filename: Path to file
79
  @rtype: string
80
  @return: File contents without newlines at the end
81
  @raise RuntimeError: When the file size exceeds L{_MAX_SIZE}
82

83
  """
84
  statcb = utils.FileStatHelper()
85

    
86
  data = utils.ReadFile(filename, size=_MAX_SIZE, preread=statcb)
87

    
88
  if statcb.st.st_size > _MAX_SIZE:
89
    msg = ("File '%s' has a size of %s bytes (up to %s allowed)" %
90
           (filename, statcb.st.st_size, _MAX_SIZE))
91
    raise RuntimeError(msg)
92

    
93
  return data.rstrip("\n")
94

    
95

    
96
class SimpleStore(object):
97
  """Interface to static cluster data.
98

99
  This is different that the config.ConfigWriter and
100
  SimpleConfigReader classes in that it holds data that will always be
101
  present, even on nodes which don't have all the cluster data.
102

103
  Other particularities of the datastore:
104
    - keys are restricted to predefined values
105

106
  """
107
  def __init__(self, cfg_location=None, _lockfile=pathutils.SSCONF_LOCK_FILE):
108
    if cfg_location is None:
109
      self._cfg_dir = pathutils.DATA_DIR
110
    else:
111
      self._cfg_dir = cfg_location
112

    
113
    self._lockfile = _lockfile
114

    
115
  def KeyToFilename(self, key):
116
    """Convert a given key into filename.
117

118
    """
119
    if key not in _VALID_KEYS:
120
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
121
                                   % str(key))
122

    
123
    filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
124
    return filename
125

    
126
  def _ReadFile(self, key, default=None):
127
    """Generic routine to read keys.
128

129
    This will read the file which holds the value requested. Errors
130
    will be changed into ConfigurationErrors.
131

132
    """
133
    filename = self.KeyToFilename(key)
134
    try:
135
      return ReadSsconfFile(filename)
136
    except EnvironmentError, err:
137
      if err.errno == errno.ENOENT and default is not None:
138
        return default
139
      raise errors.ConfigurationError("Can't read ssconf file %s: %s" %
140
                                      (filename, str(err)))
141

    
142
  def ReadAll(self):
143
    """Reads all keys and returns their values.
144

145
    @rtype: dict
146
    @return: Dictionary, ssconf key as key, value as value
147

148
    """
149
    result = []
150

    
151
    for key in _VALID_KEYS:
152
      try:
153
        value = self._ReadFile(key)
154
      except errors.ConfigurationError:
155
        # Ignore non-existing files
156
        pass
157
      else:
158
        result.append((key, value))
159

    
160
    return dict(result)
161

    
162
  def WriteFiles(self, values, dry_run=False):
163
    """Writes ssconf files used by external scripts.
164

165
    @type values: dict
166
    @param values: Dictionary of (name, value)
167
    @type dry_run boolean
168
    @param dry_run: Whether to perform a dry run
169

170
    """
171
    ssconf_lock = utils.FileLock.Open(self._lockfile)
172

    
173
    # Get lock while writing files
174
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
175
    try:
176
      for name, value in values.iteritems():
177
        if value and not value.endswith("\n"):
178
          value += "\n"
179

    
180
        if len(value) > _MAX_SIZE:
181
          msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
182
                 " allowed" % (name, len(value), _MAX_SIZE))
183
          raise errors.ConfigurationError(msg)
184

    
185
        utils.WriteFile(self.KeyToFilename(name), data=value,
186
                        mode=constants.SS_FILE_PERMS,
187
                        dry_run=dry_run)
188
    finally:
189
      ssconf_lock.Unlock()
190

    
191
  def GetFileList(self):
192
    """Return the list of all config files.
193

194
    This is used for computing node replication data.
195

196
    """
197
    return [self.KeyToFilename(key) for key in _VALID_KEYS]
198

    
199
  def GetClusterName(self):
200
    """Get the cluster name.
201

202
    """
203
    return self._ReadFile(constants.SS_CLUSTER_NAME)
204

    
205
  def GetFileStorageDir(self):
206
    """Get the file storage dir.
207

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

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

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

    
217
  def GetMasterCandidates(self):
218
    """Return the list of master candidates.
219

220
    """
221
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
222
    nl = data.splitlines(False)
223
    return nl
224

    
225
  def GetMasterCandidatesIPList(self):
226
    """Return the list of master candidates' primary IP.
227

228
    """
229
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
230
    nl = data.splitlines(False)
231
    return nl
232

    
233
  def GetMasterIP(self):
234
    """Get the IP of the master node for this cluster.
235

236
    """
237
    return self._ReadFile(constants.SS_MASTER_IP)
238

    
239
  def GetMasterNetdev(self):
240
    """Get the netdev to which we'll add the master ip.
241

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

    
245
  def GetMasterNetmask(self):
246
    """Get the master netmask.
247

248
    """
249
    try:
250
      return self._ReadFile(constants.SS_MASTER_NETMASK)
251
    except errors.ConfigurationError:
252
      family = self.GetPrimaryIPFamily()
253
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
254
      return ipcls.iplen
255

    
256
  def GetMasterNode(self):
257
    """Get the hostname of the master node for this cluster.
258

259
    """
260
    return self._ReadFile(constants.SS_MASTER_NODE)
261

    
262
  def GetNodeList(self):
263
    """Return the list of cluster nodes.
264

265
    """
266
    data = self._ReadFile(constants.SS_NODE_LIST)
267
    nl = data.splitlines(False)
268
    return nl
269

    
270
  def GetNodePrimaryIPList(self):
271
    """Return the list of cluster nodes' primary IP.
272

273
    """
274
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
275
    nl = data.splitlines(False)
276
    return nl
277

    
278
  def GetNodeSecondaryIPList(self):
279
    """Return the list of cluster nodes' secondary IP.
280

281
    """
282
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
283
    nl = data.splitlines(False)
284
    return nl
285

    
286
  def GetNodegroupList(self):
287
    """Return the list of nodegroups.
288

289
    """
290
    data = self._ReadFile(constants.SS_NODEGROUPS)
291
    nl = data.splitlines(False)
292
    return nl
293

    
294
  def GetNetworkList(self):
295
    """Return the list of networks.
296

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

    
302
  def GetClusterTags(self):
303
    """Return the cluster tags.
304

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

    
310
  def GetHypervisorList(self):
311
    """Return the list of enabled hypervisors.
312

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

    
318
  def GetMaintainNodeHealth(self):
319
    """Return the value of the maintain_node_health option.
320

321
    """
322
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
323
    # we rely on the bool serialization here
324
    return data == "True"
325

    
326
  def GetUidPool(self):
327
    """Return the user-id pool definition string.
328

329
    The separator character is a newline.
330

331
    The return value can be parsed using uidpool.ParseUidPool()::
332

333
      ss = ssconf.SimpleStore()
334
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
335

336
    """
337
    data = self._ReadFile(constants.SS_UID_POOL)
338
    return data
339

    
340
  def GetPrimaryIPFamily(self):
341
    """Return the cluster-wide primary address family.
342

343
    """
344
    try:
345
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
346
                                default=netutils.IP4Address.family))
347
    except (ValueError, TypeError), err:
348
      raise errors.ConfigurationError("Error while trying to parse primary IP"
349
                                      " family: %s" % err)
350

    
351

    
352
def WriteSsconfFiles(values, dry_run=False):
353
  """Update all ssconf files.
354

355
  Wrapper around L{SimpleStore.WriteFiles}.
356

357
  """
358
  SimpleStore().WriteFiles(values, dry_run=dry_run)
359

    
360

    
361
def GetMasterAndMyself(ss=None):
362
  """Get the master node and my own hostname.
363

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

367
  The function does not handle any errors, these should be handled in
368
  the caller (errors.ConfigurationError, errors.ResolverError).
369

370
  @param ss: either a sstore.SimpleConfigReader or a
371
      sstore.SimpleStore instance
372
  @rtype: tuple
373
  @return: a tuple (master node name, my own name)
374

375
  """
376
  if ss is None:
377
    ss = SimpleStore()
378
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
379

    
380

    
381
def CheckMaster(debug, ss=None):
382
  """Checks the node setup.
383

384
  If this is the master, the function will return. Otherwise it will
385
  exit with an exit code based on the node status.
386

387
  """
388
  try:
389
    master_name, myself = GetMasterAndMyself(ss)
390
  except errors.ConfigurationError, err:
391
    print "Cluster configuration incomplete: '%s'" % str(err)
392
    sys.exit(constants.EXIT_NODESETUP_ERROR)
393
  except errors.ResolverError, err:
394
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
395
    sys.exit(constants.EXIT_NODESETUP_ERROR)
396

    
397
  if myself != master_name:
398
    if debug:
399
      sys.stderr.write("Not master, exiting.\n")
400
    sys.exit(constants.EXIT_NOTMASTER)
401

    
402

    
403
def VerifyClusterName(name, _cfg_location=None):
404
  """Verifies cluster name against a local cluster name.
405

406
  @type name: string
407
  @param name: Cluster name
408

409
  """
410
  sstore = SimpleStore(cfg_location=_cfg_location)
411

    
412
  try:
413
    local_name = sstore.GetClusterName()
414
  except errors.ConfigurationError, err:
415
    logging.debug("Can't get local cluster name: %s", err)
416
  else:
417
    if name != local_name:
418
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
419

    
420

    
421
def VerifyKeys(keys):
422
  """Raises an exception if unknown ssconf keys are given.
423

424
  @type keys: sequence
425
  @param keys: Key names to verify
426
  @raise errors.GenericError: When invalid keys were found
427

428
  """
429
  invalid = frozenset(keys) - _VALID_KEYS
430
  if invalid:
431
    raise errors.GenericError("Invalid ssconf keys: %s" %
432
                              utils.CommaJoin(sorted(invalid)))