Statistics
| Branch: | Tag: | Revision:

root / lib / ssconf.py @ 4a8b186a

History | View | Annotate | Download (8.5 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 socket
30
import sys
31

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

    
37

    
38
class SimpleStore:
39
  """Interface to static cluster data.
40

41
  This is different that the config.ConfigWriter class in that it
42
  holds data that is (mostly) constant after the cluster
43
  initialization. Its purpose is to allow limited customization of
44
  things which would otherwise normally live in constants.py. Note
45
  that this data cannot live in ConfigWriter as that is available only
46
  on the master node, and our data must be readable by both the master
47
  and the nodes.
48

49
  Other particularities of the datastore:
50
    - keys are restricted to predefined values
51
    - values are small (<4k)
52
    - some keys are handled specially (read from the system)
53

54
  """
55
  _SS_FILEPREFIX = "ssconf_"
56
  SS_HYPERVISOR = "hypervisor"
57
  SS_NODED_PASS = "node_pass"
58
  SS_MASTER_NODE = "master_node"
59
  SS_MASTER_IP = "master_ip"
60
  SS_MASTER_NETDEV = "master_netdev"
61
  SS_CLUSTER_NAME = "cluster_name"
62
  SS_FILE_STORAGE_DIR = "file_storage_dir"
63
  SS_CONFIG_VERSION = "config_version"
64
  _VALID_KEYS = (SS_HYPERVISOR, SS_NODED_PASS, SS_MASTER_NODE, SS_MASTER_IP,
65
                 SS_MASTER_NETDEV, SS_CLUSTER_NAME, SS_FILE_STORAGE_DIR,
66
                 SS_CONFIG_VERSION)
67
  _MAX_SIZE = 4096
68

    
69
  def __init__(self, cfg_location=None):
70
    if cfg_location is None:
71
      self._cfg_dir = constants.DATA_DIR
72
    else:
73
      self._cfg_dir = cfg_location
74

    
75
  def KeyToFilename(self, key):
76
    """Convert a given key into filename.
77

78
    """
79
    if key not in self._VALID_KEYS:
80
      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
81
                                   % str(key))
82

    
83
    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
84
    return filename
85

    
86
  def _ReadFile(self, key):
87
    """Generic routine to read keys.
88

89
    This will read the file which holds the value requested. Errors
90
    will be changed into ConfigurationErrors.
91

92
    """
93
    filename = self.KeyToFilename(key)
94
    try:
95
      fh = file(filename, 'r')
96
      try:
97
        data = fh.readline(self._MAX_SIZE)
98
        data = data.rstrip('\n')
99
      finally:
100
        fh.close()
101
    except EnvironmentError, err:
102
      raise errors.ConfigurationError("Can't read from the ssconf file:"
103
                                      " '%s'" % str(err))
104
    return data
105

    
106
  def GetNodeDaemonPort(self):
107
    """Get the node daemon port for this cluster.
108

109
    Note that this routine does not read a ganeti-specific file, but
110
    instead uses socket.getservbyname to allow pre-customization of
111
    this parameter outside of ganeti.
112

113
    """
114
    try:
115
      port = socket.getservbyname("ganeti-noded", "tcp")
116
    except socket.error:
117
      port = constants.DEFAULT_NODED_PORT
118

    
119
    return port
120

    
121
  def GetHypervisorType(self):
122
    """Get the hypervisor type for this cluster.
123

124
    """
125
    return self._ReadFile(self.SS_HYPERVISOR)
126

    
127
  def GetNodeDaemonPassword(self):
128
    """Get the node password for this cluster.
129

130
    """
131
    return self._ReadFile(self.SS_NODED_PASS)
132

    
133
  def GetMasterNode(self):
134
    """Get the hostname of the master node for this cluster.
135

136
    """
137
    return self._ReadFile(self.SS_MASTER_NODE)
138

    
139
  def GetMasterIP(self):
140
    """Get the IP of the master node for this cluster.
141

142
    """
143
    return self._ReadFile(self.SS_MASTER_IP)
144

    
145
  def GetMasterNetdev(self):
146
    """Get the netdev to which we'll add the master ip.
147

148
    """
149
    return self._ReadFile(self.SS_MASTER_NETDEV)
150

    
151
  def GetClusterName(self):
152
    """Get the cluster name.
153

154
    """
155
    return self._ReadFile(self.SS_CLUSTER_NAME)
156

    
157
  def GetFileStorageDir(self):
158
    """Get the file storage dir.
159

160
    """
