Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 7142485a

History | View | Annotate | Download (15.7 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
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
  _SS_FILEPREFIX = "ssconf_"
277
  _VALID_KEYS = (
278
    constants.SS_CLUSTER_NAME,
279
    constants.SS_CLUSTER_TAGS,
280
    constants.SS_FILE_STORAGE_DIR,
281
    constants.SS_SHARED_FILE_STORAGE_DIR,
282
    constants.SS_MASTER_CANDIDATES,
283
    constants.SS_MASTER_CANDIDATES_IPS,
284
    constants.SS_MASTER_IP,
285
    constants.SS_MASTER_NETDEV,
286
    constants.SS_MASTER_NETMASK,
287
    constants.SS_MASTER_NODE,
288
    constants.SS_NODE_LIST,
289
    constants.SS_NODE_PRIMARY_IPS,
290
    constants.SS_NODE_SECONDARY_IPS,
291
    constants.SS_OFFLINE_NODES,
292
    constants.SS_ONLINE_NODES,
293
    constants.SS_PRIMARY_IP_FAMILY,
294
    constants.SS_INSTANCE_LIST,
295
    constants.SS_RELEASE_VERSION,
296
    constants.SS_HYPERVISOR_LIST,
297
    constants.SS_MAINTAIN_NODE_HEALTH,
298
    constants.SS_UID_POOL,
299
    constants.SS_NODEGROUPS,
300
    )
301
  _MAX_SIZE = 131072
302

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

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

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

    
317
    filename = self._cfg_dir + "/" + self._SS_FILEPREFIX + key
318
    return filename
319

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

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

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

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

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

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

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

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

364
    This is used for computing node replication data.
365

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

491
    The separator character is a newline.
492

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

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

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

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

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

    
513

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

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

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

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

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

    
533

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

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

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

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