Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 98dfcaff

History | View | Annotate | Download (15.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
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 GetFileStorageDir(self):
156
    return self._config_data["cluster"]["file_storage_dir"]
157

    
158
  def GetSharedFileStorageDir(self):
159
    return self._config_data["cluster"]["shared_file_storage_dir"]
160

    
161
  def GetNodeList(self):
162
    return self._config_data["nodes"].keys()
163

    
164
  def GetConfigSerialNo(self):
165
    return self._config_data["serial_no"]
166

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

    
170
  def GetDefaultNicParams(self):
171
    return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
172

    
173
  def GetDefaultNicLink(self):
174
    return self.GetDefaultNicParams()[constants.NIC_LINK]
175

    
176
  def GetNodeStatusFlags(self, node):
177
    """Get a node's status flags
178

179
    @type node: string
180
    @param node: node name
181
    @rtype: (bool, bool, bool)
182
    @return: (master_candidate, drained, offline) (or None if no such node)
183

184
    """
185
    if node not in self._config_data["nodes"]:
186
      return None
187

    
188
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
189
    drained = self._config_data["nodes"][node]["drained"]
190
    offline = self._config_data["nodes"][node]["offline"]
191
    return master_candidate, drained, offline
192

    
193
  def GetInstanceByLinkIp(self, ip, link):
194
    """Get instance name from its link and ip address.
195

196
    @type ip: string
197
    @param ip: ip address
198
    @type link: string
199
    @param link: nic link
200
    @rtype: string
201
    @return: instance name
202

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

    
212
  def GetNodePrimaryIp(self, node):
213
    """Get a node's primary ip
214

215
    @type node: string
216
    @param node: node name
217
    @rtype: string, or None
218
    @return: node's primary ip, or None if no such node
219

220
    """
221
    if node not in self._config_data["nodes"]:
222
      return None
223
    return self._config_data["nodes"][node]["primary_ip"]
224

    
225
  def GetInstancePrimaryNode(self, instance):
226
    """Get an instance's primary node
227

228
    @type instance: string
229
    @param instance: instance name
230
    @rtype: string, or None
231
    @return: primary node, or None if no such instance
232

233
    """
234
    if instance not in self._config_data["instances"]:
235
      return None
236
    return self._config_data["instances"][instance]["primary_node"]
237

    
238
  def GetNodesPrimaryIps(self):
239
    return self._nodes_primary_ips
240

    
241
  def GetMasterCandidatesPrimaryIps(self):
242
    return self._mc_primary_ips
243

    
244
  def GetInstancesIps(self, link):
245
    """Get list of nic ips connected to a certain link.
246

247
    @type link: string
248
    @param link: nic link
249
    @rtype: list
250
    @return: list of ips connected to that link
251

252
    """
253
    if not link:
254
      link = self.GetDefaultNicLink()
255

    
256
    if link in self._inst_ips_by_link:
257
      return self._inst_ips_by_link[link]
258
    else:
259
      return []
260

    
261

    
262
class SimpleStore(object):
263
  """Interface to static cluster data.
264

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

269
  Other particularities of the datastore:
270
    - keys are restricted to predefined values
271

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

    
299
  def __init__(self, cfg_location=None):
300
    if cfg_location is None:
301
      self._cfg_dir = constants.DATA_DIR
302
    else:
303
      self._cfg_dir = cfg_location
304

    
305
  def KeyToFilename(self, key):
306
    """Convert a given key into filename.
307

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

    
313
    filename = self._cfg_dir + "/" + self._SS_FILEPREFIX + key
314
    return filename
315

    
316
  def _ReadFile(self, key, default=None):
317
    """Generic routine to read keys.
318

319
    This will read the file which holds the value requested. Errors
320
    will be changed into ConfigurationErrors.
321

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

    
334
  def WriteFiles(self, values):
335
    """Writes ssconf files used by external scripts.
336

337
    @type values: dict
338
    @param values: Dictionary of (name, value)
339

340
    """
341
    ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE)
342

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

    
357
  def GetFileList(self):
358
    """Return the list of all config files.
359

360
    This is used for computing node replication data.
361

362
    """
