Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ b705c7a6

History | View | Annotate | Download (14.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2010 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
from ganeti import netutils
39

    
40

    
41
SSCONF_LOCK_TIMEOUT = 10
42

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

    
45

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

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

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

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

    
62
    self._config_data = None
63
    self._inst_ips_by_link = None
64
    self._ip_to_inst_by_link = None
65
    self._instances_ips = None
66
    self._mc_primary_ips = None
67
    self._nodes_primary_ips = None
68

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

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

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

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

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

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

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

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

    
133
    return True
134

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
189
  def GetInstanceByLinkIp(self, ip, link):
190
    """Get instance name from its link and ip address.
191

192
    @type ip: string
193
    @param ip: ip address
194
    @type link: string
195
    @param link: nic link
196
    @rtype: string
197
    @return: instance name
198

199
    """
200
    if not link:
201
      link = self.GetDefaultNicLink()
202
    if not link in self._ip_to_inst_by_link:
203
      return None
204
    if not ip in self._ip_to_inst_by_link[link]:
205
      return None
206
    return self._ip_to_inst_by_link[link][ip]
207

    
208
  def GetNodePrimaryIp(self, node):
209
    """Get a node's primary ip
210

211
    @type node: string
212
    @param node: node name
213
    @rtype: string, or None
214
    @return: node's primary ip, or None if no such node
215

216
    """
217
    if node not in self._config_data["nodes"]:
218
      return None
219
    return self._config_data["nodes"][node]["primary_ip"]
220

    
221
  def GetInstancePrimaryNode(self, instance):
222
    """Get an instance's primary node
223

224
    @type instance: string
225
    @param instance: instance name
226
    @rtype: string, or None
227
    @return: primary node, or None if no such instance
228

229
    """
230
    if instance not in self._config_data["instances"]:
231
      return None
232
    return self._config_data["instances"][instance]["primary_node"]
233

    
234
  def GetNodesPrimaryIps(self):
235
    return self._nodes_primary_ips
236

    
237
  def GetMasterCandidatesPrimaryIps(self):
238
    return self._mc_primary_ips
239

    
240
  def GetInstancesIps(self, link):
241
    """Get list of nic ips connected to a certain link.
242

243
    @type link: string
244
    @param link: nic link
245
    @rtype: list
246
    @return: list of ips connected to that link
247

248
    """
249
    if not link:
250
      link = self.GetDefaultNicLink()
251

    
252
    if link in self._inst_ips_by_link:
253
      return self._inst_ips_by_link[link]
254
    else:
255
      return []
256

    
257

    
258
class SimpleStore(object):
259
  """Interface to static cluster data.
260

261
  This is different that the config.ConfigWriter and
262
  SimpleConfigReader classes in that it holds data that will always be
263
  present, even on nodes which don't have all the cluster data.
264

265
  Other particularities of the datastore:
266
    - keys are restricted to predefined values
267

268
  """
269
  _SS_FILEPREFIX = "ssconf_"
270
  _VALID_KEYS = (
271
    constants.SS_CLUSTER_NAME,
272
    constants.SS_CLUSTER_TAGS,
273
    constants.SS_FILE_STORAGE_DIR,
274
    constants.SS_MASTER_CANDIDATES,
275
    constants.SS_MASTER_CANDIDATES_IPS,
276
    constants.SS_MASTER_IP,
277
    constants.SS_MASTER_NETDEV,
278
    constants.SS_MASTER_NODE,
279
    constants.SS_NODE_LIST,
280
    constants.SS_NODE_PRIMARY_IPS,
281
    constants.SS_NODE_SECONDARY_IPS,
282
    constants.SS_OFFLINE_NODES,
283
    constants.SS_ONLINE_NODES,
284
    constants.SS_INSTANCE_LIST,
285
    constants.SS_RELEASE_VERSION,
286
    constants.SS_HYPERVISOR_LIST,
287
    constants.SS_MAINTAIN_NODE_HEALTH,
288
    constants.SS_UID_POOL,
289
    )
290
  _MAX_SIZE = 131072
291

    
292
  def __init__(self, cfg_location=None):
293
    if cfg_location is None:
294
      self._cfg_dir = constants.DATA_DIR
295
    else:
296
      self._cfg_dir = cfg_location
297

    
298
  def KeyToFilename(self, key):
299
    """Convert a given key into filename.
300

301
    """
302
    if key not in self._VALID_KEYS:
303
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
304
                                   % str(key))
305

    
306
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
307
    return filename
308

    
309
  def _ReadFile(self, key):
310
    """Generic routine to read keys.
311

312
    This will read the file which holds the value requested. Errors
313
    will be changed into ConfigurationErrors.
314

315
    """
316
    filename = self.KeyToFilename(key)
317
    try:
318
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
319
    except EnvironmentError, err:
