Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ ad8b2f9b

History | View | Annotate | Download (9.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 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

    
32
from ganeti import errors
33
from ganeti import constants
34
from ganeti import utils
35
from ganeti import serializer
36

    
37

    
38
SSCONF_LOCK_TIMEOUT = 10
39

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

    
42

    
43
class SimpleConfigReader(object):
44
  """Simple class to read configuration file.
45

46
  """
47
  def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
48
    """Initializes this class.
49

50
    @type file_name: string
51
    @param file_name: Configuration file path
52

53
    """
54
    self._file_name = file_name
55
    self._Load()
56

    
57
  def _Load(self):
58
    """Loads (or reloads) the config file.
59

60
    """
61
    try:
62
      self._config_data = serializer.Load(utils.ReadFile(self._file_name))
63
    except IOError, err:
64
      raise errors.ConfigurationError("Cannot read config file %s: %s" %
65
                                      (self._file_name, err))
66
    except ValueError, err:
67
      raise errors.ConfigurationError("Cannot load config file %s: %s" %
68
                                      (self._file_name, err))
69

    
70
  # Clients can request a reload of the config file, so we export our internal
71
  # _Load function as Reload.
72
  Reload = _Load
73

    
74
  def GetClusterName(self):
75
    return self._config_data["cluster"]["cluster_name"]
76

    
77
  def GetHostKey(self):
78
    return self._config_data["cluster"]["rsahostkeypub"]
79

    
80
  def GetMasterNode(self):
81
    return self._config_data["cluster"]["master_node"]
82

    
83
  def GetMasterIP(self):
84
    return self._config_data["cluster"]["master_ip"]
85

    
86
  def GetMasterNetdev(self):
87
    return self._config_data["cluster"]["master_netdev"]
88

    
89
  def GetFileStorageDir(self):
90
    return self._config_data["cluster"]["file_storage_dir"]
91

    
92
  def GetHypervisorType(self):
93
    return self._config_data["cluster"]["hypervisor"]
94

    
95
  def GetNodeList(self):
96
    return self._config_data["nodes"].keys()
97

    
98
  def GetConfigSerialNo(self):
99
    return self._config_data["serial_no"]
100

    
101
  def GetClusterSerialNo(self):
102
    return self._config_data["cluster"]["serial_no"]
103

    
104

    
105
class SimpleStore(object):
106
  """Interface to static cluster data.
107

108
  This is different that the config.ConfigWriter and
109
  SimpleConfigReader classes in that it holds data that will always be
110
  present, even on nodes which don't have all the cluster data.
111

112
  Other particularities of the datastore:
113
    - keys are restricted to predefined values
114

115
  """
116
  _SS_FILEPREFIX = "ssconf_"
117
  _VALID_KEYS = (
118
    constants.SS_CLUSTER_NAME,
119
    constants.SS_CLUSTER_TAGS,
120
    constants.SS_FILE_STORAGE_DIR,
121
    constants.SS_MASTER_CANDIDATES,
122
    constants.SS_MASTER_CANDIDATES_IPS,
123
    constants.SS_MASTER_IP,
124
    constants.SS_MASTER_NETDEV,
125
    constants.SS_MASTER_NODE,
126
    constants.SS_NODE_LIST,
127
    constants.SS_NODE_PRIMARY_IPS,
128
    constants.SS_NODE_SECONDARY_IPS,
129
    constants.SS_OFFLINE_NODES,
130
    constants.SS_ONLINE_NODES,
131
    constants.SS_INSTANCE_LIST,
132
    constants.SS_RELEASE_VERSION,
133
    )
134
  _MAX_SIZE = 131072
135

    
136
  def __init__(self, cfg_location=None):
137
    if cfg_location is None:
138
      self._cfg_dir = constants.DATA_DIR
139
    else:
140
      self._cfg_dir = cfg_location
141

    
142
  def KeyToFilename(self, key):
143
    """Convert a given key into filename.
144

145
    """
146
    if key not in self._VALID_KEYS:
147
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
148
                                   % str(key))
149

    
150
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
151
    return filename
152

    
153
  def _ReadFile(self, key):
154
    """Generic routine to read keys.
155

156
    This will read the file which holds the value requested. Errors
157
    will be changed into ConfigurationErrors.
158

159
    """
160
    filename = self.KeyToFilename(key)
161
    try:
162
      data = utils.ReadFile(filename, size=self._MAX_SIZE)
163
    except EnvironmentError, err:
164
      raise errors.ConfigurationError("Can't read from the ssconf file:"
165
                                      " '%s'" % str(err))
166
    data = data.rstrip('\n')
167
    return data
168

    
169
  def WriteFiles(self, values):
170
    """Writes ssconf files used by external scripts.
171

172
    @type values: dict
173
    @param values: Dictionary of (name, value)
174

175
    """
176
    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
