Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 0fbae49a

History | View | Annotate | Download (14.7 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
    """Get instance name from its link and ip address.
190

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
256

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
333
    # Get lock while writing files
334
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
335
    try:
336
      for name, value in values.iteritems():
337
        if value and not value.endswith("\n"):
338
          value += "\n"
339
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
340
    finally:
341
      ssconf_lock.Unlock()
342

    
343
  def GetFileList(self):
344
    """Return the list of all config files.
345

346
    This is used for computing node replication data.
347

348
    """
349
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
350

    
351
  def GetClusterName(self):
352
    """Get the cluster name.
353

354
    """
355
    return self._ReadFile(constants.SS_CLUSTER_NAME)
356

    
357
  def GetFileStorageDir(self):
358
    """Get the file storage dir.
359

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

    
363
  def GetMasterCandidates(self):
364
    """Return the list of master candidates.
365

366
    """
367
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
368
    nl = data.splitlines(False)
369
    return nl
370

    
371
  def GetMasterCandidatesIPList(self):
372
    """Return the list of master candidates' primary IP.
373

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

    
379
  def GetMasterIP(self):
380
    """Get the IP of the master node for this cluster.
381

382
    """
383
    return self._ReadFile(constants.SS_MASTER_IP)
384

    
385
  def GetMasterNetdev(self):
386
    """Get the netdev to which we'll add the master ip.
387

388
    """
389
    return self._ReadFile(constants.SS_MASTER_NETDEV)
390

    
391
  def GetMasterNode(self):
392
    """Get the hostname of the master node for this cluster.
393

394
    """
395
    return self._ReadFile(constants.SS_MASTER_NODE)
396

    
397
  def GetNodeList(self):
398
    """Return the list of cluster nodes.
399

400
    """
401
    data = self._ReadFile(constants.SS_NODE_LIST)
402
    nl = data.splitlines(False)
403
    return nl
404

    
405
  def GetNodePrimaryIPList(self):
406
    """Return the list of cluster nodes' primary IP.
407

408
    """
409
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
410
    nl = data.splitlines(False)
411
    return nl
412

    
413
  def GetNodeSecondaryIPList(self):
414
    """Return the list of cluster nodes' secondary IP.
415

416
    """
417
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
418
    nl = data.splitlines(False)
419
    return nl
420

    
421
  def GetClusterTags(self):
422
    """Return the cluster tags.
423

424
    """
425
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
426
    nl = data.splitlines(False)
427
    return nl
428

    
429
  def GetHypervisorList(self):
430
    """Return the list of enabled hypervisors.
431

432
    """
433
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
434
    nl = data.splitlines(False)
435
    return nl
436

    
437
  def GetMaintainNodeHealth(self):
438
    """Return the value of the maintain_node_health option.
439

440
    """
441
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
442
    # we rely on the bool serialization here
443
    return data == "True"
444

    
445
  def GetUidPool(self):
446
    """Return the user-id pool definition string.
447

448
    The separator character is a newline.
449

450
    The return value can be parsed using uidpool.ParseUidPool():
451
      ss = ssconf.SimpleStore()
452
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n")
453

454
    """
455
    data = self._ReadFile(constants.SS_UID_POOL)
456
    return data
457

    
458

    
459
def GetMasterAndMyself(ss=None):
460
  """Get the master node and my own hostname.
461

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

465
  The function does not handle any errors, these should be handled in
466
  the caller (errors.ConfigurationError, errors.ResolverError).
467

468
  @param ss: either a sstore.SimpleConfigReader or a
469
      sstore.SimpleStore instance
470
  @rtype: tuple
471
  @return: a tuple (master node name, my own name)
472

473
  """
474
  if ss is None:
475
    ss = SimpleStore()
476
  return ss.GetMasterNode(), utils.HostInfo().name
477

    
478

    
479
def CheckMaster(debug, ss=None):
480
  """Checks the node setup.
481

482
  If this is the master, the function will return. Otherwise it will
483
  exit with an exit code based on the node status.
484

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

    
495
  if myself != master_name:
496
    if debug:
497
      sys.stderr.write("Not master, exiting.\n")
498
    sys.exit(constants.EXIT_NOTMASTER)
499

    
500

    
501
def CheckMasterCandidate(debug, ss=None):
502
  """Checks the node setup.
503

504
  If this is a master candidate, the function will return. Otherwise it will
505
  exit with an exit code based on the node status.
506

507
  """
508
  try:
509
    if ss is None:
510
      ss = SimpleStore()
511
    myself = utils.HostInfo().name
512
    candidates = ss.GetMasterCandidates()
513
  except errors.ConfigurationError, err:
514
    print "Cluster configuration incomplete: '%s'" % str(err)
515
    sys.exit(constants.EXIT_NODESETUP_ERROR)
516
  except errors.ResolverError, err:
517
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
518
    sys.exit(constants.EXIT_NODESETUP_ERROR)
519

    
520
  if myself not in candidates:
521
    if debug:
522
      sys.stderr.write("Not master candidate, exiting.\n")
523
    sys.exit(constants.EXIT_NOTCANDIDATE)