320
      raise errors.ConfigurationError("Can't read from the ssconf file:"
321
                                      " '%s'" % str(err))
322
    data = data.rstrip('\n')
323
    return data
324

    
325
  def WriteFiles(self, values):
326
    """Writes ssconf files used by external scripts.
327

328
    @type values: dict
329
    @param values: Dictionary of (name, value)
330

331
    """
332
    ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE)
333

    
334
    # Get lock while writing files
335
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
336
    try:
337
      for name, value in values.iteritems():
338
        if value and not value.endswith("\n"):
339
          value += "\n"
340
        if len(value) > self._MAX_SIZE:
341
          raise errors.ConfigurationError("ssconf file %s above maximum size" %
342
                                          name)
343
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
344
    finally:
345
      ssconf_lock.Unlock()
346

    
347
  def GetFileList(self):
348
    """Return the list of all config files.
349

350
    This is used for computing node replication data.
351

352
    """
353
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
354

    
355
  def GetClusterName(self):
356
    """Get the cluster name.
357

358
    """
359
    return self._ReadFile(constants.SS_CLUSTER_NAME)
360

    
361
  def GetFileStorageDir(self):
362
    """Get the file storage dir.
363

364
    """
365
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
366

    
367
  def GetMasterCandidates(self):
368
    """Return the list of master candidates.
369

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

    
375
  def GetMasterCandidatesIPList(self):
376
    """Return the list of master candidates' primary IP.
377

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

    
383
  def GetMasterIP(self):
384
    """Get the IP of the master node for this cluster.
385

386
    """
387
    return self._ReadFile(constants.SS_MASTER_IP)
388

    
389
  def GetMasterNetdev(self):
390
    """Get the netdev to which we'll add the master ip.
391

392
    """
393
    return self._ReadFile(constants.SS_MASTER_NETDEV)
394

    
395
  def GetMasterNode(self):
396
    """Get the hostname of the master node for this cluster.
397

398
    """
399
    return self._ReadFile(constants.SS_MASTER_NODE)
400

    
401
  def GetNodeList(self):
402
    """Return the list of cluster nodes.
403

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

    
409
  def GetNodePrimaryIPList(self):
410
    """Return the list of cluster nodes' primary IP.
411

412
    """
413
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
414
    nl = data.splitlines(False)
415
    return nl
416

    
417
  def GetNodeSecondaryIPList(self):
418
    """Return the list of cluster nodes' secondary IP.
419

420
    """
421
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
422
    nl = data.splitlines(False)
423
    return nl
424

    
425
  def GetClusterTags(self):
426
    """Return the cluster tags.
427

428
    """
429
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
430
    nl = data.splitlines(False)
431
    return nl
432

    
433
  def GetHypervisorList(self):
434
    """Return the list of enabled hypervisors.
435

436
    """
437
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
438
    nl = data.splitlines(False)
439
    return nl
440

    
441
  def GetMaintainNodeHealth(self):
442
    """Return the value of the maintain_node_health option.
443

444
    """
445
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
446
    # we rely on the bool serialization here
447
    return data == "True"
448

    
449
  def GetUidPool(self):
450
    """Return the user-id pool definition string.
451

452
    The separator character is a newline.
453

454
    The return value can be parsed using uidpool.ParseUidPool()::
455

456
      ss = ssconf.SimpleStore()
457
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
458

459
    """
460
    data = self._ReadFile(constants.SS_UID_POOL)
461
    return data
462

    
463

    
464
def GetMasterAndMyself(ss=None):
465
  """Get the master node and my own hostname.
466

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

470
  The function does not handle any errors, these should be handled in
471
  the caller (errors.ConfigurationError, errors.ResolverError).
472

473
  @param ss: either a sstore.SimpleConfigReader or a
474
      sstore.SimpleStore instance
475
  @rtype: tuple
476
  @return: a tuple (master node name, my own name)
477

478
  """
479
  if ss is None:
480
    ss = SimpleStore()
481
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
482

    
483

    
484
def CheckMaster(debug, ss=None):
485
  """Checks the node setup.
486

487
  If this is the master, the function will return. Otherwise it will
488
  exit with an exit code based on the node status.
489

490
  """
491
  try:
492
    master_name, myself = GetMasterAndMyself(ss)
493
  except errors.ConfigurationError, err:
494
    print "Cluster configuration incomplete: '%s'" % str(err)
495
    sys.exit(constants.EXIT_NODESETUP_ERROR)
496
  except errors.ResolverError, err:
497
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
498
    sys.exit(constants.EXIT_NODESETUP_ERROR)
499

    
500
  if myself != master_name:
501
    if debug:
502
      sys.stderr.write("Not master, exiting.\n")
503
    sys.exit(constants.EXIT_NOTMASTER)