177

    
178
    # Get lock while writing files
179
    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
180
    try:
181
      for name, value in values.iteritems():
182
        if value and not value.endswith("\n"):
183
          value += "\n"
184
        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
185
    finally:
186
      ssconf_lock.Unlock()
187

    
188
  def GetFileList(self):
189
    """Return the list of all config files.
190

191
    This is used for computing node replication data.
192

193
    """
194
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
195

    
196
  def GetClusterName(self):
197
    """Get the cluster name.
198

199
    """
200
    return self._ReadFile(constants.SS_CLUSTER_NAME)
201

    
202
  def GetFileStorageDir(self):
203
    """Get the file storage dir.
204

205
    """
206
    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
207

    
208
  def GetMasterCandidates(self):
209
    """Return the list of master candidates.
210

211
    """
212
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
213
    nl = data.splitlines(False)
214
    return nl
215

    
216
  def GetMasterCandidatesIPList(self):
217
    """Return the list of master candidates' primary IP.
218

219
    """
220
    data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
221
    nl = data.splitlines(False)
222
    return nl
223

    
224
  def GetMasterIP(self):
225
    """Get the IP of the master node for this cluster.
226

227
    """
228
    return self._ReadFile(constants.SS_MASTER_IP)
229

    
230
  def GetMasterNetdev(self):
231
    """Get the netdev to which we'll add the master ip.
232

233
    """
234
    return self._ReadFile(constants.SS_MASTER_NETDEV)
235

    
236
  def GetMasterNode(self):
237
    """Get the hostname of the master node for this cluster.
238

239
    """
240
    return self._ReadFile(constants.SS_MASTER_NODE)
241

    
242
  def GetNodeList(self):
243
    """Return the list of cluster nodes.
244

245
    """
246
    data = self._ReadFile(constants.SS_NODE_LIST)
247
    nl = data.splitlines(False)
248
    return nl
249

    
250
  def GetNodePrimaryIPList(self):
251
    """Return the list of cluster nodes' primary IP.
252

253
    """
254
    data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
255
    nl = data.splitlines(False)
256
    return nl
257

    
258
  def GetNodeSecondaryIPList(self):
259
    """Return the list of cluster nodes' secondary IP.
260

261
    """
262
    data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
263
    nl = data.splitlines(False)
264
    return nl
265

    
266
  def GetClusterTags(self):
267
    """Return the cluster tags.
268

269
    """
270
    data = self._ReadFile(constants.SS_CLUSTER_TAGS)
271
    nl = data.splitlines(False)
272
    return nl
273

    
274

    
275
def GetMasterAndMyself(ss=None):
276
  """Get the master node and my own hostname.
277

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

281
  The function does not handle any errors, these should be handled in
282
  the caller (errors.ConfigurationError, errors.ResolverError).
283

284
  @param ss: either a sstore.SimpleConfigReader or a
285
      sstore.SimpleStore instance
286
  @rtype: tuple
287
  @return: a tuple (master node name, my own name)
288

289
  """
290
  if ss is None:
291
    ss = SimpleStore()
292
  return ss.GetMasterNode(), utils.HostInfo().name
293

    
294

    
295
def CheckMaster(debug, ss=None):
296
  """Checks the node setup.
297

298
  If this is the master, the function will return. Otherwise it will
299
  exit with an exit code based on the node status.
300

301
  """
302
  try:
303
    master_name, myself = GetMasterAndMyself(ss)
304
  except errors.ConfigurationError, err:
305
    print "Cluster configuration incomplete: '%s'" % str(err)
306
    sys.exit(constants.EXIT_NODESETUP_ERROR)
307
  except errors.ResolverError, err:
308
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
309
    sys.exit(constants.EXIT_NODESETUP_ERROR)
310

    
311
  if myself != master_name:
312
    if debug:
313
      sys.stderr.write("Not master, exiting.\n")
314
    sys.exit(constants.EXIT_NOTMASTER)
315

    
316

    
317
def CheckMasterCandidate(debug, ss=None):
318
  """Checks the node setup.
319

320
  If this is a master candidate, the function will return. Otherwise it will
321
  exit with an exit code based on the node status.
322

323
  """
324
  try:
325
    if ss is None:
326
      ss = SimpleStore()
327
    myself = utils.HostInfo().name
328
    candidates = ss.GetMasterCandidates()
329
  except errors.ConfigurationError, err:
330
    print "Cluster configuration incomplete: '%s'" % str(err)
331
    sys.exit(constants.EXIT_NODESETUP_ERROR)
332
  except errors.ResolverError, err:
333
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
334
    sys.exit(constants.EXIT_NODESETUP_ERROR)
335

    
336
  if myself not in candidates:
337
    if debug:
338
      sys.stderr.write("Not master candidate, exiting.\n")
339
    sys.exit(constants.EXIT_NOTCANDIDATE)
340