Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ cc7f5bfc

History | View | Annotate | Download (11.1 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 errors
34
from ganeti import constants
35
from ganeti import utils
36
from ganeti import netutils
37
from ganeti import pathutils
38

    
39

    
40
SSCONF_LOCK_TIMEOUT = 10
41

    
42
#: Valid ssconf keys
43
_VALID_KEYS = frozenset([
44
  constants.SS_CLUSTER_NAME,
45
  constants.SS_CLUSTER_TAGS,
46
  constants.SS_FILE_STORAGE_DIR,
47
  constants.SS_SHARED_FILE_STORAGE_DIR,
48
  constants.SS_MASTER_CANDIDATES,
49
  constants.SS_MASTER_CANDIDATES_IPS,
50
  constants.SS_MASTER_IP,
51
  constants.SS_MASTER_NETDEV,
52
  constants.SS_MASTER_NETMASK,
53
  constants.SS_MASTER_NODE,
54
  constants.SS_NODE_LIST,
55
  constants.SS_NODE_PRIMARY_IPS,
56
  constants.SS_NODE_SECONDARY_IPS,
57
  constants.SS_OFFLINE_NODES,
58
  constants.SS_ONLINE_NODES,
59
  constants.SS_PRIMARY_IP_FAMILY,
60
  constants.SS_INSTANCE_LIST,
61
  constants.SS_RELEASE_VERSION,
62
  constants.SS_HYPERVISOR_LIST,
63
  constants.SS_MAINTAIN_NODE_HEALTH,
64
  constants.SS_UID_POOL,
65
  constants.SS_NODEGROUPS,
66
  constants.SS_NETWORKS,
67
  ])
68

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

    
72

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

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

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

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

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

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

    
94

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

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

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

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

    
112
    self._lockfile = _lockfile
113

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

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

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

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

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

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

    
141
  def WriteFiles(self, values, dry_run=False):
142
    """Writes ssconf files used by external scripts.
143

144
    @type values: dict
145
    @param values: Dictionary of (name, value)
146
    @type dry_run boolean
147
    @param dry_run: Whether to perform a dry run
148

149
    """
150
    ssconf_lock = utils.FileLock.Open(self._lockfile)
151

    
152
    # Get lock while writing files
153
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
154
    try:
155
      for name, value in values.iteritems():
156
        if value and not value.endswith("\n"):
157
          value += "\n"
158

    
159
        if len(value) > _MAX_SIZE:
160
          msg = ("Value '%s' has a length of %s bytes, but only up to %s are"
161
                 " allowed" % (name, len(value), _MAX_SIZE))
162
          raise errors.ConfigurationError(msg)
163

    
164
        utils.WriteFile(self.KeyToFilename(name), data=value,
165
                        mode=constants.SS_FILE_PERMS,
166
                        dry_run=dry_run)
167
    finally:
168
      ssconf_lock.Unlock()
169

    
170
  def GetFileList(self):
171
    """Return the list of all config files.
172

173
    This is used for computing node replication data.
174

175
    """
176
    return [self.KeyToFilename(key) for key in _VALID_KEYS]
177

    
178
  def GetClusterName(self):
179
    """Get the cluster name.
180

181
    """
182
    return self._ReadFile(constants.SS_CLUSTER_NAME)
183

    
184
  def GetFileStorageDir(self):
185
    """Get the file storage dir.
186

187
    """
188
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
189

    
190
  def GetSharedFileStorageDir(self):
191
    """Get the shared file storage dir.
192

193
    """
194
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
195

    
196
  def GetMasterCandidates(self):
197
    """Return the list of master candidates.
198

199
    """
200
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
201
    nl = data.splitlines(False)
202
    return nl
203

    
204
  def GetMasterCandidatesIPList(self):
205
    """Return the list of master candidates' primary IP.
206

207
    """
208
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
209
    nl = data.splitlines(False)
210
    return nl
211

    
212
  def GetMasterIP(self):
213
    """Get the IP of the master node for this cluster.
214

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

    
218
  def GetMasterNetdev(self):
219
    """Get the netdev to which we'll add the master ip.
220

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

    
224
  def GetMasterNetmask(self):
225
    """Get the master netmask.
226

227
    """
228
    try:
229
      return self._ReadFile(constants.SS_MASTER_NETMASK)
230
    except errors.ConfigurationError:
231
      family = self.GetPrimaryIPFamily()
232
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
233
      return ipcls.iplen
234

    
235
  def GetMasterNode(self):
236
    """Get the hostname of the master node for this cluster.
237

238
    """
239
    return self._ReadFile(constants.SS_MASTER_NODE)
240

    
241
  def GetNodeList(self):
242
    """Return the list of cluster nodes.
243

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

    
249
  def GetNodePrimaryIPList(self):
250
    """Return the list of cluster nodes' primary IP.
251

252
    """
253
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
254
    nl = data.splitlines(False)
255
    return nl
256

    
257
  def GetNodeSecondaryIPList(self):
258
    """Return the list of cluster nodes' secondary IP.
259

260
    """
261
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
262
    nl = data.splitlines(False)
263
    return nl
264

    
265
  def GetNodegroupList(self):
266
    """Return the list of nodegroups.
267

268
    """
269
    data = self._ReadFile(constants.SS_NODEGROUPS)
270
    nl = data.splitlines(False)
271
    return nl
272

    
273
  def GetNetworkList(self):
274
    """Return the list of networks.
275

276
    """
277
    data = self._ReadFile(constants.SS_NETWORKS)
278
    nl = data.splitlines(False)
279
    return nl
280

    
281
  def GetClusterTags(self):
282
    """Return the cluster tags.
283

284
    """
285
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
286
    nl = data.splitlines(False)
287
    return nl
288

    
289
  def GetHypervisorList(self):
290
    """Return the list of enabled hypervisors.
291

292
    """
293
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
294
    nl = data.splitlines(False)
295
    return nl
296

    
297
  def GetMaintainNodeHealth(self):
298
    """Return the value of the maintain_node_health option.
299

300
    """
301
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
302
    # we rely on the bool serialization here
303
    return data == "True"
304

    
305
  def GetUidPool(self):
306
    """Return the user-id pool definition string.
307

308
    The separator character is a newline.
309

310
    The return value can be parsed using uidpool.ParseUidPool()::
311

312
      ss = ssconf.SimpleStore()
313
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
314

315
    """
316
    data = self._ReadFile(constants.SS_UID_POOL)
317
    return data
318

    
319
  def GetPrimaryIPFamily(self):
320
    """Return the cluster-wide primary address family.
321

322
    """
323
    try:
324
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
325
                                default=netutils.IP4Address.family))
326
    except (ValueError, TypeError), err:
327
      raise errors.ConfigurationError("Error while trying to parse primary IP"
328
                                      " family: %s" % err)
329

    
330

    
331
def WriteSsconfFiles(values, dry_run=False):
332
  """Update all ssconf files.
333

334
  Wrapper around L{SimpleStore.WriteFiles}.
335

336
  """
337
  SimpleStore().WriteFiles(values, dry_run=dry_run)
338

    
339

    
340
def GetMasterAndMyself(ss=None):
341
  """Get the master node and my own hostname.
342

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

346
  The function does not handle any errors, these should be handled in
347
  the caller (errors.ConfigurationError, errors.ResolverError).
348

349
  @param ss: either a sstore.SimpleConfigReader or a
350
      sstore.SimpleStore instance
351
  @rtype: tuple
352
  @return: a tuple (master node name, my own name)
353

354
  """
355
  if ss is None:
356
    ss = SimpleStore()
357
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
358

    
359

    
360
def CheckMaster(debug, ss=None):
361
  """Checks the node setup.
362

363
  If this is the master, the function will return. Otherwise it will
364
  exit with an exit code based on the node status.
365

366
  """
367
  try:
368
    master_name, myself = GetMasterAndMyself(ss)
369
  except errors.ConfigurationError, err:
370
    print "Cluster configuration incomplete: '%s'" % str(err)
371
    sys.exit(constants.EXIT_NODESETUP_ERROR)
372
  except errors.ResolverError, err:
373
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
374
    sys.exit(constants.EXIT_NODESETUP_ERROR)
375

    
376
  if myself != master_name:
377
    if debug:
378
      sys.stderr.write("Not master, exiting.\n")
379
    sys.exit(constants.EXIT_NOTMASTER)
380

    
381

    
382
def VerifyClusterName(name, _cfg_location=None):
383
  """Verifies cluster name against a local cluster name.
384

385
  @type name: string
386
  @param name: Cluster name
387

388
  """
389
  sstore = SimpleStore(cfg_location=_cfg_location)
390

    
391
  try:
392
    local_name = sstore.GetClusterName()
393
  except errors.ConfigurationError, err:
394
    logging.debug("Can't get local cluster name: %s", err)
395
  else:
396
    if name != local_name:
397
      raise errors.GenericError("Current cluster name is '%s'" % local_name)
398

    
399

    
400
def VerifyKeys(keys):
401
  """Raises an exception if unknown ssconf keys are given.
402

403
  @type keys: sequence
404
  @param keys: Key names to verify
405
  @raise errors.GenericError: When invalid keys were found
406

407
  """
408
  invalid = frozenset(keys) - _VALID_KEYS
409
  if invalid:
410
    raise errors.GenericError("Invalid ssconf keys: %s" %
411
                              utils.CommaJoin(sorted(invalid)))