Don't pass sstore to LUs anymore
[ganeti-local] / lib / ssconf.py
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)