Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 5c983ee5

History | View | Annotate | Download (13.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
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._inst_ips_by_link = None
63
    self._ip_to_inst_by_link = None
64
    self._mc_primary_ips = None
65
    self._nodes_primary_ips = None
66

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

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

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

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

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

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

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

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

    
131
    return True
132

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

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

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

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

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

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

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

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

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

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

    
164
  def GetDefaultNicParams(self):
165
    return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
166

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

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

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

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

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

    
187
  def GetInstanceByLinkIp(self, ip, link):
188
    if not link:
189
      link = self.GetDefaultNicLink()
190
    if not link in self._ip_to_inst_by_link:
191
      return None
192
    if not ip in self._ip_to_inst_by_link[link]:
193
      return None
194
    return self._ip_to_inst_by_link[link][ip]
195

    
196
  def GetNodePrimaryIp(self, node):
197
    """Get a node's primary ip
198

199
    @type node: string
200
    @param node: node name
201
    @rtype: string, or None
202
    @return: node's primary ip, or None if no such node
203

204
    """
205
    if node not in self._config_data["nodes"]:
206
      return None
207
    return self._config_data["nodes"][node]["primary_ip"]
208

    
209
  def GetInstancePrimaryNode(self, instance):
210
    """Get an instance's primary node
211

212
    @type instance: string
213
    @param instance: instance name
214
    @rtype: string, or None
215
    @return: primary node, or None if no such instance
216

217
    """
218
    if instance not in self._config_data["instances"]:
219
      return None
220
    return self._config_data["instances"][instance]["primary_node"]
221

    
222
  def GetNodesPrimaryIps(self):
223
    return self._nodes_primary_ips
224

    
225
  def GetMasterCandidatesPrimaryIps(self):
226
    return self._mc_primary_ips
227

    
228
  def GetInstancesIps(self, link):
229
    if not link:
230
      link = self.GetDefaultNicLink()
231

    
232
    if link in self._inst_ips_by_link:
233
      return self._inst_ips_by_link[link]
234
    else:
235
      return []
236

    
237

    
238
class SimpleStore(object):
239
  """Interface to static cluster data.
240

241
  This is different that the config.ConfigWriter and
242
  SimpleConfigReader classes in that it holds data that will always be
243
  present, even on nodes which don't have all the cluster data.
244

245
  Other particularities of the datastore:
246
    - keys are restricted to predefined values
247

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

    
269
  def __init__(self, cfg_location=None):
270
    if cfg_location is None:
271
      self._cfg_dir = constants.DATA_DIR
272
    else:
273
      self._cfg_dir = cfg_location
274

    
275
  def KeyToFilename(self, key):
276
    """Convert a given key into filename.
277

278
    """
279
    if key not in self._VALID_KEYS:
280
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
281
                                   % str(key))
282

    
283
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
284
    return filename
285

    
286
  def _ReadFile(self, key):
287
    """Generic routine to read keys.
288

289
    This will read the file which holds the value requested. Errors
290
    will be changed into ConfigurationErrors.
291

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

    
302
  def WriteFiles(self, values):
303
    """Writes ssconf files used by external scripts.
304

305
    @type values: dict
306
    @param values: Dictionary of (name, value)
307

308
    """
309
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
310

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

    
321
  def GetFileList(self):
322
    """Return the list of all config files.
323

324
    This is used for computing node replication data.
325

326
    """
327
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
328

    
329
  def GetClusterName(self):
330
    """Get the cluster name.
331

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

    
335
  def GetFileStorageDir(self):
336
    """Get the file storage dir.
337

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

    
341
  def GetMasterCandidates(self):
342
    """Return the list of master candidates.
343

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

    
349
  def GetMasterCandidatesIPList(self):
350
    """Return the list of master candidates' primary IP.
351

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

    
357
  def GetMasterIP(self):
358
    """Get the IP of the master node for this cluster.
359

360
    """
361
    return self._ReadFile(constants.SS_MASTER_IP)
362

    
363
  def GetMasterNetdev(self):
364
    """Get the netdev to which we'll add the master ip.
365

366
    """
367
    return self._ReadFile(constants.SS_MASTER_NETDEV)
368

    
369
  def GetMasterNode(self):
370
    """Get the hostname of the master node for this cluster.
371

372
    """
373
    return self._ReadFile(constants.SS_MASTER_NODE)
374

    
375
  def GetNodeList(self):
376
    """Return the list of cluster nodes.
377

378
    """
379
    data = self._ReadFile(constants.SS_NODE_LIST)
380
    nl = data.splitlines(False)
381
    return nl
382

    
383
  def GetNodePrimaryIPList(self):
384
    """Return the list of cluster nodes' primary IP.
385

386
    """
387
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
388
    nl = data.splitlines(False)
389
    return nl
390

    
391
  def GetNodeSecondaryIPList(self):
392
    """Return the list of cluster nodes' secondary IP.
393

394
    """
395
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
396
    nl = data.splitlines(False)
397
    return nl
398

    
399
  def GetClusterTags(self):
400
    """Return the cluster tags.
401

402
    """
403
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
404
    nl = data.splitlines(False)
405
    return nl
406

    
407

    
408
def GetMasterAndMyself(ss=None):
409
  """Get the master node and my own hostname.
410

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

414
  The function does not handle any errors, these should be handled in
415
  the caller (errors.ConfigurationError, errors.ResolverError).
416

417
  @param ss: either a sstore.SimpleConfigReader or a
418
      sstore.SimpleStore instance
419
  @rtype: tuple
420
  @return: a tuple (master node name, my own name)
421

422
  """
423
  if ss is None:
424
    ss = SimpleStore()
425
  return ss.GetMasterNode(), utils.HostInfo().name
426

    
427

    
428
def CheckMaster(debug, ss=None):
429
  """Checks the node setup.
430

431
  If this is the master, the function will return. Otherwise it will
432
  exit with an exit code based on the node status.
433

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

    
444
  if myself != master_name:
445
    if debug:
446
      sys.stderr.write("Not master, exiting.\n")
447
    sys.exit(constants.EXIT_NOTMASTER)
448

    
449

    
450
def CheckMasterCandidate(debug, ss=None):
451
  """Checks the node setup.
452

453
  If this is a master candidate, the function will return. Otherwise it will
454
  exit with an exit code based on the node status.
455

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

    
469
  if myself not in candidates:
470
    if debug:
471
      sys.stderr.write("Not master candidate, exiting.\n")
472
    sys.exit(constants.EXIT_NOTCANDIDATE)
473