Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 84e344d4

History | View | Annotate | Download (12.9 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
    # we need a forced reload at class init time, to initialize _last_*
61
    self._Load(force=True)
62

    
63
  def _Load(self, force=False):
64
    """Loads (or reloads) the config file.
65

66
    @type force: boolean
67
    @param force: whether to force the reload without checking the mtime
68
    @rtype: boolean
69
    @return: boolean value that says whether we reloaded the configuration or
70
             not (because we decided it was already up-to-date)
71

72
    """
73
    try:
74
      cfg_stat = os.stat(self._file_name)
75
    except EnvironmentError, err:
76
      raise errors.ConfigurationError("Cannot stat config file %s: %s" %
77
                                      (self._file_name, err))
78
    inode = cfg_stat.st_ino
79
    mtime = cfg_stat.st_mtime
80
    size = cfg_stat.st_size
81

    
82
    reload = False
83
    if force or inode != self._last_inode or \
84
       mtime > self._last_mtime or \
85
       size != self._last_size:
86
      self._last_inode = inode
87
      self._last_mtime = mtime
88
      self._last_size = size
89
      reload = True
90

    
91
    if not reload:
92
      return False
93

    
94
    try:
95
      self._config_data = serializer.Load(utils.ReadFile(self._file_name))
96
    except EnvironmentError, err:
97
      raise errors.ConfigurationError("Cannot read config file %s: %s" %
98
                                      (self._file_name, err))
99
    except ValueError, err:
100
      raise errors.ConfigurationError("Cannot load config file %s: %s" %
101
                                      (self._file_name, err))
102

    
103
    self._ip_to_instance = {}
104
    self._instances_ips = []
105
    self._inst_ips_by_link = {}
106
    c_nparams = self._config_data['cluster']['nicparams'][constants.PP_DEFAULT]
107
    for iname in self._config_data['instances']:
108
      instance = self._config_data['instances'][iname]
109
      for nic in instance['nics']:
110
        if 'ip' in nic and nic['ip']:
111
          self._instances_ips.append(nic['ip'])
112
          self._ip_to_instance[nic['ip']] = iname
113
          params = objects.FillDict(c_nparams, nic['nicparams'])
114
          if not params['link'] in self._inst_ips_by_link:
115
            self._inst_ips_by_link[params['link']] = []
116
          self._inst_ips_by_link[params['link']].append(nic['ip'])
117

    
118
    self._nodes_primary_ips = []
119
    self._mc_primary_ips = []
120
    for node_name in self._config_data["nodes"]:
121
      node = self._config_data["nodes"][node_name]
122
      self._nodes_primary_ips.append(node["primary_ip"])
123
      if node["master_candidate"]:
124
        self._mc_primary_ips.append(node["primary_ip"])
125

    
126
    return True
127

    
128
  # Clients can request a reload of the config file, so we export our internal
129
  # _Load function as Reload.
130
  Reload = _Load
131

    
132
  def GetClusterName(self):
133
    return self._config_data["cluster"]["cluster_name"]
134

    
135
  def GetHostKey(self):
136
    return self._config_data["cluster"]["rsahostkeypub"]
137

    
138
  def GetMasterNode(self):
139
    return self._config_data["cluster"]["master_node"]
140

    
141
  def GetMasterIP(self):
142
    return self._config_data["cluster"]["master_ip"]
143

    
144
  def GetMasterNetdev(self):
145
    return self._config_data["cluster"]["master_netdev"]
146

    
147
  def GetFileStorageDir(self):
148
    return self._config_data["cluster"]["file_storage_dir"]
149

    
150
  def GetNodeList(self):
151
    return self._config_data["nodes"].keys()
152

    
153
  def GetConfigSerialNo(self):
154
    return self._config_data["serial_no"]
155

    
156
  def GetClusterSerialNo(self):
157
    return self._config_data["cluster"]["serial_no"]
158

    
159
  def GetNodeStatusFlags(self, node):
160
    """Get a node's status flags
161

162
    @type node: string
163
    @param node: node name
164
    @rtype: (bool, bool, bool)
165
    @return: (master_candidate, drained, offline) (or None if no such node)
166

167
    """
168
    if node not in self._config_data["nodes"]:
169
      return None
170

    
171
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
172
    drained = self._config_data["nodes"][node]["drained"]
173
    offline = self._config_data["nodes"][node]["offline"]
174
    return master_candidate, drained, offline
175

    
176
  def GetInstanceByIp(self, ip):
177
    if ip not in self._ip_to_instance:
178
      return None
179
    return self._ip_to_instance[ip]
180

    
181
  def GetNodePrimaryIp(self, node):
182
    """Get a node's primary ip
183

184
    @type node: string
185
    @param node: node name
186
    @rtype: string, or None
187
    @return: node's primary ip, or None if no such node
188

189
    """
190
    if node not in self._config_data["nodes"]:
191
      return None
192
    return self._config_data["nodes"][node]["primary_ip"]
193

    
194
  def GetInstancePrimaryNode(self, instance):
195
    """Get an instance's primary node
196

197
    @type instance: string
198
    @param instance: instance name
199
    @rtype: string, or None
200
    @return: primary node, or None if no such instance
201

202
    """
203
    if instance not in self._config_data["instances"]:
204
      return None
205
    return self._config_data["instances"][instance]["primary_node"]
206

    
207
  def GetNodesPrimaryIps(self):
208
    return self._nodes_primary_ips
209

    
210
  def GetMasterCandidatesPrimaryIps(self):
211
    return self._mc_primary_ips
212

    
213
  def GetInstancesIps(self, link):
214
    if link is None:
215
      return self._instances_ips
216
    if link in self._inst_ips_by_link:
217
      return self._inst_ips_by_link[link]
218
    else:
219
      return []
220

    
221

    
222
class SimpleStore(object):
223
  """Interface to static cluster data.
224

225
  This is different that the config.ConfigWriter and
226
  SimpleConfigReader classes in that it holds data that will always be
227
  present, even on nodes which don't have all the cluster data.
228

229
  Other particularities of the datastore:
230
    - keys are restricted to predefined values
231

232
  """
233
  _SS_FILEPREFIX = "ssconf_"
234
  _VALID_KEYS = (
235
    constants.SS_CLUSTER_NAME,
236
    constants.SS_CLUSTER_TAGS,
237
    constants.SS_FILE_STORAGE_DIR,
238
    constants.SS_MASTER_CANDIDATES,
239
    constants.SS_MASTER_CANDIDATES_IPS,
240
    constants.SS_MASTER_IP,
241
    constants.SS_MASTER_NETDEV,
242
    constants.SS_MASTER_NODE,
243
    constants.SS_NODE_LIST,
244
    constants.SS_NODE_PRIMARY_IPS,
245
    constants.SS_NODE_SECONDARY_IPS,
246
    constants.SS_OFFLINE_NODES,
247
    constants.SS_ONLINE_NODES,
248
    constants.SS_INSTANCE_LIST,
249
    constants.SS_RELEASE_VERSION,
250
    )
251
  _MAX_SIZE = 131072
252

    
253
  def __init__(self, cfg_location=None):
254
    if cfg_location is None:
255
      self._cfg_dir = constants.DATA_DIR
256
    else:
257
      self._cfg_dir = cfg_location
258

    
259
  def KeyToFilename(self, key):
260
    """Convert a given key into filename.
261

262
    """
263
    if key not in self._VALID_KEYS:
264
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
265
                                   % str(key))
