Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 69b99987

History | View | Annotate | Download (13.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
from ganeti import objects
38

    
39

    
40
SSCONF_LOCK_TIMEOUT = 10
41

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

    
44

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

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

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

55
    """
56
    self._file_name = file_name
57
    self._last_inode = None
58
    self._last_mtime = None
59
    self._last_size = None
60

    
61
    self._config_data = None
62
    self._instances_ips = None
63
    self._inst_ips_by_link = None
64
    self._ip_to_instance = None
65
    self._mc_primary_ips = None
66
    self._nodes_primary_ips = None
67

    
68
    # we need a forced reload at class init time, to initialize _last_*
69
    self._Load(force=True)
70

    
71
  def _Load(self, force=False):
72
    """Loads (or reloads) the config file.
73

74
    @type force: boolean
75
    @param force: whether to force the reload without checking the mtime
76
    @rtype: boolean
77
    @return: boolean value that says whether we reloaded the configuration or
78
             not (because we decided it was already up-to-date)
79

80
    """
81
    try:
82
      cfg_stat = os.stat(self._file_name)
83
    except EnvironmentError, err:
84
      raise errors.ConfigurationError("Cannot stat config file %s: %s" %
85
                                      (self._file_name, err))
86
    inode = cfg_stat.st_ino
87
    mtime = cfg_stat.st_mtime
88
    size = cfg_stat.st_size
89

    
90
    if (force or inode != self._last_inode or
91
        mtime > self._last_mtime or
92
        size != self._last_size):
93
      self._last_inode = inode
94
      self._last_mtime = mtime
95
      self._last_size = size
96
    else:
97
      # Don't reload
98
      return False
99

    
100
    try:
101
      self._config_data = serializer.Load(utils.ReadFile(self._file_name))
102
    except EnvironmentError, err:
103
      raise errors.ConfigurationError("Cannot read config file %s: %s" %
104
                                      (self._file_name, err))
105
    except ValueError, err:
106
      raise errors.ConfigurationError("Cannot load config file %s: %s" %
107
                                      (self._file_name, err))
108

    
109
    self._ip_to_instance = {}
110
    self._instances_ips = []
111
    self._inst_ips_by_link = {}
112
    c_nparams = self._config_data['cluster']['nicparams'][constants.PP_DEFAULT]
113
    for iname in self._config_data['instances']:
114
      instance = self._config_data['instances'][iname]
115
      for nic in instance['nics']:
116
        if 'ip' in nic and nic['ip']:
117
          self._instances_ips.append(nic['ip'])
118
          self._ip_to_instance[nic['ip']] = iname
119
          params = objects.FillDict(c_nparams, nic['nicparams'])
120
          if not params['link'] in self._inst_ips_by_link:
121
            self._inst_ips_by_link[params['link']] = []
122
          self._inst_ips_by_link[params['link']].append(nic['ip'])
123

    
124
    self._nodes_primary_ips = []
125
    self._mc_primary_ips = []
126
    for node_name in self._config_data["nodes"]:
127
      node = self._config_data["nodes"][node_name]
128
      self._nodes_primary_ips.append(node["primary_ip"])
129
      if node["master_candidate"]:
130
        self._mc_primary_ips.append(node["primary_ip"])
131

    
132
    return True
133

    
134
  # Clients can request a reload of the config file, so we export our internal
135
  # _Load function as Reload.
136
  Reload = _Load
137

    
138
  def GetClusterName(self):
139
    return self._config_data["cluster"]["cluster_name"]
140

    
141
  def GetHostKey(self):
142
    return self._config_data["cluster"]["rsahostkeypub"]
143

    
144
  def GetMasterNode(self):
145
    return self._config_data["cluster"]["master_node"]
146

    
147
  def GetMasterIP(self):
148
    return self._config_data["cluster"]["master_ip"]
149

    
150
  def GetMasterNetdev(self):
151
    return self._config_data["cluster"]["master_netdev"]
152

    
153
  def GetFileStorageDir(self):
154
    return self._config_data["cluster"]["file_storage_dir"]
155

    
156
  def GetNodeList(self):
157
    return self._config_data["nodes"].keys()
158

    
159
  def GetConfigSerialNo(self):
160
    return self._config_data["serial_no"]
161

    
162
  def GetClusterSerialNo(self):
163
    return self._config_data["cluster"]["serial_no"]
164

    
165
  def GetNodeStatusFlags(self, node):
166
    """Get a node's status flags
167

168
    @type node: string
169
    @param node: node name
170
    @rtype: (bool, bool, bool)
171
    @return: (master_candidate, drained, offline) (or None if no such node)
172

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

    
177
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
178
    drained = self._config_data["nodes"][node]["drained"]
179
    offline = self._config_data["nodes"][node]["offline"]
180
    return master_candidate, drained, offline
181

    
182
  def GetInstanceByIp(self, ip):
183
    if ip not in self._ip_to_instance:
184
      return None
185
    return self._ip_to_instance[ip]
186

    
187
  def GetNodePrimaryIp(self, node):
188
    """Get a node's primary ip
189

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

195
    """
196
    if node not in self._config_data["nodes"]:
197
      return None
198
    return self._config_data["nodes"][node]["primary_ip"]
199

    
200
  def GetInstancePrimaryNode(self, instance):
201
    """Get an instance's primary node
202

203
    @type instance: string
204
    @param instance: instance name
205
    @rtype: string, or None
206
    @return: primary node, or None if no such instance
207

208
    """
209
    if instance not in self._config_data["instances"]:
210
      return None
211
    return self._config_data["instances"][instance]["primary_node"]
212

    
213
  def GetNodesPrimaryIps(self):
214
    return self._nodes_primary_ips
215

    
216
  def GetMasterCandidatesPrimaryIps(self):
217
    return self._mc_primary_ips
218

    
219
  def GetInstancesIps(self, link):
220
    if link is None:
221
      return self._instances_ips
222
    if link in self._inst_ips_by_link:
223
      return self._inst_ips_by_link[link]
224
    else:
225
      return []
226

    
227

    
228
class SimpleStore(object):
229
  """Interface to static cluster data.
230

231
  This is different that the config.ConfigWriter and
232
  SimpleConfigReader classes in that it holds data that will always be
233
  present, even on nodes which don't have all the cluster data.
234

235
  Other particularities of the datastore:
236
    - keys are restricted to predefined values
237

238
  """
239
  _SS_FILEPREFIX = "ssconf_"
240
  _VALID_KEYS = (
241
    constants.SS_CLUSTER_NAME,
242
    constants.SS_CLUSTER_TAGS,
243
    constants.SS_FILE_STORAGE_DIR,
244
    constants.SS_MASTER_CANDIDATES,
245
    constants.SS_MASTER_CANDIDATES_IPS,
246
    constants.SS_MASTER_IP,
247
    constants.SS_MASTER_NETDEV,
248
    constants.SS_MASTER_NODE,
249
    constants.SS_NODE_LIST,
250
    constants.SS_NODE_PRIMARY_IPS,
251
    constants.SS_NODE_SECONDARY_IPS,
252
    constants.SS_OFFLINE_NODES,
253
    constants.SS_ONLINE_NODES,
254
    constants.SS_INSTANCE_LIST,
255
    constants.SS_RELEASE_VERSION,
256
    )
257
  _MAX_SIZE = 131072
258

    
259
  def __init__(self, cfg_location=None):
260
    if cfg_location is None:
261
      self._cfg_dir = constants.DATA_DIR
262
    else:
263
      self._cfg_dir = cfg_location
264

    
265
  def KeyToFilename(self, key):
266
    """Convert a given key into filename.
267

268
    """
269
    if key not in self._VALID_KEYS:
270
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
271
                                   % str(key))
