Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 7bcc78e4

History | View | Annotate | Download (10.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 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 re
31
import os
32

    
33
from ganeti import errors
34
from ganeti import constants
35
from ganeti import utils
36
from ganeti import serializer
37

    
38

    
39
SSCONF_LOCK_TIMEOUT = 10
40

    
41
RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$')
42

    
43

    
44
class SimpleConfigReader(object):
45
  """Simple class to read configuration file.
46

47
  """
48
  def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
49
    """Initializes this class.
50

51
    @type file_name: string
52
    @param file_name: Configuration file path
53

54
    """
55
    self._file_name = file_name
56
    self._last_inode = None
57
    self._last_mtime = None
58
    self._last_size = None
59
    # we need a forced reload at class init time, to initialize _last_*
60
    self._Load(force=True)
61

    
62
  def _Load(self, force=False):
63
    """Loads (or reloads) the config file.
64

65
    @type force: boolean
66
    @param force: whether to force the reload without checking the mtime
67
    @rtype: boolean
68
    @return: boolean values that says whether we reloaded the configuration or not
69
             (because we decided it was already up-to-date)
70

71
    """
72
    cfg_stat = os.stat(self._file_name)
73
    inode = cfg_stat.st_ino
74
    mtime = cfg_stat.st_mtime
75
    size = cfg_stat.st_size
76

    
77
    reload = False
78
    if force or inode != self._last_inode or \
79
       mtime > self._last_mtime or \
80
       size != self._last_size:
81
      self._last_inode = inode
82
      self._last_mtime = mtime
83
      self._last_size = size
84
      reload = True
85

    
86
    if not reload:
87
      return False
88

    
89
    try:
90
      self._config_data = serializer.Load(utils.ReadFile(self._file_name))
91
    except IOError, err:
92
      raise errors.ConfigurationError("Cannot read config file %s: %s" %
93
                                      (self._file_name, err))
94
    except ValueError, err:
95
      raise errors.ConfigurationError("Cannot load config file %s: %s" %
96
                                      (self._file_name, err))
97

    
98
    return True
99

    
100
  # Clients can request a reload of the config file, so we export our internal
101
  # _Load function as Reload.
102
  Reload = _Load
103

    
104
  def GetClusterName(self):
105
    return self._config_data["cluster"]["cluster_name"]
106

    
107
  def GetHostKey(self):
108
    return self._config_data["cluster"]["rsahostkeypub"]
109

    
110
  def GetMasterNode(self):
111
    return self._config_data["cluster"]["master_node"]
112

    
113
  def GetMasterIP(self):
114
    return self._config_data["cluster"]["master_ip"]
115

    
116
  def GetMasterNetdev(self):
117
    return self._config_data["cluster"]["master_netdev"]
118

    
119
  def GetFileStorageDir(self):
120
    return self._config_data["cluster"]["file_storage_dir"]
121

    
122
  def GetHypervisorType(self):
123
    return self._config_data["cluster"]["hypervisor"]
124

    
125
  def GetNodeList(self):
126
    return self._config_data["nodes"].keys()
127

    
128
  def GetConfigSerialNo(self):
129
    return self._config_data["serial_no"]
130

    
131
  def GetClusterSerialNo(self):
132
    return self._config_data["cluster"]["serial_no"]
133

    
134

    
135
class SimpleStore(object):
136
  """Interface to static cluster data.
137

138
  This is different that the config.ConfigWriter and
139
  SimpleConfigReader classes in that it holds data that will always be
140
  present, even on nodes which don't have all the cluster data.
141

142
  Other particularities of the datastore:
143
    - keys are restricted to predefined values
144

145
  """
146
  _SS_FILEPREFIX = "ssconf_"
147
  _VALID_KEYS = (
148
    constants.SS_CLUSTER_NAME,
149
    constants.SS_CLUSTER_TAGS,
150
    constants.SS_FILE_STORAGE_DIR,
151
    constants.SS_MASTER_CANDIDATES,
152
    constants.SS_MASTER_CANDIDATES_IPS,
153
    constants.SS_MASTER_IP,
154
    constants.SS_MASTER_NETDEV,
155
    constants.SS_MASTER_NODE,
156
    constants.SS_NODE_LIST,
157
    constants.SS_NODE_PRIMARY_IPS,
158
    constants.SS_NODE_SECONDARY_IPS,
159
    constants.SS_OFFLINE_NODES,
160
    constants.SS_ONLINE_NODES,
161
    constants.SS_INSTANCE_LIST,
162
    constants.SS_RELEASE_VERSION,
163
    )
164
  _MAX_SIZE = 131072
165

    
166
  def __init__(self, cfg_location=None):
167
    if cfg_location is None:
168
      self._cfg_dir = constants.DATA_DIR
169
    else:
170
      self._cfg_dir = cfg_location
171

    
172
  def KeyToFilename(self, key):
173
    """Convert a given key into filename.
174

175
    """
176
    if key not in self._VALID_KEYS:
177
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
178
                                   % str(key))
179

    
180
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
181
    return filename
