Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 82599b3e

History | View | Annotate | Download (14.9 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 GetNodeList(self):
159
    return self._config_data["nodes"].keys()
160

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
258

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

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

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

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

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

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

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

    
309
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
310
    return filename
311

    
312
  def _ReadFile(self, key, default=None):
313
    """Generic routine to read keys.
314

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

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

    
330
  def WriteFiles(self, values):
331
    """Writes ssconf files used by external scripts.
332

333
    @type values: dict
334
    @param values: Dictionary of (name, value)
335

336
    """
337
    ssconf_lock = utils.FileLock.Open(constants.SSCONF_LOCK_FILE)
338

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

    
352
  def GetFileList(self):
353
    """Return the list of all config files.
354

355
    This is used for computing node replication data.
356

357
    """
358
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
359

    
360
  def GetClusterName(self):
361
    """Get the cluster name.
362

363
    """
364
    return self._ReadFile(constants.SS_CLUSTER_NAME)
365

    
366
  def GetFileStorageDir(self):
367
    """Get the file storage dir.
368

369
    """
370
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
371

    
372
  def GetMasterCandidates(self):
373
    """Return the list of master candidates.
374

375
    """
376
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
377
    nl = data.splitlines(False)
378
    return nl
379

    
380
  def GetMasterCandidatesIPList(self):
381
    """Return the list of master candidates' primary IP.
382

383
    """
384
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
385
    nl = data.splitlines(False)
386
    return nl
387

    
388
  def GetMasterIP(self):
389
    """Get the IP of the master node for this cluster.
390

391
    """
392
    return self._ReadFile(constants.SS_MASTER_IP)
393

    
394
  def GetMasterNetdev(self):
395
    """Get the netdev to which we'll add the master ip.
396

397
    """
398
    return self._ReadFile(constants.SS_MASTER_NETDEV)
399

    
400
  def GetMasterNode(self):
401
    """Get the hostname of the master node for this cluster.
402

403
    """
404
    return self._ReadFile(constants.SS_MASTER_NODE)
405

    
406
  def GetNodeList(self):
407
    """Return the list of cluster nodes.
408

409
    """
410
    data = self._ReadFile(constants.SS_NODE_LIST)
411
    nl = data.splitlines(False)
412
    return nl
413

    
414
  def GetNodePrimaryIPList(self):
415
    """Return the list of cluster nodes' primary IP.
416

417
    """
418
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
419
    nl = data.splitlines(False)
420
    return nl
421

    
422
  def GetNodeSecondaryIPList(self):
423
    """Return the list of cluster nodes' secondary IP.
424

425
    """
426
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
427
    nl = data.splitlines(False)
428
    return nl
429

    
430
  def GetNodegroupList(self):
431
    """Return the list of nodegroups.
432

433
    """
434
    data = self._ReadFile(constants.SS_NODEGROUPS)
435
    nl = data.splitlines(False)
436
    return nl
437

    
438
  def GetClusterTags(self):
439
    """Return the cluster tags.
440

441
    """
442
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
443
    nl = data.splitlines(False)
444
    return nl
445

    
446
  def GetHypervisorList(self):
447
    """Return the list of enabled hypervisors.
448

449
    """
450
    data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
451
    nl = data.splitlines(False)
452
    return nl
453

    
454
  def GetMaintainNodeHealth(self):
455
    """Return the value of the maintain_node_health option.
456

457
    """
458
    data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
459
    # we rely on the bool serialization here
460
    return data == "True"
461

    
462
  def GetUidPool(self):
463
    """Return the user-id pool definition string.
464

465
    The separator character is a newline.
466

467
    The return value can be parsed using uidpool.ParseUidPool()::
468

469
      ss = ssconf.SimpleStore()
470
      uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
471

472
    """
473
    data = self._ReadFile(constants.SS_UID_POOL)
474
    return data
475

    
476
  def GetPrimaryIPFamily(self):
477
    """Return the cluster-wide primary address family.
478

479
    """
480
    try:
481
      return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
482
                                default=netutils.IP4Address.family))
483
    except (ValueError, TypeError), err:
484
      raise errors.ConfigurationError("Error while trying to parse primary ip"
485
                                      " family: %s" % err)
486

    
487

    
488
def GetMasterAndMyself(ss=None):
489
  """Get the master node and my own hostname.
490

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

494
  The function does not handle any errors, these should be handled in
495
  the caller (errors.ConfigurationError, errors.ResolverError).
496

497
  @param ss: either a sstore.SimpleConfigReader or a
498
      sstore.SimpleStore instance
499
  @rtype: tuple
500
  @return: a tuple (master node name, my own name)
501

502
  """
503
  if ss is None:
504
    ss = SimpleStore()
505
  return ss.GetMasterNode(), netutils.Hostname.GetSysName()
506

    
507

    
508
def CheckMaster(debug, ss=None):
509
  """Checks the node setup.
510

511
  If this is the master, the function will return. Otherwise it will
512
  exit with an exit code based on the node status.
513

514
  """
515
  try:
516
    master_name, myself = GetMasterAndMyself(ss)
517
  except errors.ConfigurationError, err:
518
    print "Cluster configuration incomplete: '%s'" % str(err)
519
    sys.exit(constants.EXIT_NODESETUP_ERROR)
520
  except errors.ResolverError, err:
521
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
522
    sys.exit(constants.EXIT_NODESETUP_ERROR)
523

    
524
  if myself != master_name:
525
    if debug:
526
      sys.stderr.write("Not master, exiting.\n")
527
    sys.exit(constants.EXIT_NOTMASTER)