Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 8062638d

History | View | Annotate | Download (14.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

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

    
40

    
41
SSCONF_LOCK_TIMEOUT = 10
42

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

    
45

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

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

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

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

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

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

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

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

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

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

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

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

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

    
133
    return True
134

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

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

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

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

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

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

    
154
  def GetFileStorageDir(self):
155
    return self._config_data["cluster"]["file_storage_dir"]
156

    
157
  def GetNodeList(self):
158
    return self._config_data["nodes"].keys()
159

    
160
  def GetConfigSerialNo(self):
161
    return self._config_data["serial_no"]
162

    
163
  def GetClusterSerialNo(self):
164
    return self._config_data["cluster"]["serial_no"]
165

    
166
  def GetDefaultNicParams(self):
167
    return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
168

    
169
  def GetDefaultNicLink(self):
170
    return self.GetDefaultNicParams()[constants.NIC_LINK]
171

    
172
  def GetNodeStatusFlags(self, node):
173
    """Get a node's status flags
174

175
    @type node: string
176
    @param node: node name
177
    @rtype: (bool, bool, bool)
178
    @return: (master_candidate, drained, offline) (or None if no such node)
179

180
    """
181
    if node not in self._config_data["nodes"]:
182
      return None
183

    
184
    master_candidate = self._config_data["nodes"][node]["master_candidate"]
185
    drained = self._config_data["nodes"][node]["drained"]
186
    offline = self._config_data["nodes"][node]["offline"]
187
    return master_candidate, drained, offline
188

    
189
  def GetInstanceByLinkIp(self, ip, link):
190
    """Get instance name from its link and ip address.
191

192
    @type ip: string
193
    @param ip: ip address
194
    @type link: string
195
    @param link: nic link
196
    @rtype: string
197
    @return: instance name
198

199
    """
200
    if not link:
201
      link = self.GetDefaultNicLink()
202
    if not link in self._ip_to_inst_by_link:
203
      return None
204
    if not ip in self._ip_to_inst_by_link[link]:
205
      return None
206
    return self._ip_to_inst_by_link[link][ip]
207

    
208
  def GetNodePrimaryIp(self, node):
209
    """Get a node's primary ip
210

211
    @type node: string
212
    @param node: node name
213
    @rtype: string, or None
214
    @return: node's primary ip, or None if no such node
215

216
    """
217
    if node not in self._config_data["nodes"]:
218
      return None
219
    return self._config_data["nodes"][node]["primary_ip"]
220

    
221
  def GetInstancePrimaryNode(self, instance):
222
    """Get an instance's primary node
223

224
    @type instance: string
225
    @param instance: instance name
226
    @rtype: string, or None
227
    @return: primary node, or None if no such instance
228

229
    """
230
    if instance not in self._config_data["instances"]:
231
      return None
232
    return self._config_data["instances"][instance]["primary_node"]
233

    
234
  def GetNodesPrimaryIps(self):
235
    return self._nodes_primary_ips
236

    
237
  def GetMasterCandidatesPrimaryIps(self):
238
    return self._mc_primary_ips
239

    
240
  def GetInstancesIps(self, link):
241
    """Get list of nic ips connected to a certain link.
242

243
    @type link: string
244
    @param link: nic link
245
    @rtype: list
246
    @return: list of ips connected to that link
247

248
    """
249
    if not link:
250
      link = self.GetDefaultNicLink()
251

    
252
    if link in self._inst_ips_by_link:
253
      return self._inst_ips_by_link[link]
254
    else:
255
      return []
256

    
257

    
258
class SimpleStore(object):
259
  """Interface to static cluster data.
260

261
  This is different that the config.ConfigWriter and
262
  SimpleConfigReader classes in that it holds data that will always be
263
  present, even on nodes which don't have all the cluster data.
264

265
  Other particularities of the datastore:
266
    - keys are restricted to predefined values
267

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

    
293
  def __init__(self, cfg_location=None):
294
    if cfg_location is None:
295
      self._cfg_dir = constants.DATA_DIR
296
    else:
297
      self._cfg_dir = cfg_location
298

    
299
  def KeyToFilename(self, key):
300
    """Convert a given key into filename.
301

302
    """
303
    if key not in self._VALID_KEYS:
304
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
305
                                   % str(key))
306

    
307
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
308
    return filename
309

    
310
  def _ReadFile(self, key):
311
    """Generic routine to read keys.
312

313
    This will read the file which holds the value requested. Errors
314
    will be changed into ConfigurationErrors.
315

316
    """
317
    filename = self.KeyToFilename(key)
318
    try:
319
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
320
    except EnvironmentError, err:
321
      raise errors.ConfigurationError("Can't read from the ssconf file:"
322
                                      " '%s'" % str(err))
323
    data = data.rstrip('\n')
324
    return data
325

    
326
  def WriteFiles(self, values):
327
    """Writes ssconf files used by external scripts.
328

329
    @type values: dict
330
    @param values: Dictionary of (name, value)
331

332
    """
333
    ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE)
334

    
335
    # Get lock while writing files
336
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
337
    try:
338
      for name, value in values.iteritems():
339
        if value and not value.endswith("\n"):
340
          value += "\n"
341
        if len(value) > self._MAX_SIZE:
342
          raise errors.ConfigurationError("ssconf file %s above maximum size" %
343
                                          name)
344
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
345
    finally:
346
      ssconf_lock.Unlock()
347

    
348
  def GetFileList(self):
349
    """Return the list of all config files.
350

351
    This is used for computing node replication data.
352

353
    """
354
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
355

    
356
  def GetClusterName(self):
357
    """Get the cluster name.
358

359
    """
360
    return self._ReadFile(constants.SS_CLUSTER_NAME)
361

    
362
  def GetFileStorageDir(self):
363
    """Get the file storage dir.
364

365
    """
366
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
367

    
368
  def GetMasterCandidates(self):
369
    """Return the list of master candidates.
370

371
    """
372
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
373
    nl = data.splitlines(False)
374
    return nl
375

    
376
  def GetMasterCandidatesIPList(self):
377
    """Return the list of master candidates' primary IP.
378

379
    """
380
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
381
    nl = data.splitlines(False)
382
    return nl
383

    
384
  def GetMasterIP(self):
385
    """Get the IP of the master node for this cluster.
386

387
    """
388
    return self._ReadFile(constants.SS_MASTER_IP)
389

    
390
  def GetMasterNetdev(self):
391
    """Get the netdev to which we'll add the master ip.
392

393
    """
394
    return self._ReadFile(constants.SS_MASTER_NETDEV)
395

    
396
  def GetMasterNode(self):
397
    """Get the hostname of the master node for this cluster.
398

399
    """
400
    return self._ReadFile(constants.SS_MASTER_NODE)
401

    
402
  def GetNodeList(self):
403
    """Return the list of cluster nodes.
404

405
    """
406
    data = self._ReadFile(constants.SS_NODE_LIST)
407
    nl = data.splitlines(False)
408
    return nl
409

    
410
  def GetNodePrimaryIPList(self):
411
    """Return the list of cluster nodes' primary IP.
412

413
    """
414
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
415
    nl = data.splitlines(False)
416
    return nl
417

    
418
  def GetNodeSecondaryIPList(self):
419
    """Return the list of cluster nodes' secondary IP.
420

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

    
426
  def GetClusterTags(self):
427
    """Return the cluster tags.
428

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

    
434
  def GetHypervisorList(self):
435
    """Return the list of enabled hypervisors.
436

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

    
442
  def GetMaintainNodeHealth(self):
443
    """Return the value of the maintain_node_health option.
444

445
    """
446
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
447
    # we rely on the bool serialization here
448
    return data == "True"
449

    
450
  def GetUidPool(self):
451
    """Return the user-id pool definition string.
452

453
    The separator character is a newline.
454

455
    The return value can be parsed using uidpool.ParseUidPool()::
456

457
      ss = ssconf.SimpleStore()
458
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
459

460
    """
461
    data = self._ReadFile(constants.SS_UID_POOL)
462
    return data
463

    
464
  def GetPrimaryIPFamily(self):
465
    """Return the cluster-wide primary address family.
466

467
    """
468
    try:
469
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY))
470
    except (ValueError, TypeError), err:
471
      raise errors.ConfigurationError("Error while trying to parse primary ip"
472
                                      " family: %s" % err)
473

    
474

    
475
def GetMasterAndMyself(ss=None):
476
  """Get the master node and my own hostname.
477

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

481
  The function does not handle any errors, these should be handled in
482
  the caller (errors.ConfigurationError, errors.ResolverError).
483

484
  @param ss: either a sstore.SimpleConfigReader or a
485
      sstore.SimpleStore instance
486
  @rtype: tuple
487
  @return: a tuple (master node name, my own name)
488

489
  """
490
  if ss is None:
491
    ss = SimpleStore()
492
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
493

    
494

    
495
def CheckMaster(debug, ss=None):
496
  """Checks the node setup.
497

498
  If this is the master, the function will return. Otherwise it will
499
  exit with an exit code based on the node status.
500

501
  """
502
  try:
503
    master_name, myself = GetMasterAndMyself(ss)
504
  except errors.ConfigurationError, err:
505
    print "Cluster configuration incomplete: '%s'" % str(err)
506
    sys.exit(constants.EXIT_NODESETUP_ERROR)
507
  except errors.ResolverError, err:
508
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
509
    sys.exit(constants.EXIT_NODESETUP_ERROR)
510

    
511
  if myself != master_name:
512
    if debug:
513
      sys.stderr.write("Not master, exiting.\n")
514
    sys.exit(constants.EXIT_NOTMASTER)