Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ c09254c2

History | View | Annotate | Download (15.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 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
import errno
33

    
34
from ganeti import errors
35
from ganeti import constants
36
from ganeti import utils
37
from ganeti import serializer
38
from ganeti import objects
39
from ganeti import netutils
40

    
41

    
42
SSCONF_LOCK_TIMEOUT = 10
43

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

    
46

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

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

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

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

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

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

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

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

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

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

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

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

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

    
134
    return True
135

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

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

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

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

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

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

    
155
  def GetMasterNetmask(self):
156
    return self._config_data["cluster"]["master_netmask"]
157

    
158
  def GetFileStorageDir(self):
159
    return self._config_data["cluster"]["file_storage_dir"]
160

    
161
  def GetSharedFileStorageDir(self):
162
    return self._config_data["cluster"]["shared_file_storage_dir"]
163

    
164
  def GetNodeList(self):
165
    return self._config_data["nodes"].keys()
166

    
167
  def GetConfigSerialNo(self):
168
    return self._config_data["serial_no"]
169

    
170
  def GetClusterSerialNo(self):
171
    return self._config_data["cluster"]["serial_no"]
172

    
173
  def GetDefaultNicParams(self):
174
    return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
175

    
176
  def GetDefaultNicLink(self):
177
    return self.GetDefaultNicParams()[constants.NIC_LINK]
178

    
179
  def GetNodeStatusFlags(self, node):
180
    """Get a node's status flags
181

182
    @type node: string
183
    @param node: node name
184
    @rtype: (bool, bool, bool)
185
    @return: (master_candidate, drained, offline) (or None if no such node)
186

187
    """
188
    if node not in self._config_data["nodes"]:
189
      return None
190

    
191
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
192
    drained = self._config_data["nodes"][node]["drained"]
193
    offline = self._config_data["nodes"][node]["offline"]
194
    return master_candidate, drained, offline
195

    
196
  def GetInstanceByLinkIp(self, ip, link):
197
    """Get instance name from its link and ip address.
198

199
    @type ip: string
200
    @param ip: ip address
201
    @type link: string
202
    @param link: nic link
203
    @rtype: string
204
    @return: instance name
205

206
    """
207
    if not link:
208
      link = self.GetDefaultNicLink()
209
    if not link in self._ip_to_inst_by_link:
210
      return None
211
    if not ip in self._ip_to_inst_by_link[link]:
212
      return None
213
    return self._ip_to_inst_by_link[link][ip]
214

    
215
  def GetNodePrimaryIp(self, node):
216
    """Get a node's primary ip
217

218
    @type node: string
219
    @param node: node name
220
    @rtype: string, or None
221
    @return: node's primary ip, or None if no such node
222

223
    """
224
    if node not in self._config_data["nodes"]:
225
      return None
226
    return self._config_data["nodes"][node]["primary_ip"]
227

    
228
  def GetInstancePrimaryNode(self, instance):
229
    """Get an instance's primary node
230

231
    @type instance: string
232
    @param instance: instance name
233
    @rtype: string, or None
234
    @return: primary node, or None if no such instance
235

236
    """
237
    if instance not in self._config_data["instances"]:
238
      return None
239
    return self._config_data["instances"][instance]["primary_node"]
240

    
241
  def GetNodesPrimaryIps(self):
242
    return self._nodes_primary_ips
243

    
244
  def GetMasterCandidatesPrimaryIps(self):
245
    return self._mc_primary_ips
246

    
247
  def GetInstancesIps(self, link):
248
    """Get list of nic ips connected to a certain link.
249

250
    @type link: string
251
    @param link: nic link
252
    @rtype: list
253
    @return: list of ips connected to that link
254

255
    """
256
    if not link:
257
      link = self.GetDefaultNicLink()
258

    
259
    if link in self._inst_ips_by_link:
260
      return self._inst_ips_by_link[link]
261
    else:
262
      return []
263

    
264

    
265
class SimpleStore(object):
266
  """Interface to static cluster data.
267

268
  This is different that the config.ConfigWriter and
269
  SimpleConfigReader classes in that it holds data that will always be
270
  present, even on nodes which don't have all the cluster data.
271

272
  Other particularities of the datastore:
273
    - keys are restricted to predefined values
274

275
  """
276
  _VALID_KEYS = (
277
    constants.SS_CLUSTER_NAME,
278
    constants.SS_CLUSTER_TAGS,
279
    constants.SS_FILE_STORAGE_DIR,
280
    constants.SS_SHARED_FILE_STORAGE_DIR,
281
    constants.SS_MASTER_CANDIDATES,
282
    constants.SS_MASTER_CANDIDATES_IPS,
283
    constants.SS_MASTER_IP,
284
    constants.SS_MASTER_NETDEV,
285
    constants.SS_MASTER_NETMASK,
286
    constants.SS_MASTER_NODE,
287
    constants.SS_NODE_LIST,
288
    constants.SS_NODE_PRIMARY_IPS,
289
    constants.SS_NODE_SECONDARY_IPS,
290
    constants.SS_OFFLINE_NODES,
291
    constants.SS_ONLINE_NODES,
292
    constants.SS_PRIMARY_IP_FAMILY,
293
    constants.SS_INSTANCE_LIST,
294
    constants.SS_RELEASE_VERSION,
295
    constants.SS_HYPERVISOR_LIST,
296
    constants.SS_MAINTAIN_NODE_HEALTH,
297
    constants.SS_UID_POOL,
298
    constants.SS_NODEGROUPS,
299
    )
300
  _MAX_SIZE = 131072
301

    
302
  def __init__(self, cfg_location=None):
303
    if cfg_location is None:
304
      self._cfg_dir = constants.DATA_DIR
305
    else:
306
      self._cfg_dir = cfg_location
307

    
308
  def KeyToFilename(self, key):
309
    """Convert a given key into filename.
310

311
    """
312
    if key not in self._VALID_KEYS:
313
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
314
                                   % str(key))
