Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 6915fe26

History | View | Annotate | Download (15.5 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 netdev to which we'll add the master ip.
417

418
    """
419
    return self._ReadFile(constants.SS_MASTER_NETMASK)
420

    
421
  def GetMasterNode(self):
422
    """Get the hostname of the master node for this cluster.
423

424
    """
425
    return self._ReadFile(constants.SS_MASTER_NODE)
426

    
427
  def GetNodeList(self):
428
    """Return the list of cluster nodes.
429

430
    """
431
    data = self._ReadFile(constants.SS_NODE_LIST)
432
    nl = data.splitlines(False)
433
    return nl
434

    
435
  def GetNodePrimaryIPList(self):
436
    """Return the list of cluster nodes' primary IP.
437

438
    """
439
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
440
    nl = data.splitlines(False)
441
    return nl
442

    
443
  def GetNodeSecondaryIPList(self):
444
    """Return the list of cluster nodes' secondary IP.
445

446
    """
447
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
448
    nl = data.splitlines(False)
449
    return nl
450

    
451
  def GetNodegroupList(self):
452
    """Return the list of nodegroups.
453

454
    """
455
    data = self._ReadFile(constants.SS_NODEGROUPS)
456
    nl = data.splitlines(False)
457
    return nl
458

    
459
  def GetClusterTags(self):
460
    """Return the cluster tags.
461

462
    """
463
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
464
    nl = data.splitlines(False)
465
    return nl
466

    
467
  def GetHypervisorList(self):
468
    """Return the list of enabled hypervisors.
469

470
    """
471
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
472
    nl = data.splitlines(False)
473
    return nl
474

    
475
  def GetMaintainNodeHealth(self):
476
    """Return the value of the maintain_node_health option.
477

478
    """
479
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
480
    # we rely on the bool serialization here
481
    return data == "True"
482

    
483
  def GetUidPool(self):
484
    """Return the user-id pool definition string.
485

486
    The separator character is a newline.
487

488
    The return value can be parsed using uidpool.ParseUidPool()::
489

490
      ss = ssconf.SimpleStore()
491
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
492

493
    """
494
    data = self._ReadFile(constants.SS_UID_POOL)
495
    return data
496

    
497
  def GetPrimaryIPFamily(self):
498
    """Return the cluster-wide primary address family.
499

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

    
508

    
509
def GetMasterAndMyself(ss=None):
510
  """Get the master node and my own hostname.
511

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

515
  The function does not handle any errors, these should be handled in
516
  the caller (errors.ConfigurationError, errors.ResolverError).
517

518
  @param ss: either a sstore.SimpleConfigReader or a
519
      sstore.SimpleStore instance
520
  @rtype: tuple
521
  @return: a tuple (master node name, my own name)
522

523
  """
524
  if ss is None:
525
    ss = SimpleStore()
526
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
527

    
528

    
529
def CheckMaster(debug, ss=None):
530
  """Checks the node setup.
531

532
  If this is the master, the function will return. Otherwise it will
533
  exit with an exit code based on the node status.
534

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

    
545
  if myself != master_name:
546
    if debug:
547
      sys.stderr.write("Not master, exiting.\n")
548
    sys.exit(constants.EXIT_NOTMASTER)