363
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
364

    
365
  def GetClusterName(self):
366
    """Get the cluster name.
367

368
    """
369
    return self._ReadFile(constants.SS_CLUSTER_NAME)
370

    
371
  def GetFileStorageDir(self):
372
    """Get the file storage dir.
373

374
    """
375
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
376

    
377
  def GetSharedFileStorageDir(self):
378
    """Get the shared file storage dir.
379

380
    """
381
    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
382

    
383
  def GetMasterCandidates(self):
384
    """Return the list of master candidates.
385

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

    
391
  def GetMasterCandidatesIPList(self):
392
    """Return the list of master candidates' primary IP.
393

394
    """
395
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
396
    nl = data.splitlines(False)
397
    return nl
398

    
399
  def GetMasterIP(self):
400
    """Get the IP of the master node for this cluster.
401

402
    """
403
    return self._ReadFile(constants.SS_MASTER_IP)
404

    
405
  def GetMasterNetdev(self):
406
    """Get the netdev to which we'll add the master ip.
407

408
    """
409
    return self._ReadFile(constants.SS_MASTER_NETDEV)
410

    
411
  def GetMasterNode(self):
412
    """Get the hostname of the master node for this cluster.
413

414
    """
415
    return self._ReadFile(constants.SS_MASTER_NODE)
416

    
417
  def GetNodeList(self):
418
    """Return the list of cluster nodes.
419

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

    
425
  def GetNodePrimaryIPList(self):
426
    """Return the list of cluster nodes' primary IP.
427

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

    
433
  def GetNodeSecondaryIPList(self):
434
    """Return the list of cluster nodes' secondary IP.
435

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

    
441
  def GetNodegroupList(self):
442
    """Return the list of nodegroups.
443

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

    
449
  def GetClusterTags(self):
450
    """Return the cluster tags.
451

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

    
457
  def GetHypervisorList(self):
458
    """Return the list of enabled hypervisors.
459

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

    
465
  def GetMaintainNodeHealth(self):
466
    """Return the value of the maintain_node_health option.
467

468
    """
469
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
470
    # we rely on the bool serialization here
471
    return data == "True"
472

    
473
  def GetUidPool(self):
474
    """Return the user-id pool definition string.
475

476
    The separator character is a newline.
477

478
    The return value can be parsed using uidpool.ParseUidPool()::
479

480
      ss = ssconf.SimpleStore()
481
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
482

483
    """
484
    data = self._ReadFile(constants.SS_UID_POOL)
485
    return data
486

    
487
  def GetPrimaryIPFamily(self):
488
    """Return the cluster-wide primary address family.
489

490
    """
491
    try:
492
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
493
                                default=netutils.IP4Address.family))
494
    except (ValueError, TypeError), err:
495
      raise errors.ConfigurationError("Error while trying to parse primary ip"
496
                                      " family: %s" % err)
497

    
498

    
499
def GetMasterAndMyself(ss=None):
500
  """Get the master node and my own hostname.
501

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

505
  The function does not handle any errors, these should be handled in
506
  the caller (errors.ConfigurationError, errors.ResolverError).
507

508
  @param ss: either a sstore.SimpleConfigReader or a
509
      sstore.SimpleStore instance
510
  @rtype: tuple
511
  @return: a tuple (master node name, my own name)
512

513
  """
514
  if ss is None:
515
    ss = SimpleStore()
516
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
517

    
518

    
519
def CheckMaster(debug, ss=None):
520
  """Checks the node setup.
521

522
  If this is the master, the function will return. Otherwise it will
523
  exit with an exit code based on the node status.
524

525
  """
526
  try:
527
    master_name, myself = GetMasterAndMyself(ss)
528
  except errors.ConfigurationError, err:
529
    print "Cluster configuration incomplete: '%s'" % str(err)
530
    sys.exit(constants.EXIT_NODESETUP_ERROR)
531
  except errors.ResolverError, err:
532
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
533
    sys.exit(constants.EXIT_NODESETUP_ERROR)
534

    
535
  if myself != master_name:
536
    if debug:
537
      sys.stderr.write("Not master, exiting.\n")
538
    sys.exit(constants.EXIT_NOTMASTER)