Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 34e54ebc

History | View | Annotate | Download (12.4 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 value that says whether we reloaded the configuration or
69
             not (because we decided it was already up-to-date)
70

71
    """
72
    try:
73
      cfg_stat = os.stat(self._file_name)
74
    except EnvironmentError, err:
75
      raise errors.ConfigurationError("Cannot stat config file %s: %s" %
76
                                      (self._file_name, err))
77
    inode = cfg_stat.st_ino
78
    mtime = cfg_stat.st_mtime
79
    size = cfg_stat.st_size
80

    
81
    reload = False
82
    if force or inode != self._last_inode or \
83
       mtime > self._last_mtime or \
84
       size != self._last_size:
85
      self._last_inode = inode
86
      self._last_mtime = mtime
87
      self._last_size = size
88
      reload = True
89

    
90
    if not reload:
91
      return False
92

    
93
    try:
94
      self._config_data = serializer.Load(utils.ReadFile(self._file_name))
95
    except EnvironmentError, err:
96
      raise errors.ConfigurationError("Cannot read config file %s: %s" %
97
                                      (self._file_name, err))
98
    except ValueError, err:
99
      raise errors.ConfigurationError("Cannot load config file %s: %s" %
100
                                      (self._file_name, err))
101

    
102
    self._ip_to_instance = {}
103
    self._instances_ips = []
104
    for iname in self._config_data['instances']:
105
      instance = self._config_data['instances'][iname]
106
      for nic in instance['nics']:
107
        if 'ip' in nic and nic['ip']:
108
          self._instances_ips.append(nic['ip'])
109
          self._ip_to_instance[nic['ip']] = iname
110

    
111
    self._nodes_primary_ips = []
112
    self._mc_primary_ips = []
113
    for node_name in self._config_data["nodes"]:
114
      node = self._config_data["nodes"][node_name]
115
      self._nodes_primary_ips.append(node["primary_ip"])
116
      if node["master_candidate"]:
117
        self._mc_primary_ips.append(node["primary_ip"])
118

    
119
    return True
120

    
121
  # Clients can request a reload of the config file, so we export our internal
122
  # _Load function as Reload.
123
  Reload = _Load
124

    
125
  def GetClusterName(self):
126
    return self._config_data["cluster"]["cluster_name"]
127

    
128
  def GetHostKey(self):
129
    return self._config_data["cluster"]["rsahostkeypub"]
130

    
131
  def GetMasterNode(self):
132
    return self._config_data["cluster"]["master_node"]
133

    
134
  def GetMasterIP(self):
135
    return self._config_data["cluster"]["master_ip"]
136

    
137
  def GetMasterNetdev(self):
138
    return self._config_data["cluster"]["master_netdev"]
139

    
140
  def GetFileStorageDir(self):
141
    return self._config_data["cluster"]["file_storage_dir"]
142

    
143
  def GetNodeList(self):
144
    return self._config_data["nodes"].keys()
145

    
146
  def GetConfigSerialNo(self):
147
    return self._config_data["serial_no"]
148

    
149
  def GetClusterSerialNo(self):
150
    return self._config_data["cluster"]["serial_no"]
151

    
152
  def GetNodeStatusFlags(self, node):
153
    """Get a node's status flags
154

155
    @type node: string
156
    @param node: node name
157
    @rtype: (bool, bool, bool)
158
    @return: (master_candidate, drained, offline) (or None if no such node)
159

160
    """
161
    if node not in self._config_data["nodes"]:
162
      return None
163

    
164
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
165
    drained = self._config_data["nodes"][node]["drained"]
166
    offline = self._config_data["nodes"][node]["offline"]
167
    return master_candidate, drained, offline
168

    
169
  def GetInstanceByIp(self, ip):
170
    if ip not in self._ip_to_instance:
171
      return None
172
    return self._ip_to_instance[ip]
173

    
174
  def GetNodePrimaryIp(self, node):
175
    """Get a node's primary ip
176

177
    @type node: string
178
    @param node: node name
179
    @rtype: string, or None
180
    @return: node's primary ip, or None if no such node
181

182
    """
183
    if node not in self._config_data["nodes"]:
184
      return None
185
    return self._config_data["nodes"][node]["primary_ip"]
186

    
187
  def GetInstancePrimaryNode(self, instance):
188
    """Get an instance's primary node
189

190
    @type instance: string
191
    @param instance: instance name
192
    @rtype: string, or None
193
    @return: primary node, or None if no such instance
194

195
    """
196
    if instance not in self._config_data["instances"]:
197
      return None
198
    return self._config_data["instances"][instance]["primary_node"]
199

    
200
  def GetNodesPrimaryIps(self):
201
    return self._nodes_primary_ips
202

    
203
  def GetMasterCandidatesPrimaryIps(self):
204
    return self._mc_primary_ips
205

    
206
  def GetInstancesIps(self):
207
    return self._instances_ips
208

    
209

    
210
class SimpleStore(object):
211
  """Interface to static cluster data.
212

213
  This is different that the config.ConfigWriter and
214
  SimpleConfigReader classes in that it holds data that will always be
215
  present, even on nodes which don't have all the cluster data.
216

217
  Other particularities of the datastore:
218
    - keys are restricted to predefined values
219

220
  """
221
  _SS_FILEPREFIX = "ssconf_"
222
  _VALID_KEYS = (
223
    constants.SS_CLUSTER_NAME,
224
    constants.SS_CLUSTER_TAGS,
225
    constants.SS_FILE_STORAGE_DIR,
226
    constants.SS_MASTER_CANDIDATES,
227
    constants.SS_MASTER_CANDIDATES_IPS,
228
    constants.SS_MASTER_IP,
229
    constants.SS_MASTER_NETDEV,
230
    constants.SS_MASTER_NODE,
231
    constants.SS_NODE_LIST,
232
    constants.SS_NODE_PRIMARY_IPS,
233
    constants.SS_NODE_SECONDARY_IPS,
234
    constants.SS_OFFLINE_NODES,
235
    constants.SS_ONLINE_NODES,
236
    constants.SS_INSTANCE_LIST,
237
    constants.SS_RELEASE_VERSION,
238
    )
239
  _MAX_SIZE = 131072
240

    
241
  def __init__(self, cfg_location=None):
242
    if cfg_location is None:
243
      self._cfg_dir = constants.DATA_DIR
244
    else:
245
      self._cfg_dir = cfg_location
246

    
247
  def KeyToFilename(self, key):
248
    """Convert a given key into filename.
249

250
    """
251
    if key not in self._VALID_KEYS:
252
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
253
                                   % str(key))
254

    
255
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
256
    return filename
257

    
258
  def _ReadFile(self, key):
259
    """Generic routine to read keys.
260

261
    This will read the file which holds the value requested. Errors
262
    will be changed into ConfigurationErrors.
263

264
    """
265
    filename = self.KeyToFilename(key)
266
    try:
267
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
268
    except EnvironmentError, err:
269
      raise errors.ConfigurationError("Can't read from the ssconf file:"
270
                                      " '%s'" % str(err))
271
    data = data.rstrip('\n')
272
    return data
273

    
274
  def WriteFiles(self, values):
275
    """Writes ssconf files used by external scripts.
276

277
    @type values: dict
278
    @param values: Dictionary of (name, value)
279

280
    """
281
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
282

    
283
    # Get lock while writing files
284
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
285
    try:
286
      for name, value in values.iteritems():
287
        if value and not value.endswith("\n"):
288
          value += "\n"
289
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
290
    finally:
291
      ssconf_lock.Unlock()
292

    
293
  def GetFileList(self):
294
    """Return the list of all config files.
295

296
    This is used for computing node replication data.
297

298
    """
299
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
300

    
301
  def GetClusterName(self):
302
    """Get the cluster name.
303

304
    """
305
    return self._ReadFile(constants.SS_CLUSTER_NAME)
306

    
307
  def GetFileStorageDir(self):
308
    """Get the file storage dir.
309

310
    """
311
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
312

    
313
  def GetMasterCandidates(self):
314
    """Return the list of master candidates.
315

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

    
321
  def GetMasterCandidatesIPList(self):
322
    """Return the list of master candidates' primary IP.
323

324
    """
325
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
326
    nl = data.splitlines(False)
327
    return nl
328

    
329
  def GetMasterIP(self):
330
    """Get the IP of the master node for this cluster.
331

332
    """
333
    return self._ReadFile(constants.SS_MASTER_IP)
334

    
335
  def GetMasterNetdev(self):
336
    """Get the netdev to which we'll add the master ip.
337

338
    """
339
    return self._ReadFile(constants.SS_MASTER_NETDEV)
340

    
341
  def GetMasterNode(self):
342
    """Get the hostname of the master node for this cluster.
343

344
    """
345
    return self._ReadFile(constants.SS_MASTER_NODE)
346

    
347
  def GetNodeList(self):
348
    """Return the list of cluster nodes.
349

350
    """
351
    data = self._ReadFile(constants.SS_NODE_LIST)
352
    nl = data.splitlines(False)
353
    return nl
354

    
355
  def GetNodePrimaryIPList(self):
356
    """Return the list of cluster nodes' primary IP.
357

358
    """
359
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
360
    nl = data.splitlines(False)
361
    return nl
362

    
363
  def GetNodeSecondaryIPList(self):
364
    """Return the list of cluster nodes' secondary IP.
365

366
    """
367
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
368
    nl = data.splitlines(False)
369
    return nl
370

    
371
  def GetClusterTags(self):
372
    """Return the cluster tags.
373

374
    """
375
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
376
    nl = data.splitlines(False)
377
    return nl
378

    
379

    
380
def GetMasterAndMyself(ss=None):
381
  """Get the master node and my own hostname.
382

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

386
  The function does not handle any errors, these should be handled in
387
  the caller (errors.ConfigurationError, errors.ResolverError).
388

389
  @param ss: either a sstore.SimpleConfigReader or a
390
      sstore.SimpleStore instance
391
  @rtype: tuple
392
  @return: a tuple (master node name, my own name)
393

394
  """
395
  if ss is None:
396
    ss = SimpleStore()
397
  return ss.GetMasterNode(), utils.HostInfo().name
398

    
399

    
400
def CheckMaster(debug, ss=None):
401
  """Checks the node setup.
402

403
  If this is the master, the function will return. Otherwise it will
404
  exit with an exit code based on the node status.
405

406
  """
407
  try:
408
    master_name, myself = GetMasterAndMyself(ss)
409
  except errors.ConfigurationError, err:
410
    print "Cluster configuration incomplete: '%s'" % str(err)
411
    sys.exit(constants.EXIT_NODESETUP_ERROR)
412
  except errors.ResolverError, err:
413
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
414
    sys.exit(constants.EXIT_NODESETUP_ERROR)
415

    
416
  if myself != master_name:
417
    if debug:
418
      sys.stderr.write("Not master, exiting.\n")
419
    sys.exit(constants.EXIT_NOTMASTER)
420

    
421

    
422
def CheckMasterCandidate(debug, ss=None):
423
  """Checks the node setup.
424

425
  If this is a master candidate, the function will return. Otherwise it will
426
  exit with an exit code based on the node status.
427

428
  """
429
  try:
430
    if ss is None:
431
      ss = SimpleStore()
432
    myself = utils.HostInfo().name
433
    candidates = ss.GetMasterCandidates()
434
  except errors.ConfigurationError, err:
435
    print "Cluster configuration incomplete: '%s'" % str(err)
436
    sys.exit(constants.EXIT_NODESETUP_ERROR)
437
  except errors.ResolverError, err:
438
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
439
    sys.exit(constants.EXIT_NODESETUP_ERROR)
440

    
441
  if myself not in candidates:
442
    if debug:
443
      sys.stderr.write("Not master candidate, exiting.\n")
444
    sys.exit(constants.EXIT_NOTCANDIDATE)
445