Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ efbb4fd2

History | View | Annotate | Download (12.3 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
    for iname in self._config_data['instances']:
104
      instance = self._config_data['instances'][iname]
105
      for nic in instance['nics']:
106
        if 'ip' in nic and nic['ip']:
107
          self._ip_to_instance[nic['ip']] = iname
108

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

    
117
    return True
118

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

    
123
  def GetClusterName(self):
124
    return self._config_data["cluster"]["cluster_name"]
125

    
126
  def GetHostKey(self):
127
    return self._config_data["cluster"]["rsahostkeypub"]
128

    
129
  def GetMasterNode(self):
130
    return self._config_data["cluster"]["master_node"]
131

    
132
  def GetMasterIP(self):
133
    return self._config_data["cluster"]["master_ip"]
134

    
135
  def GetMasterNetdev(self):
136
    return self._config_data["cluster"]["master_netdev"]
137

    
138
  def GetFileStorageDir(self):
139
    return self._config_data["cluster"]["file_storage_dir"]
140

    
141
  def GetNodeList(self):
142
    return self._config_data["nodes"].keys()
143

    
144
  def GetConfigSerialNo(self):
145
    return self._config_data["serial_no"]
146

    
147
  def GetClusterSerialNo(self):
148
    return self._config_data["cluster"]["serial_no"]
149

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

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

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

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

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

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

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

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

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

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

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

    
198
  def GetNodesPrimaryIps(self):
199
    return self._nodes_primary_ips
200

    
201
  def GetMasterCandidatesPrimaryIps(self):
202
    return self._mc_primary_ips
203

    
204

    
205
class SimpleStore(object):
206
  """Interface to static cluster data.
207

208
  This is different that the config.ConfigWriter and
209
  SimpleConfigReader classes in that it holds data that will always be
210
  present, even on nodes which don't have all the cluster data.
211

212
  Other particularities of the datastore:
213
    - keys are restricted to predefined values
214

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

    
236
  def __init__(self, cfg_location=None):
237
    if cfg_location is None:
238
      self._cfg_dir = constants.DATA_DIR
239
    else:
240
      self._cfg_dir = cfg_location
241

    
242
  def KeyToFilename(self, key):
243
    """Convert a given key into filename.
244

245
    """
246
    if key not in self._VALID_KEYS:
247
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
248
                                   % str(key))
249

    
250
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
251
    return filename
252

    
253
  def _ReadFile(self, key):
254
    """Generic routine to read keys.
255

256
    This will read the file which holds the value requested. Errors
257
    will be changed into ConfigurationErrors.
258

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

    
269
  def WriteFiles(self, values):
270
    """Writes ssconf files used by external scripts.
271

272
    @type values: dict
273
    @param values: Dictionary of (name, value)
274

275
    """
276
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
277

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

    
288
  def GetFileList(self):
289
    """Return the list of all config files.
290

291
    This is used for computing node replication data.
292

293
    """
294
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
295

    
296
  def GetClusterName(self):
297
    """Get the cluster name.
298

299
    """
300
    return self._ReadFile(constants.SS_CLUSTER_NAME)
301

    
302
  def GetFileStorageDir(self):
303
    """Get the file storage dir.
304

305
    """
306
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
307

    
308
  def GetMasterCandidates(self):
309
    """Return the list of master candidates.
310

311
    """
312
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
313
    nl = data.splitlines(False)
314
    return nl
315

    
316
  def GetMasterCandidatesIPList(self):
317
    """Return the list of master candidates' primary IP.
318

319
    """
320
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
321
    nl = data.splitlines(False)
322
    return nl
323

    
324
  def GetMasterIP(self):
325
    """Get the IP of the master node for this cluster.
326

327
    """
328
    return self._ReadFile(constants.SS_MASTER_IP)
329

    
330
  def GetMasterNetdev(self):
331
    """Get the netdev to which we'll add the master ip.
332

333
    """
334
    return self._ReadFile(constants.SS_MASTER_NETDEV)
335

    
336
  def GetMasterNode(self):
337
    """Get the hostname of the master node for this cluster.
338

339
    """
340
    return self._ReadFile(constants.SS_MASTER_NODE)
341

    
342
  def GetNodeList(self):
343
    """Return the list of cluster nodes.
344

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

    
350
  def GetNodePrimaryIPList(self):
351
    """Return the list of cluster nodes' primary IP.
352

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

    
358
  def GetNodeSecondaryIPList(self):
359
    """Return the list of cluster nodes' secondary IP.
360

361
    """
362
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
363
    nl = data.splitlines(False)
364
    return nl
365

    
366
  def GetClusterTags(self):
367
    """Return the cluster tags.
368

369
    """
370
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
371
    nl = data.splitlines(False)
372
    return nl
373

    
374

    
375
def GetMasterAndMyself(ss=None):
376
  """Get the master node and my own hostname.
377

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

381
  The function does not handle any errors, these should be handled in
382
  the caller (errors.ConfigurationError, errors.ResolverError).
383

384
  @param ss: either a sstore.SimpleConfigReader or a
385
      sstore.SimpleStore instance
386
  @rtype: tuple
387
  @return: a tuple (master node name, my own name)
388

389
  """
390
  if ss is None:
391
    ss = SimpleStore()
392
  return ss.GetMasterNode(), utils.HostInfo().name
393

    
394

    
395
def CheckMaster(debug, ss=None):
396
  """Checks the node setup.
397

398
  If this is the master, the function will return. Otherwise it will
399
  exit with an exit code based on the node status.
400

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

    
411
  if myself != master_name:
412
    if debug:
413
      sys.stderr.write("Not master, exiting.\n")
414
    sys.exit(constants.EXIT_NOTMASTER)
415

    
416

    
417
def CheckMasterCandidate(debug, ss=None):
418
  """Checks the node setup.
419

420
  If this is a master candidate, the function will return. Otherwise it will
421
  exit with an exit code based on the node status.
422

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

    
436
  if myself not in candidates:
437
    if debug:
438
      sys.stderr.write("Not master candidate, exiting.\n")
439
    sys.exit(constants.EXIT_NOTCANDIDATE)
440