272

    
273
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
274
    return filename
275

    
276
  def _ReadFile(self, key):
277
    """Generic routine to read keys.
278

279
    This will read the file which holds the value requested. Errors
280
    will be changed into ConfigurationErrors.
281

282
    """
283
    filename = self.KeyToFilename(key)
284
    try:
285
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
286
    except EnvironmentError, err:
287
      raise errors.ConfigurationError("Can't read from the ssconf file:"
288
                                      " '%s'" % str(err))
289
    data = data.rstrip('\n')
290
    return data
291

    
292
  def WriteFiles(self, values):
293
    """Writes ssconf files used by external scripts.
294

295
    @type values: dict
296
    @param values: Dictionary of (name, value)
297

298
    """
299
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
300

    
301
    # Get lock while writing files
302
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
303
    try:
304
      for name, value in values.iteritems():
305
        if value and not value.endswith("\n"):
306
          value += "\n"
307
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
308
    finally:
309
      ssconf_lock.Unlock()
310

    
311
  def GetFileList(self):
312
    """Return the list of all config files.
313

314
    This is used for computing node replication data.
315

316
    """
317
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
318

    
319
  def GetClusterName(self):
320
    """Get the cluster name.
321

322
    """
323
    return self._ReadFile(constants.SS_CLUSTER_NAME)
