Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ e843991b

History | View | Annotate | Download (10.7 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
  def GetNodeStatusFlags(self, node):
135
    """Get a node's status flags
136

137
    @type node: string
138
    @param node: node name
139
    @rtype: (bool, bool, bool)
140
    @return: (master_candidate, drained, offline) (or None if no such node)
141

142
    """
143
    if node not in self._config_data["nodes"]:
144
      return None
145

    
146
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
147
    drained = self._config_data["nodes"][node]["drained"]
148
    offline = self._config_data["nodes"][node]["offline"]
149
    return master_candidate, drained, offline
150

    
151

    
152
class SimpleStore(object):
153
  """Interface to static cluster data.
154

155
  This is different that the config.ConfigWriter and
156
  SimpleConfigReader classes in that it holds data that will always be
157
  present, even on nodes which don't have all the cluster data.
158

159
  Other particularities of the datastore:
160
    - keys are restricted to predefined values
161

162
  """
163
  _SS_FILEPREFIX = "ssconf_"
164
  _VALID_KEYS = (
165
    constants.SS_CLUSTER_NAME,
166
    constants.SS_CLUSTER_TAGS,
167
    constants.SS_FILE_STORAGE_DIR,
168
    constants.SS_MASTER_CANDIDATES,
169
    constants.SS_MASTER_CANDIDATES_IPS,
170
    constants.SS_MASTER_IP,
171
    constants.SS_MASTER_NETDEV,
172
    constants.SS_MASTER_NODE,
173
    constants.SS_NODE_LIST,
174
    constants.SS_NODE_PRIMARY_IPS,
175
    constants.SS_NODE_SECONDARY_IPS,
176
    constants.SS_OFFLINE_NODES,
177
    constants.SS_ONLINE_NODES,
178
    constants.SS_INSTANCE_LIST,
179
    constants.SS_RELEASE_VERSION,
180
    )
181
  _MAX_SIZE = 131072
182

    
183
  def __init__(self, cfg_location=None):
184
    if cfg_location is None:
185
      self._cfg_dir = constants.DATA_DIR
186
    else:
187
      self._cfg_dir = cfg_location
188

    
189
  def KeyToFilename(self, key):
190
    """Convert a given key into filename.
191

192
    """
193
    if key not in self._VALID_KEYS:
194
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
195
                                   % str(key))
196

    
197
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
198
    return filename
199

    
200
  def _ReadFile(self, key):
201
    """Generic routine to read keys.
202

203
    This will read the file which holds the value requested. Errors
204
    will be changed into ConfigurationErrors.
205

206
    """
207
    filename = self.KeyToFilename(key)
208
    try:
209
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
210
    except EnvironmentError, err:
211
      raise errors.ConfigurationError("Can't read from the ssconf file:"
212
                                      " '%s'" % str(err))
213
    data = data.rstrip('\n')
214
    return data
215

    
216
  def WriteFiles(self, values):
217
    """Writes ssconf files used by external scripts.
218

219
    @type values: dict
220
    @param values: Dictionary of (name, value)
221

222
    """
223
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
224

    
225
    # Get lock while writing files
226
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
227
    try:
228
      for name, value in values.iteritems():
229
        if value and not value.endswith("\n"):
230
          value += "\n"
231
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
232
    finally:
233
      ssconf_lock.Unlock()
234

    
235
  def GetFileList(self):
236
    """Return the list of all config files.
237

238
    This is used for computing node replication data.
239

240
    """
241
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
242

    
243
  def GetClusterName(self):
244
    """Get the cluster name.
245

246
    """
247
    return self._ReadFile(constants.SS_CLUSTER_NAME)
248

    
249
  def GetFileStorageDir(self):
250
    """Get the file storage dir.
251

252
    """
253
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
254

    
255
  def GetMasterCandidates(self):
256
    """Return the list of master candidates.
257

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

    
263
  def GetMasterCandidatesIPList(self):
264
    """Return the list of master candidates' primary IP.
265

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

    
271
  def GetMasterIP(self):
272
    """Get the IP of the master node for this cluster.
273

274
    """
275
    return self._ReadFile(constants.SS_MASTER_IP)
276

    
277
  def GetMasterNetdev(self):
278
    """Get the netdev to which we'll add the master ip.
279

280
    """
281
    return self._ReadFile(constants.SS_MASTER_NETDEV)
282

    
283
  def GetMasterNode(self):
284
    """Get the hostname of the master node for this cluster.
285

286
    """
287
    return self._ReadFile(constants.SS_MASTER_NODE)
288

    
289
  def GetNodeList(self):
290
    """Return the list of cluster nodes.
291

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

    
297
  def GetNodePrimaryIPList(self):
298
    """Return the list of cluster nodes' primary IP.
299

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

    
305
  def GetNodeSecondaryIPList(self):
306
    """Return the list of cluster nodes' secondary IP.
307

308
    """
309
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
310
    nl = data.splitlines(False)
311
    return nl
312

    
313
  def GetClusterTags(self):
314
    """Return the cluster tags.
315

316
    """
317
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
318
    nl = data.splitlines(False)
319
    return nl
320

    
321

    
322
def GetMasterAndMyself(ss=None):
323
  """Get the master node and my own hostname.
324

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

328
  The function does not handle any errors, these should be handled in
329
  the caller (errors.ConfigurationError, errors.ResolverError).
330

331
  @param ss: either a sstore.SimpleConfigReader or a
332
      sstore.SimpleStore instance
333
  @rtype: tuple
334
  @return: a tuple (master node name, my own name)
335

336
  """
337
  if ss is None:
338
    ss = SimpleStore()
339
  return ss.GetMasterNode(), utils.HostInfo().name
340

    
341

    
342
def CheckMaster(debug, ss=None):
343
  """Checks the node setup.
344

345
  If this is the master, the function will return. Otherwise it will
346
  exit with an exit code based on the node status.
347

348
  """
349
  try:
350
    master_name, myself = GetMasterAndMyself(ss)
351
  except errors.ConfigurationError, err:
352
    print "Cluster configuration incomplete: '%s'" % str(err)
353
    sys.exit(constants.EXIT_NODESETUP_ERROR)
354
  except errors.ResolverError, err:
355
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
356
    sys.exit(constants.EXIT_NODESETUP_ERROR)
357

    
358
  if myself != master_name:
359
    if debug:
360
      sys.stderr.write("Not master, exiting.\n")
361
    sys.exit(constants.EXIT_NOTMASTER)
362

    
363

    
364
def CheckMasterCandidate(debug, ss=None):
365
  """Checks the node setup.
366

367
  If this is a master candidate, the function will return. Otherwise it will
368
  exit with an exit code based on the node status.
369

370
  """
371
  try:
372
    if ss is None:
373
      ss = SimpleStore()
374
    myself = utils.HostInfo().name
375
    candidates = ss.GetMasterCandidates()
376
  except errors.ConfigurationError, err:
377
    print "Cluster configuration incomplete: '%s'" % str(err)
378
    sys.exit(constants.EXIT_NODESETUP_ERROR)
379
  except errors.ResolverError, err:
380
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
381
    sys.exit(constants.EXIT_NODESETUP_ERROR)
382

    
383
  if myself not in candidates:
384
    if debug:
385
      sys.stderr.write("Not master candidate, exiting.\n")
386
    sys.exit(constants.EXIT_NOTCANDIDATE)
387