266

    
267
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
268
    return filename
269

    
270
  def _ReadFile(self, key):
271
    """Generic routine to read keys.
272

273
    This will read the file which holds the value requested. Errors
274
    will be changed into ConfigurationErrors.
275

276
    """
277
    filename = self.KeyToFilename(key)
278
    try:
279
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
280
    except EnvironmentError, err:
281
      raise errors.ConfigurationError("Can't read from the ssconf file:"
282
                                      " '%s'" % str(err))
283
    data = data.rstrip('\n')
284
    return data
285

    
286
  def WriteFiles(self, values):
287
    """Writes ssconf files used by external scripts.
288

289
    @type values: dict
290
    @param values: Dictionary of (name, value)
291

292
    """
293
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
294

    
295
    # Get lock while writing files
296
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
297
    try:
298
      for name, value in values.iteritems():
299
        if value and not value.endswith("\n"):
300
          value += "\n"
301
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
302
    finally:
303
      ssconf_lock.Unlock()
304

    
305
  def GetFileList(self):
306
    """Return the list of all config files.
307

308
    This is used for computing node replication data.
309

310
    """
311
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
312

    
313
  def GetClusterName(self):
314
    """Get the cluster name.
315

316
    """
317
    return self._ReadFile(constants.SS_CLUSTER_NAME)