182

    
183
  def _ReadFile(self, key):
184
    """Generic routine to read keys.
185

186
    This will read the file which holds the value requested. Errors
187
    will be changed into ConfigurationErrors.
188

189
    """
190
    filename = self.KeyToFilename(key)
191
    try:
192
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
193
    except EnvironmentError, err:
194
      raise errors.ConfigurationError("Can't read from the ssconf file:"
195
                                      " '%s'" % str(err))
196
    data = data.rstrip('\n')
197
    return data
198

    
199
  def WriteFiles(self, values):
200
    """Writes ssconf files used by external scripts.
201

202
    @type values: dict
203
    @param values: Dictionary of (name, value)
204

205
    """
206
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
207

    
208
    # Get lock while writing files
209
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
210
    try:
211
      for name, value in values.iteritems():
212
        if value and not value.endswith("\n"):
213
          value += "\n"
214
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
215
    finally:
216
      ssconf_lock.Unlock()
217

    
218
  def GetFileList(self):
219
    """Return the list of all config files.
220

221
    This is used for computing node replication data.
222

223
    """
224
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
225

    
226
  def GetClusterName(self):
227
    """Get the cluster name.
228

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

    
232
  def GetFileStorageDir(self):
233
    """Get the file storage dir.
234

235
    """
236
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
237

    
238
  def GetMasterCandidates(self):
239
    """Return the list of master candidates.
240

241
    """
242
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
243
    nl = data.splitlines(False)
244
    return nl
245

    
246
  def GetMasterCandidatesIPList(self):
247
    """Return the list of master candidates' primary IP.
248

249
    """
250
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
251
    nl = data.splitlines(False)
252
    return nl
253

    
254
  def GetMasterIP(self):
255
    """Get the IP of the master node for this cluster.
256

257
    """
258
    return self._ReadFile(constants.SS_MASTER_IP)
259

    
260
  def GetMasterNetdev(self):
261
    """Get the netdev to which we'll add the master ip.
262

263
    """
264
    return self._ReadFile(constants.SS_MASTER_NETDEV)
265

    
266
  def GetMasterNode(self):
267
    """Get the hostname of the master node for this cluster.
268

269
    """
270
    return self._ReadFile(constants.SS_MASTER_NODE)
271

    
272
  def GetNodeList(self):
273
    """Return the list of cluster nodes.
274

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

    
280
  def GetNodePrimaryIPList(self):
281
    """Return the list of cluster nodes' primary IP.
282

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

    
288
  def GetNodeSecondaryIPList(self):
289
    """Return the list of cluster nodes' secondary IP.
290

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

    
296
  def GetClusterTags(self):
297
    """Return the cluster tags.
298

299
    """
300
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
301
    nl = data.splitlines(False)
302
    return nl
303

    
304

    
305
def GetMasterAndMyself(ss=None):
306
  """Get the master node and my own hostname.
307

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

311
  The function does not handle any errors, these should be handled in
312
  the caller (errors.ConfigurationError, errors.ResolverError).
313

314
  @param ss: either a sstore.SimpleConfigReader or a
315
      sstore.SimpleStore instance
316
  @rtype: tuple
317
  @return: a tuple (master node name, my own name)
318

319
  """
320
  if ss is None:
321
    ss = SimpleStore()
322
  return ss.GetMasterNode(), utils.HostInfo().name
323

    
324

    
325
def CheckMaster(debug, ss=None):
326
  """Checks the node setup.
327

328
  If this is the master, the function will return. Otherwise it will
329
  exit with an exit code based on the node status.
330

331
  """
332
  try:
333
    master_name, myself = GetMasterAndMyself(ss)
334
  except errors.ConfigurationError, err:
335
    print "Cluster configuration incomplete: '%s'" % str(err)
336
    sys.exit(constants.EXIT_NODESETUP_ERROR)
337
  except errors.ResolverError, err:
338
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
339
    sys.exit(constants.EXIT_NODESETUP_ERROR)
340

    
341
  if myself != master_name:
342
    if debug:
343
      sys.stderr.write("Not master, exiting.\n")
344
    sys.exit(constants.EXIT_NOTMASTER)
345

    
346

    
347
def CheckMasterCandidate(debug, ss=None):
348
  """Checks the node setup.
349

350
  If this is a master candidate, the function will return. Otherwise it will
351
  exit with an exit code based on the node status.
352

353
  """
354
  try:
355
    if ss is None:
356
      ss = SimpleStore()
357
    myself = utils.HostInfo().name
358
    candidates = ss.GetMasterCandidates()
359
  except errors.ConfigurationError, err:
360
    print "Cluster configuration incomplete: '%s'" % str(err)
361
    sys.exit(constants.EXIT_NODESETUP_ERROR)
362
  except errors.ResolverError, err:
363
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
364
    sys.exit(constants.EXIT_NODESETUP_ERROR)
365

    
366
  if myself not in candidates:
367
    if debug:
368
      sys.stderr.write("Not master candidate, exiting.\n")
369
    sys.exit(constants.EXIT_NOTCANDIDATE)
370