Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ ae130c81

History | View | Annotate | Download (11.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
    self._ip_to_instance = {}
99
    for iname in self._config_data['instances']:
100
      instance = self._config_data['instances'][iname]
101
      for nic in instance['nics']:
102
        if 'ip' in nic and nic['ip']:
103
          self._ip_to_instance[nic['ip']] = iname
104

    
105
    return True
106

    
107
  # Clients can request a reload of the config file, so we export our internal
108
  # _Load function as Reload.
109
  Reload = _Load
110

    
111
  def GetClusterName(self):
112
    return self._config_data["cluster"]["cluster_name"]
113

    
114
  def GetHostKey(self):
115
    return self._config_data["cluster"]["rsahostkeypub"]
116

    
117
  def GetMasterNode(self):
118
    return self._config_data["cluster"]["master_node"]
119

    
120
  def GetMasterIP(self):
121
    return self._config_data["cluster"]["master_ip"]
122

    
123
  def GetMasterNetdev(self):
124
    return self._config_data["cluster"]["master_netdev"]
125

    
126
  def GetFileStorageDir(self):
127
    return self._config_data["cluster"]["file_storage_dir"]
128

    
129
  def GetHypervisorType(self):
130
    return self._config_data["cluster"]["hypervisor"]
131

    
132
  def GetNodeList(self):
133
    return self._config_data["nodes"].keys()
134

    
135
  def GetConfigSerialNo(self):
136
    return self._config_data["serial_no"]
137

    
138
  def GetClusterSerialNo(self):
139
    return self._config_data["cluster"]["serial_no"]
140

    
141
  def GetNodeStatusFlags(self, node):
142
    """Get a node's status flags
143

144
    @type node: string
145
    @param node: node name
146
    @rtype: (bool, bool, bool)
147
    @return: (master_candidate, drained, offline) (or None if no such node)
148

149
    """
150
    if node not in self._config_data["nodes"]:
151
      return None
152

    
153
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
154
    drained = self._config_data["nodes"][node]["drained"]
155
    offline = self._config_data["nodes"][node]["offline"]
156
    return master_candidate, drained, offline
157

    
158
  def GetInstanceByIp(self, ip):
159
    if ip not in self._ip_to_instance:
160
      return None
161
    return self._ip_to_instance[ip]
162

    
163
  def GetNodePrimaryIp(self, node):
164
    """Get a node's primary ip
165

166
    @type node: string
167
    @param node: node name
168
    @rtype: string, or None
169
    @return: node's primary ip, or None if no such node
170

171
    """
172
    if node not in self._config_data["nodes"]:
173
      return None
174
    return self._config_data["nodes"][node]["primary_ip"]
175

    
176
  def GetInstancePrimaryNode(self, instance):
177
    """Get an instance's primary node
178

179
    @type instance: string
180
    @param instance: instance name
181
    @rtype: string, or None
182
    @return: primary node, or None if no such instance
183

184
    """
185
    if instance not in self._config_data["instances"]:
186
      return None
187
    return self._config_data["instances"][instance]["primary_node"]
188

    
189

    
190
class SimpleStore(object):
191
  """Interface to static cluster data.
192

193
  This is different that the config.ConfigWriter and
194
  SimpleConfigReader classes in that it holds data that will always be
195
  present, even on nodes which don't have all the cluster data.
196

197
  Other particularities of the datastore:
198
    - keys are restricted to predefined values
199

200
  """
201
  _SS_FILEPREFIX = "ssconf_"
202
  _VALID_KEYS = (
203
    constants.SS_CLUSTER_NAME,
204
    constants.SS_CLUSTER_TAGS,
205
    constants.SS_FILE_STORAGE_DIR,
206
    constants.SS_MASTER_CANDIDATES,
207
    constants.SS_MASTER_CANDIDATES_IPS,
208
    constants.SS_MASTER_IP,
209
    constants.SS_MASTER_NETDEV,
210
    constants.SS_MASTER_NODE,
211
    constants.SS_NODE_LIST,
212
    constants.SS_NODE_PRIMARY_IPS,
213
    constants.SS_NODE_SECONDARY_IPS,
214
    constants.SS_OFFLINE_NODES,
215
    constants.SS_ONLINE_NODES,
216
    constants.SS_INSTANCE_LIST,
217
    constants.SS_RELEASE_VERSION,
218
    )
219
  _MAX_SIZE = 131072
220

    
221
  def __init__(self, cfg_location=None):
222
    if cfg_location is None:
223
      self._cfg_dir = constants.DATA_DIR
224
    else:
225
      self._cfg_dir = cfg_location
226

    
227
  def KeyToFilename(self, key):
228
    """Convert a given key into filename.
229

230
    """
231
    if key not in self._VALID_KEYS:
232
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
233
                                   % str(key))