318

    
319
  def GetFileStorageDir(self):
320
    """Get the file storage dir.
321

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

    
325
  def GetMasterCandidates(self):
326
    """Return the list of master candidates.
327

328
    """
329
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
330
    nl = data.splitlines(False)
331
    return nl
332

    
333
  def GetMasterCandidatesIPList(self):
334
    """Return the list of master candidates' primary IP.
335

336
    """
337
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
338
    nl = data.splitlines(False)
339
    return nl
340

    
341
  def GetMasterIP(self):
342
    """Get the IP of the master node for this cluster.
343

344
    """
345
    return self._ReadFile(constants.SS_MASTER_IP)
346

    
347
  def GetMasterNetdev(self):
348
    """Get the netdev to which we'll add the master ip.
349

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

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

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

    
359
  def GetNodeList(self):
360
    """Return the list of cluster nodes.
361

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

    
367
  def GetNodePrimaryIPList(self):
368
    """Return the list of cluster nodes' primary IP.
369

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

    
375
  def GetNodeSecondaryIPList(self):
376
    """Return the list of cluster nodes' secondary IP.
377

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

    
383
  def GetClusterTags(self):
384
    """Return the cluster tags.
385

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

    
391

    
392
def GetMasterAndMyself(ss=None):
393
  """Get the master node and my own hostname.
394

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

398
  The function does not handle any errors, these should be handled in
399
  the caller (errors.ConfigurationError, errors.ResolverError).
400

401
  @param ss: either a sstore.SimpleConfigReader or a
402
      sstore.SimpleStore instance
403
  @rtype: tuple
404
  @return: a tuple (master node name, my own name)
405

406
  """
407
  if ss is None:
408
    ss = SimpleStore()
409
  return ss.GetMasterNode(), utils.HostInfo().name
410

    
411

    
412
def CheckMaster(debug, ss=None):
413
  """Checks the node setup.
414

415
  If this is the master, the function will return. Otherwise it will
416
  exit with an exit code based on the node status.
417

418
  """
419
  try:
420
    master_name, myself = GetMasterAndMyself(ss)
421
  except errors.ConfigurationError, err:
422
    print "Cluster configuration incomplete: '%s'" % str(err)
423
    sys.exit(constants.EXIT_NODESETUP_ERROR)
424
  except errors.ResolverError, err:
425
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
426
    sys.exit(constants.EXIT_NODESETUP_ERROR)
427

    
428
  if myself != master_name:
429
    if debug:
430
      sys.stderr.write("Not master, exiting.\n")
431
    sys.exit(constants.EXIT_NOTMASTER)
432

    
433

    
434
def CheckMasterCandidate(debug, ss=None):
435
  """Checks the node setup.
436

437
  If this is a master candidate, the function will return. Otherwise it will
438
  exit with an exit code based on the node status.
439

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

    
453
  if myself not in candidates:
454
    if debug:
455
      sys.stderr.write("Not master candidate, exiting.\n")
456
    sys.exit(constants.EXIT_NOTCANDIDATE)
457