Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ ee501db1

History | View | Annotate | Download (15.8 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
from ganeti import pathutils
41

    
42

    
43
SSCONF_LOCK_TIMEOUT = 10
44

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

    
47

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

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

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

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

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

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

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

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

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

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

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

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

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

    
135
    return True
136

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
265

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

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

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

276
  """
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 = pathutils.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 + "/" + constants.SSCONF_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(pathutils.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 WriteSsconfFiles(values):
515
  """Update all ssconf files.
516

517
  Wrapper around L{SimpleStore.WriteFiles}.
518

519
  """
520
  ssconf.SimpleStore().WriteFiles(values)
521

    
522

    
523
def GetMasterAndMyself(ss=None):
524
  """Get the master node and my own hostname.
525

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

529
  The function does not handle any errors, these should be handled in
530
  the caller (errors.ConfigurationError, errors.ResolverError).
531

532
  @param ss: either a sstore.SimpleConfigReader or a
533
      sstore.SimpleStore instance
534
  @rtype: tuple
535
  @return: a tuple (master node name, my own name)
536

537
  """
538
  if ss is None:
539
    ss = SimpleStore()
540
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
541

    
542

    
543
def CheckMaster(debug, ss=None):
544
  """Checks the node setup.
545

546
  If this is the master, the function will return. Otherwise it will
547
  exit with an exit code based on the node status.
548

549
  """
550
  try:
551
    master_name, myself = GetMasterAndMyself(ss)
552
  except errors.ConfigurationError, err:
553
    print "Cluster configuration incomplete: '%s'" % str(err)
554
    sys.exit(constants.EXIT_NODESETUP_ERROR)
555
  except errors.ResolverError, err:
556
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
557
    sys.exit(constants.EXIT_NODESETUP_ERROR)
558

    
559
  if myself != master_name:
560
    if debug:
561
      sys.stderr.write("Not master, exiting.\n")
562
    sys.exit(constants.EXIT_NOTMASTER)