315

    
316
    filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
317
    return filename
318

    
319
  def _ReadFile(self, key, default=None):
320
    """Generic routine to read keys.
321

322
    This will read the file which holds the value requested. Errors
323
    will be changed into ConfigurationErrors.
324

325
    """
326
    filename = self.KeyToFilename(key)
327
    try:
328
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
329
    except EnvironmentError, err:
330
      if err.errno == errno.ENOENT and default is not None:
331
        return default
332
      raise errors.ConfigurationError("Can't read from the ssconf file:"
333
                                      " '%s'" % str(err))
334
    data = data.rstrip("\n")
335
    return data
336

    
337
  def WriteFiles(self, values):
338
    """Writes ssconf files used by external scripts.
339

340
    @type values: dict
341
    @param values: Dictionary of (name, value)
342

343
    """
344
    ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE)
345

    
346
    # Get lock while writing files
347
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
348
    try:
349
      for name, value in values.iteritems():
350
        if value and not value.endswith("\n"):
351
          value += "\n"
352
        if len(value) > self._MAX_SIZE:
353
          raise errors.ConfigurationError("ssconf file %s above maximum size" %
354
                                          name)
355
        utils.WriteFile(self.KeyToFilename(name), data=value,
356
                        mode=constants.SS_FILE_PERMS)
357
    finally:
358
      ssconf_lock.Unlock()
359

    
360
  def GetFileList(self):
361
    """Return the list of all config files.
362

363
    This is used for computing node replication data.
364

365
    """
366
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
367

    
368
  def GetClusterName(self):
369
    """Get the cluster name.
370

371
    """
372
    return self._ReadFile(constants.SS_CLUSTER_NAME)
373

    
374
  def GetFileStorageDir(self):
375
    """Get the file storage dir.
376

377
    """
378
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
379

    
380
  def GetSharedFileStorageDir(self):
381
    """Get the shared file storage dir.
382

383
    """
384
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
385

    
386
  def GetMasterCandidates(self):
387
    """Return the list of master candidates.
388

389
    """
390
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
391
    nl = data.splitlines(False)
392
    return nl
393

    
394
  def GetMasterCandidatesIPList(self):
395
    """Return the list of master candidates' primary IP.
396

397
    """
398
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
399
    nl = data.splitlines(False)
400
    return nl
401

    
402
  def GetMasterIP(self):
403
    """Get the IP of the master node for this cluster.
404

405
    """
406
    return self._ReadFile(constants.SS_MASTER_IP)
