Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ b4478d34

History | View | Annotate | Download (13.5 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._instances_ips = 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_inst_by_link = {}
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
          params = objects.FillDict(c_nparams, nic['nicparams'])
118
          if not params['link'] in self._inst_ips_by_link:
119
            self._inst_ips_by_link[params['link']] = []
120
            self._ip_to_inst_by_link[params['link']] = {}
121
          self._ip_to_inst_by_link[params['link']][nic['ip']] = iname
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 GetInstanceByLinkIp(self, ip, link):
189
    if not link:
190
      link = self.GetDefaultNicLink()
191
    if not link in self._ip_to_inst_by_link:
192
      return None
193
    if not ip in self._ip_to_inst_by_link[link]:
194
      return None
195
    return self._ip_to_inst_by_link[link][ip]
196

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

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

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

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

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

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

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

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

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

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

    
238

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

325
    This is used for computing node replication data.
326

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
408

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

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

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

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

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

    
428

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

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

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

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

    
450

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

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

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

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