161
    return self._ReadFile(self.SS_FILE_STORAGE_DIR)
162

    
163
  def GetConfigVersion(self):
164
    """Get the configuration version.
165

166
    """
167
    value = self._ReadFile(self.SS_CONFIG_VERSION)
168
    try:
169
      return int(value)
170
    except (ValueError, TypeError), err:
171
      raise errors.ConfigurationError("Failed to convert config version %s to"
172
                                      " int: '%s'" % (value, str(err)))
173

    
174
  def GetFileList(self):
175
    """Return the list of all config files.
176

177
    This is used for computing node replication data.
178

179
    """
180
    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
181

    
182

    
183
class WritableSimpleStore(SimpleStore):
184
  """This is a read/write interface to SimpleStore, which is used rarely, when
185
  values need to be changed. Since WriteFile handles updates in an atomic way
186
  it should be fine to use two WritableSimpleStore at the same time, but in
187
  the future we might want to put additional protection for this class.
188

189
  A WritableSimpleStore cannot be used to update system-dependent values.
190

191
  """
192

    
193
  def SetKey(self, key, value):
194
    """Set the value of a key.
195

196
    This should be used only when adding a node to a cluster, or in other
197
    infrequent operations such as cluster-rename or master-failover.
198

199
    """
200
    file_name = self.KeyToFilename(key)
201
    utils.WriteFile(file_name, data="%s\n" % str(value),
202
                    uid=0, gid=0, mode=0400)
203

    
204

    
205
class SimpleConfigReader:
206
  """Simple class to read configuration file.
207

208
  """
209
  def __init__(self, file_name=constants.CLUSTER_CONF_FILE):
210
    """Initializes this class.
211

212
    @type file_name: string
213
    @param file_name: Configuration file path
214

215
    """
216
    self._file_name = file_name
217
    self._config_data = serializer.Load(utils.ReadFile(file_name))
218
    # TODO: Error handling
219

    
220
  def GetClusterName(self):
221
    return self._config_data["cluster"]["cluster_name"]
222

    
223
  def GetHostKey(self):
224
    return self._config_data["cluster"]["rsahostkeypub"]
225

    
226
  def GetMasterNode(self):
227
    return self._config_data["cluster"]["master_node"]
228

    
229
  def GetMasterIP(self):
230
    return self._config_data["cluster"]["master_ip"]
231

    
232
  def GetMasterNetdev(self):
233
    return self._config_data["cluster"]["master_netdev"]
234

    
235
  def GetFileStorageDir(self):
236
    return self._config_data["cluster"]["file_storage_dir"]
237

    
238
  def GetHypervisorType(self):
239
    return self._config_data["cluster"]["hypervisor"]
240

    
241
  def GetNodeList(self):
242
    return self._config_data["nodes"].keys()
243

    
244

    
245
class SimpleConfigWriter(SimpleConfigReader):
246
  """Simple class to write configuration file.
247

248
  """
249
  def SetMasterNode(self, node):
250
    """Change master node.
251

252
    """
253
    self._config_data["cluster"]["master_node"] = node
254

    
255
  def Save(self):
256
    """Writes configuration file.
257

258
    Warning: Doesn't take care of locking or synchronizing with other
259
    processes.
260

261
    """
262
    utils.WriteFile(self._file_name,
263
                    data=serializer.Dump(self._config_data),
264
                    mode=0600)
265

    
266

    
267
def GetMasterAndMyself(ss=None):
268
  """Get the master node and my own hostname.
269

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

273
  The function does not handle any errors, these should be handled in
274
  the caller (errors.ConfigurationError, errors.ResolverError).
275

276
  """
277
  if ss is None:
278
    ss = SimpleStore()
279
  return ss.GetMasterNode(), utils.HostInfo().name
280

    
281
def CheckMaster(debug, ss=None):
282
  """Checks the node setup.
283

284
  If this is the master, the function will return. Otherwise it will
285
  exit with an exit code based on the node status.
286

287
  """
288
  try:
289
    master_name, myself = GetMasterAndMyself(ss)
290
  except errors.ConfigurationError, err:
291
    print "Cluster configuration incomplete: '%s'" % str(err)
292
    sys.exit(constants.EXIT_NODESETUP_ERROR)
293
  except errors.ResolverError, err:
294
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
295
    sys.exit(constants.EXIT_NODESETUP_ERROR)
296

    
297
  if myself != master_name:
298
    if debug:
299
      sys.stderr.write("Not master, exiting.\n")
300
    sys.exit(constants.EXIT_NOTMASTER)