234

    
235
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
236
    return filename
237

    
238
  def _ReadFile(self, key):
239
    """Generic routine to read keys.
240

241
    This will read the file which holds the value requested. Errors
242
    will be changed into ConfigurationErrors.
243

244
    """
245
    filename = self.KeyToFilename(key)
246
    try:
247
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
248
    except EnvironmentError, err:
249
      raise errors.ConfigurationError("Can't read from the ssconf file:"
250
                                      " '%s'" % str(err))
251
    data = data.rstrip('\n')
252
    return data
253

    
254
  def WriteFiles(self, values):
255
    """Writes ssconf files used by external scripts.
256

257
    @type values: dict
258
    @param values: Dictionary of (name, value)
259

260
    """
261
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
262

    
263
    # Get lock while writing files
264
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
265
    try:
266
      for name, value in values.iteritems():
267
        if value and not value.endswith("\n"):
268
          value += "\n"
269
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
270
    finally:
271
      ssconf_lock.Unlock()
272

    
273
  def GetFileList(self):
274
    """Return the list of all config files.
275

276
    This is used for computing node replication data.
277

278
    """
279
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
280

    
281
  def GetClusterName(self):
282
    """Get the cluster name.
283

284
    """
285
    return self._ReadFile(constants.SS_CLUSTER_NAME)
286

    
287
  def GetFileStorageDir(self):
288
    """Get the file storage dir.
289

290
    """
291
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
292

    
293
  def GetMasterCandidates(self):
294
    """Return the list of master candidates.
295

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

    
301
  def GetMasterCandidatesIPList(self):
302
    """Return the list of master candidates' primary IP.
303

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

    
309
  def GetMasterIP(self):
310
    """Get the IP of the master node for this cluster.
311

312
    """
313
    return self._ReadFile(constants.SS_MASTER_IP)
314

    
315
  def GetMasterNetdev(self):
316
    """Get the netdev to which we'll add the master ip.
317

318
    """
319
    return self._ReadFile(constants.SS_MASTER_NETDEV)
320

    
321
  def GetMasterNode(self):
322
    """Get the hostname of the master node for this cluster.
323

324
    """
325
    return self._ReadFile(constants.SS_MASTER_NODE)
326

    
327
  def GetNodeList(self):
328
    """Return the list of cluster nodes.
329

330
    """
331
    data = self._ReadFile(constants.SS_NODE_LIST)
332
    nl = data.splitlines(False)
333
    return nl
334

    
335
  def GetNodePrimaryIPList(self):
336
    """Return the list of cluster nodes' primary IP.
337

338
    """
339
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
340
    nl = data.splitlines(False)
341
    return nl
342

    
343
  def GetNodeSecondaryIPList(self):
344
    """Return the list of cluster nodes' secondary IP.
345

346
    """
347
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
348
    nl = data.splitlines(False)
349
    return nl
350

    
351
  def GetClusterTags(self):
352
    """Return the cluster tags.
353

354
    """
355
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
356
    nl = data.splitlines(False)
357
    return nl
358

    
359

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

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

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

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

374
  """
375
  if ss is None:
376
    ss = SimpleStore()
377
  return ss.GetMasterNode(), utils.HostInfo().name
378

    
379

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

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

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

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

    
401

    
402
def CheckMasterCandidate(debug, ss=None):
403
  """Checks the node setup.
404

405
  If this is a master candidate, the function will return. Otherwise it will
406
  exit with an exit code based on the node status.
407

408
  """
409
  try:
410
    if ss is None:
411
      ss = SimpleStore()
412
    myself = utils.HostInfo().name
413
    candidates = ss.GetMasterCandidates()
414
  except errors.ConfigurationError, err:
415
    print "Cluster configuration incomplete: '%s'" % str(err)
416
    sys.exit(constants.EXIT_NODESETUP_ERROR)
417
  except errors.ResolverError, err:
418
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
419
    sys.exit(constants.EXIT_NODESETUP_ERROR)
420

    
421
  if myself not in candidates:
422
    if debug:
423
      sys.stderr.write("Not master candidate, exiting.\n")
424
    sys.exit(constants.EXIT_NOTCANDIDATE)
425