Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 47a626b0

History | View | Annotate | Download (13.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
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 GetDefaultNicParams(self):
166
    return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
167

    
168
  def GetDefaultNicLink(self):
169
    return self.GetDefaultNicParams()[constants.NIC_LINK]
170

    
171
  def GetNodeStatusFlags(self, node):
172
    """Get a node's status flags
173

174
    @type node: string
175
    @param node: node name
176
    @rtype: (bool, bool, bool)
177
    @return: (master_candidate, drained, offline) (or None if no such node)
178

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

    
183
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
184
    drained = self._config_data["nodes"][node]["drained"]
185
    offline = self._config_data["nodes"][node]["offline"]
186
    return master_candidate, drained, offline
187

    
188
  def GetInstanceByIp(self, ip):
189
    if ip not in self._ip_to_instance:
190
      return None
191
    return self._ip_to_instance[ip]
192

    
193
  def GetNodePrimaryIp(self, node):
194
    """Get a node's primary ip
195

196
    @type node: string
197
    @param node: node name
198
    @rtype: string, or None
199
    @return: node's primary ip, or None if no such node
200

201
    """
202
    if node not in self._config_data["nodes"]:
203
      return None
204
    return self._config_data["nodes"][node]["primary_ip"]
205

    
206
  def GetInstancePrimaryNode(self, instance):
207
    """Get an instance's primary node
208

209
    @type instance: string
210
    @param instance: instance name
211
    @rtype: string, or None
212
    @return: primary node, or None if no such instance
213

214
    """
215
    if instance not in self._config_data["instances"]:
216
      return None
217
    return self._config_data["instances"][instance]["primary_node"]
218

    
219
  def GetNodesPrimaryIps(self):
220
    return self._nodes_primary_ips
221

    
222
  def GetMasterCandidatesPrimaryIps(self):
223
    return self._mc_primary_ips
224

    
225
  def GetInstancesIps(self, link):
226
    if link is None:
227
      return self._instances_ips
228
    if link in self._inst_ips_by_link:
229
      return self._inst_ips_by_link[link]
230
    else:
231
      return []
232

    
233

    
234
class SimpleStore(object):
235
  """Interface to static cluster data.
236

237
  This is different that the config.ConfigWriter and
238
  SimpleConfigReader classes in that it holds data that will always be
239
  present, even on nodes which don't have all the cluster data.
240

241
  Other particularities of the datastore:
242
    - keys are restricted to predefined values
243

244
  """
245
  _SS_FILEPREFIX = "ssconf_"
246
  _VALID_KEYS = (
247
    constants.SS_CLUSTER_NAME,
248
    constants.SS_CLUSTER_TAGS,
249
    constants.SS_FILE_STORAGE_DIR,
250
    constants.SS_MASTER_CANDIDATES,
251
    constants.SS_MASTER_CANDIDATES_IPS,
252
    constants.SS_MASTER_IP,
253
    constants.SS_MASTER_NETDEV,
254
    constants.SS_MASTER_NODE,
255
    constants.SS_NODE_LIST,
256
    constants.SS_NODE_PRIMARY_IPS,
257
    constants.SS_NODE_SECONDARY_IPS,
258
    constants.SS_OFFLINE_NODES,
259
    constants.SS_ONLINE_NODES,
260
    constants.SS_INSTANCE_LIST,
261
    constants.SS_RELEASE_VERSION,
262
    )
263
  _MAX_SIZE = 131072
264

    
265
  def __init__(self, cfg_location=None):
266
    if cfg_location is None:
267
      self._cfg_dir = constants.DATA_DIR
268
    else:
269
      self._cfg_dir = cfg_location
270

    
271
  def KeyToFilename(self, key):
272
    """Convert a given key into filename.
273

274
    """
275
    if key not in self._VALID_KEYS:
276
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
277
                                   % str(key))
278

    
279
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
280
    return filename
281

    
282
  def _ReadFile(self, key):
283
    """Generic routine to read keys.
284

285
    This will read the file which holds the value requested. Errors
286
    will be changed into ConfigurationErrors.
287

288
    """
289
    filename = self.KeyToFilename(key)
290
    try:
291
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
292
    except EnvironmentError, err:
293
      raise errors.ConfigurationError("Can't read from the ssconf file:"
294
                                      " '%s'" % str(err))
295
    data = data.rstrip('\n')
296
    return data
297

    
298
  def WriteFiles(self, values):
299
    """Writes ssconf files used by external scripts.
300

301
    @type values: dict
302
    @param values: Dictionary of (name, value)
303

304
    """
305
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
306

    
307
    # Get lock while writing files
308
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
309
    try:
310
      for name, value in values.iteritems():
311
        if value and not value.endswith("\n"):
312
          value += "\n"
313
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
314
    finally:
315
      ssconf_lock.Unlock()
316

    
317
  def GetFileList(self):
318
    """Return the list of all config files.
319

320
    This is used for computing node replication data.
321

322
    """
323
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
324

    
325
  def GetClusterName(self):
326
    """Get the cluster name.
327

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

    
331
  def GetFileStorageDir(self):
332
    """Get the file storage dir.
333

334
    """
335
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
336

    
337
  def GetMasterCandidates(self):
338
    """Return the list of master candidates.
339

340
    """
341
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
342
    nl = data.splitlines(False)
343
    return nl
344

    
345
  def GetMasterCandidatesIPList(self):
346
    """Return the list of master candidates' primary IP.
347

348
    """
349
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
350
    nl = data.splitlines(False)
351
    return nl
352

    
353
  def GetMasterIP(self):
354
    """Get the IP of the master node for this cluster.
355

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

    
359
  def GetMasterNetdev(self):
360
    """Get the netdev to which we'll add the master ip.
361

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

    
365
  def GetMasterNode(self):
366
    """Get the hostname of the master node for this cluster.
367

368
    """
369
    return self._ReadFile(constants.SS_MASTER_NODE)
370

    
371
  def GetNodeList(self):
372
    """Return the list of cluster nodes.
373

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

    
379
  def GetNodePrimaryIPList(self):
380
    """Return the list of cluster nodes' primary IP.
381

382
    """
383
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
384
    nl = data.splitlines(False)
385
    return nl
386

    
387
  def GetNodeSecondaryIPList(self):
388
    """Return the list of cluster nodes' secondary IP.
389

390
    """
391
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
392
    nl = data.splitlines(False)
393
    return nl
394

    
395
  def GetClusterTags(self):
396
    """Return the cluster tags.
397

398
    """
399
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
400
    nl = data.splitlines(False)
401
    return nl
402

    
403

    
404
def GetMasterAndMyself(ss=None):
405
  """Get the master node and my own hostname.
406

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

410
  The function does not handle any errors, these should be handled in
411
  the caller (errors.ConfigurationError, errors.ResolverError).
412

413
  @param ss: either a sstore.SimpleConfigReader or a
414
      sstore.SimpleStore instance
415
  @rtype: tuple
416
  @return: a tuple (master node name, my own name)
417

418
  """
419
  if ss is None:
420
    ss = SimpleStore()
421
  return ss.GetMasterNode(), utils.HostInfo().name
422

    
423

    
424
def CheckMaster(debug, ss=None):
425
  """Checks the node setup.
426

427
  If this is the master, the function will return. Otherwise it will
428
  exit with an exit code based on the node status.
429

430
  """
431
  try:
432
    master_name, myself = GetMasterAndMyself(ss)
433
  except errors.ConfigurationError, err:
434
    print "Cluster configuration incomplete: '%s'" % str(err)
435
    sys.exit(constants.EXIT_NODESETUP_ERROR)
436
  except errors.ResolverError, err:
437
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
438
    sys.exit(constants.EXIT_NODESETUP_ERROR)
439

    
440
  if myself != master_name:
441
    if debug:
442
      sys.stderr.write("Not master, exiting.\n")
443
    sys.exit(constants.EXIT_NOTMASTER)
444

    
445

    
446
def CheckMasterCandidate(debug, ss=None):
447
  """Checks the node setup.
448

449
  If this is a master candidate, the function will return. Otherwise it will
450
  exit with an exit code based on the node status.
451

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

    
465
  if myself not in candidates:
466
    if debug:
467
      sys.stderr.write("Not master candidate, exiting.\n")
468
    sys.exit(constants.EXIT_NOTCANDIDATE)
469