Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 911dfc49

History | View | Annotate | Download (10 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

    
32
from ganeti import errors
33
from ganeti import constants
34
from ganeti import utils
35
from ganeti import netutils
36
from ganeti import pathutils
37

    
38

    
39
SSCONF_LOCK_TIMEOUT = 10
40

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

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

    
71

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

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

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

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

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

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

    
93

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

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

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

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

    
111
  def KeyToFilename(self, key):
112
    """Convert a given key into filename.
113

114
    """
115
    if key not in _VALID_KEYS:
116
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
117
                                   % str(key))
118

    
119
    filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
120
    return filename
121

    
122
  def _ReadFile(self, key, default=None):
123
    """Generic routine to read keys.
124

125
    This will read the file which holds the value requested. Errors
126
    will be changed into ConfigurationErrors.
127

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

    
138
  def WriteFiles(self, values):
139
    """Writes ssconf files used by external scripts.
140

141
    @type values: dict
142
    @param values: Dictionary of (name, value)
143

144
    """
145
    ssconf_lock = utils.FileLock.Open(pathutils.SSCONF_LOCK_FILE)
146

    
147
    # Get lock while writing files
148
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
149
    try:
150
      for name, value in values.iteritems():
151
        if value and not value.endswith("\n"):
152
          value += "\n"
153
        if len(value) > _MAX_SIZE:
154
          raise errors.ConfigurationError("ssconf file %s above maximum size" %
155
                                          name)
156
        utils.WriteFile(self.KeyToFilename(name), data=value,
157
                        mode=constants.SS_FILE_PERMS)
158
    finally:
159
      ssconf_lock.Unlock()
160

    
161
  def GetFileList(self):
162
    """Return the list of all config files.
163

164
    This is used for computing node replication data.
165

166
    """
167
    return [self.KeyToFilename(key) for key in _VALID_KEYS]
168

    
169
  def GetClusterName(self):
170
    """Get the cluster name.
171

172
    """
173
    return self._ReadFile(constants.SS_CLUSTER_NAME)
174

    
175
  def GetFileStorageDir(self):
176
    """Get the file storage dir.
177

178
    """
179
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
180

    
181
  def GetSharedFileStorageDir(self):
182
    """Get the shared file storage dir.
183

184
    """
185
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
186

    
187
  def GetMasterCandidates(self):
188
    """Return the list of master candidates.
189

190
    """
191
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
192
    nl = data.splitlines(False)
193
    return nl
194

    
195
  def GetMasterCandidatesIPList(self):
196
    """Return the list of master candidates' primary IP.
197

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

    
203
  def GetMasterIP(self):
204
    """Get the IP of the master node for this cluster.
205

206
    """
207
    return self._ReadFile(constants.SS_MASTER_IP)
208

    
209
  def GetMasterNetdev(self):
210
    """Get the netdev to which we'll add the master ip.
211

212
    """
213
    return self._ReadFile(constants.SS_MASTER_NETDEV)
214

    
215
  def GetMasterNetmask(self):
216
    """Get the master netmask.
217

218
    """
219
    try:
220
      return self._ReadFile(constants.SS_MASTER_NETMASK)
221
    except errors.ConfigurationError:
222
      family = self.GetPrimaryIPFamily()
223
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
224
      return ipcls.iplen
225

    
226
  def GetMasterNode(self):
227
    """Get the hostname of the master node for this cluster.
228

229
    """
230
    return self._ReadFile(constants.SS_MASTER_NODE)
231

    
232
  def GetNodeList(self):
233
    """Return the list of cluster nodes.
234

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

    
240
  def GetNodePrimaryIPList(self):
241
    """Return the list of cluster nodes' primary IP.
242

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

    
248
  def GetNodeSecondaryIPList(self):
249
    """Return the list of cluster nodes' secondary IP.
250

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

    
256
  def GetNodegroupList(self):
257
    """Return the list of nodegroups.
258

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

    
264
  def GetNetworkList(self):
265
    """Return the list of networks.
266

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

    
272
  def GetClusterTags(self):
273
    """Return the cluster tags.
274

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

    
280
  def GetHypervisorList(self):
281
    """Return the list of enabled hypervisors.
282

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

    
288
  def GetMaintainNodeHealth(self):
289
    """Return the value of the maintain_node_health option.
290

291
    """
292
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
293
    # we rely on the bool serialization here
294
    return data == "True"
295

    
296
  def GetUidPool(self):
297
    """Return the user-id pool definition string.
298

299
    The separator character is a newline.
300

301
    The return value can be parsed using uidpool.ParseUidPool()::
302

303
      ss = ssconf.SimpleStore()
304
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
305

306
    """
307
    data = self._ReadFile(constants.SS_UID_POOL)
308
    return data
309

    
310
  def GetPrimaryIPFamily(self):
311
    """Return the cluster-wide primary address family.
312

313
    """
314
    try:
315
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
316
                                default=netutils.IP4Address.family))
317
    except (ValueError, TypeError), err:
318
      raise errors.ConfigurationError("Error while trying to parse primary IP"
319
                                      " family: %s" % err)
320

    
321

    
322
def WriteSsconfFiles(values):
323
  """Update all ssconf files.
324

325
  Wrapper around L{SimpleStore.WriteFiles}.
326

327
  """
328
  SimpleStore().WriteFiles(values)
329

    
330

    
331
def GetMasterAndMyself(ss=None):
332
  """Get the master node and my own hostname.
333

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

337
  The function does not handle any errors, these should be handled in
338
  the caller (errors.ConfigurationError, errors.ResolverError).
339

340
  @param ss: either a sstore.SimpleConfigReader or a
341
      sstore.SimpleStore instance
342
  @rtype: tuple
343
  @return: a tuple (master node name, my own name)
344

345
  """
346
  if ss is None:
347
    ss = SimpleStore()
348
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
349

    
350

    
351
def CheckMaster(debug, ss=None):
352
  """Checks the node setup.
353

354
  If this is the master, the function will return. Otherwise it will
355
  exit with an exit code based on the node status.
356

357
  """
358
  try:
359
    master_name, myself = GetMasterAndMyself(ss)
360
  except errors.ConfigurationError, err:
361
    print "Cluster configuration incomplete: '%s'" % str(err)
362
    sys.exit(constants.EXIT_NODESETUP_ERROR)
363
  except errors.ResolverError, err:
364
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
365
    sys.exit(constants.EXIT_NODESETUP_ERROR)
366

    
367
  if myself != master_name:
368
    if debug:
369
      sys.stderr.write("Not master, exiting.\n")
370
    sys.exit(constants.EXIT_NOTMASTER)