407

    
408
  def GetMasterNetdev(self):
409
    """Get the netdev to which we'll add the master ip.
410

411
    """
412
    return self._ReadFile(constants.SS_MASTER_NETDEV)
413

    
414
  def GetMasterNetmask(self):
415
    """Get the master netmask.
416

417
    """
418
    try:
419
      return self._ReadFile(constants.SS_MASTER_NETMASK)
420
    except errors.ConfigurationError:
421
      family = self.GetPrimaryIPFamily()
422
      ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
423
      return ipcls.iplen
424

    
425
  def GetMasterNode(self):
426
    """Get the hostname of the master node for this cluster.
427

428
    """
429
    return self._ReadFile(constants.SS_MASTER_NODE)
430

    
431
  def GetNodeList(self):
432
    """Return the list of cluster nodes.
433

434
    """
435
    data = self._ReadFile(constants.SS_NODE_LIST)
436
    nl = data.splitlines(False)
437
    return nl
438

    
439
  def GetNodePrimaryIPList(self):
440
    """Return the list of cluster nodes' primary IP.
441

442
    """
443
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
444
    nl = data.splitlines(False)
445
    return nl
446

    
447
  def GetNodeSecondaryIPList(self):
448
    """Return the list of cluster nodes' secondary IP.
449

450
    """
451
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
452
    nl = data.splitlines(False)
453
    return nl
454

    
455
  def GetNodegroupList(self):
456
    """Return the list of nodegroups.
457

458
    """
459
    data = self._ReadFile(constants.SS_NODEGROUPS)
460
    nl = data.splitlines(False)
461
    return nl
462

    
463
  def GetClusterTags(self):
464
    """Return the cluster tags.
465

466
    """
467
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
468
    nl = data.splitlines(False)
469
    return nl
470

    
471
  def GetHypervisorList(self):
472
    """Return the list of enabled hypervisors.
473

474
    """
475
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
476
    nl = data.splitlines(False)
477
    return nl
478

    
479
  def GetMaintainNodeHealth(self):
480
    """Return the value of the maintain_node_health option.
481

482
    """
483
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
484
    # we rely on the bool serialization here
485
    return data == "True"
486

    
487
  def GetUidPool(self):
488
    """Return the user-id pool definition string.
489

490
    The separator character is a newline.
491

492
    The return value can be parsed using uidpool.ParseUidPool()::
493

494
      ss = ssconf.SimpleStore()
495
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
496

497
    """
498
    data = self._ReadFile(constants.SS_UID_POOL)
499
    return data
500

    
501
  def GetPrimaryIPFamily(self):
502
    """Return the cluster-wide primary address family.
503

504
    """
505
    try:
506
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
507
                                default=netutils.IP4Address.family))
508
    except (ValueError, TypeError), err:
509
      raise errors.ConfigurationError("Error while trying to parse primary ip"
510
                                      " family: %s" % err)
511

    
512

    
513
def GetMasterAndMyself(ss=None):
514
  """Get the master node and my own hostname.
515

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

519
  The function does not handle any errors, these should be handled in
520
  the caller (errors.ConfigurationError, errors.ResolverError).
521

522
  @param ss: either a sstore.SimpleConfigReader or a
523
      sstore.SimpleStore instance
524
  @rtype: tuple
525
  @return: a tuple (master node name, my own name)
526

527
  """
528
  if ss is None:
529
    ss = SimpleStore()
530
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
531

    
532

    
533
def CheckMaster(debug, ss=None):
534
  """Checks the node setup.
535

536
  If this is the master, the function will return. Otherwise it will
537
  exit with an exit code based on the node status.
538

539
  """
540
  try:
541
    master_name, myself = GetMasterAndMyself(ss)
542
  except errors.ConfigurationError, err:
543
    print "Cluster configuration incomplete: '%s'" % str(err)
544
    sys.exit(constants.EXIT_NODESETUP_ERROR)
545
  except errors.ResolverError, err:
546
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
547
    sys.exit(constants.EXIT_NODESETUP_ERROR)
548

    
549
  if myself != master_name:
550
    if debug:
551
      sys.stderr.write("Not master, exiting.\n")
552
    sys.exit(constants.EXIT_NOTMASTER)