324

    
325
  def GetFileStorageDir(self):
326
    """Get the file storage dir.
327

328
    """
329
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
330

    
331
  def GetMasterCandidates(self):
332
    """Return the list of master candidates.
333

334
    """
335
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
336
    nl = data.splitlines(False)
337
    return nl
338

    
339
  def GetMasterCandidatesIPList(self):
340
    """Return the list of master candidates' primary IP.
341

342
    """
343
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
344
    nl = data.splitlines(False)
345
    return nl
346

    
347
  def GetMasterIP(self):
348
    """Get the IP of the master node for this cluster.
349

350
    """
351
    return self._ReadFile(constants.SS_MASTER_IP)
352

    
353
  def GetMasterNetdev(self):
354
    """Get the netdev to which we'll add the master ip.
355

356
    """
357
    return self._ReadFile(constants.SS_MASTER_NETDEV)
358

    
359
  def GetMasterNode(self):
360
    """Get the hostname of the master node for this cluster.
361

362
    """
363
    return self._ReadFile(constants.SS_MASTER_NODE)
364

    
365
  def GetNodeList(self):
366
    """Return the list of cluster nodes.
367

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

    
373
  def GetNodePrimaryIPList(self):
374
    """Return the list of cluster nodes' primary IP.
375

376
    """
377
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
378
    nl = data.splitlines(False)
379
    return nl
380

    
381
  def GetNodeSecondaryIPList(self):
382
    """Return the list of cluster nodes' secondary IP.
383

384
    """
385
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
386
    nl = data.splitlines(False)
387
    return nl
388

    
389
  def GetClusterTags(self):
390
    """Return the cluster tags.
391

392
    """
393
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
394
    nl = data.splitlines(False)
395
    return nl
396

    
397

    
398
def GetMasterAndMyself(ss=None):
399
  """Get the master node and my own hostname.
400

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

404
  The function does not handle any errors, these should be handled in
405
  the caller (errors.ConfigurationError, errors.ResolverError).
406

407
  @param ss: either a sstore.SimpleConfigReader or a
408
      sstore.SimpleStore instance
409
  @rtype: tuple
410
  @return: a tuple (master node name, my own name)
411

412
  """
413
  if ss is None:
414
    ss = SimpleStore()
415
  return ss.GetMasterNode(), utils.HostInfo().name
416

    
417

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

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

424
  """
425
  try:
426
    master_name, myself = GetMasterAndMyself(ss)
427
  except errors.ConfigurationError, err:
428
    print "Cluster configuration incomplete: '%s'" % str(err)
429
    sys.exit(constants.EXIT_NODESETUP_ERROR)
430
  except errors.ResolverError, err:
431
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
432
    sys.exit(constants.EXIT_NODESETUP_ERROR)
433

    
434
  if myself != master_name:
435
    if debug:
436
      sys.stderr.write("Not master, exiting.\n")
437
    sys.exit(constants.EXIT_NOTMASTER)
438

    
439

    
440
def CheckMasterCandidate(debug, ss=None):
441
  """Checks the node setup.
442

443
  If this is a master candidate, the function will return. Otherwise it will
444
  exit with an exit code based on the node status.
445

446
  """
447
  try:
448
    if ss is None:
449
      ss = SimpleStore()
450
    myself = utils.HostInfo().name
451
    candidates = ss.GetMasterCandidates()
452
  except errors.ConfigurationError, err:
453
    print "Cluster configuration incomplete: '%s'" % str(err)
454
    sys.exit(constants.EXIT_NODESETUP_ERROR)
455
  except errors.ResolverError, err:
456
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
457
    sys.exit(constants.EXIT_NODESETUP_ERROR)
458

    
459
  if myself not in candidates:
460
    if debug:
461
      sys.stderr.write("Not master candidate, exiting.\n")
462
    sys.exit(constants.EXIT_NOTCANDIDATE)
463