Fix checks in LUSetNodeParms for the master node
[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 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._config_data = serializer.Load(utils.ReadFile(file_name))
56     # TODO: Error handling
57
58   def GetClusterName(self):
59     return self._config_data["cluster"]["cluster_name"]
60
61   def GetHostKey(self):
62     return self._config_data["cluster"]["rsahostkeypub"]
63
64   def GetMasterNode(self):
65     return self._config_data["cluster"]["master_node"]
66
67   def GetMasterIP(self):
68     return self._config_data["cluster"]["master_ip"]
69
70   def GetMasterNetdev(self):
71     return self._config_data["cluster"]["master_netdev"]
72
73   def GetFileStorageDir(self):
74     return self._config_data["cluster"]["file_storage_dir"]
75
76   def GetHypervisorType(self):
77     return self._config_data["cluster"]["hypervisor"]
78
79   def GetNodeList(self):
80     return self._config_data["nodes"].keys()
81
82   @classmethod
83   def FromDict(cls, val, cfg_file=constants.CLUSTER_CONF_FILE):
84     """Alternative construction from a dictionary.
85
86     """
87     obj = SimpleConfigReader.__new__(cls)
88     obj._config_data = val
89     obj._file_name = cfg_file
90     return obj
91
92
93 class SimpleConfigWriter(SimpleConfigReader):
94   """Simple class to write configuration file.
95
96   """
97   def Save(self):
98     """Writes configuration file.
99
100     Warning: Doesn't take care of locking or synchronizing with other
101     processes.
102
103     """
104     utils.WriteFile(self._file_name,
105                     data=serializer.Dump(self._config_data),
106                     mode=0600)
107
108
109 class SimpleStore(object):
110   """Interface to static cluster data.
111
112   This is different that the config.ConfigWriter and
113   SimpleConfigReader classes in that it holds data that will always be
114   present, even on nodes which don't have all the cluster data.
115
116   Other particularities of the datastore:
117     - keys are restricted to predefined values
118
119   """
120   _SS_FILEPREFIX = "ssconf_"
121   _VALID_KEYS = (
122     constants.SS_CLUSTER_NAME,
123     constants.SS_CLUSTER_TAGS,
124     constants.SS_FILE_STORAGE_DIR,
125     constants.SS_MASTER_CANDIDATES,
126     constants.SS_MASTER_IP,
127     constants.SS_MASTER_NETDEV,
128     constants.SS_MASTER_NODE,
129     constants.SS_NODE_LIST,
130     constants.SS_OFFLINE_NODES,
131     constants.SS_ONLINE_NODES,
132     constants.SS_INSTANCE_LIST,
133     constants.SS_RELEASE_VERSION,
134     )
135   _MAX_SIZE = 131072
136
137   def __init__(self, cfg_location=None):
138     if cfg_location is None:
139       self._cfg_dir = constants.DATA_DIR
140     else:
141       self._cfg_dir = cfg_location
142
143   def KeyToFilename(self, key):
144     """Convert a given key into filename.
145
146     """
147     if key not in self._VALID_KEYS:
148       raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
149                                    % str(key))
150
151     filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
152     return filename
153
154   def _ReadFile(self, key):
155     """Generic routine to read keys.
156
157     This will read the file which holds the value requested. Errors
158     will be changed into ConfigurationErrors.
159
160     """
161     filename = self.KeyToFilename(key)
162     try:
163       fh = file(filename, 'r')
164       try:
165         data = fh.read(self._MAX_SIZE)
166         data = data.rstrip('\n')
167       finally:
168         fh.close()
169     except EnvironmentError, err:
170       raise errors.ConfigurationError("Can't read from the ssconf file:"
171                                       " '%s'" % str(err))
172     return data
173
174   def WriteFiles(self, values):
175     """Writes ssconf files used by external scripts.
176
177     @type values: dict
178     @param values: Dictionary of (name, value)
179
180     """
181     ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
182
183     # Get lock while writing files
184     ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
185     try:
186       for name, value in values.iteritems():
187         if value and not value.endswith("\n"):
188           value += "\n"
189         utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
190     finally:
191       ssconf_lock.Unlock()
192
193   def GetFileList(self):
194     """Return the list of all config files.
195
196     This is used for computing node replication data.
197
198     """
199     return [self.KeyToFilename(key) for key in self._VALID_KEYS]
200
201   def GetClusterName(self):
202     """Get the cluster name.
203
204     """
205     return self._ReadFile(constants.SS_CLUSTER_NAME)
206
207   def GetFileStorageDir(self):
208     """Get the file storage dir.
209
210     """
211     return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
212
213   def GetMasterCandidates(self):
214     """Return the list of master candidates.
215
216     """
217     data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
218     nl = data.splitlines(False)
219     return nl
220
221   def GetMasterIP(self):
222     """Get the IP of the master node for this cluster.
223
224     """
225     return self._ReadFile(constants.SS_MASTER_IP)
226
227   def GetMasterNetdev(self):
228     """Get the netdev to which we'll add the master ip.
229
230     """
231     return self._ReadFile(constants.SS_MASTER_NETDEV)
232
233   def GetMasterNode(self):
234     """Get the hostname of the master node for this cluster.
235
236     """
237     return self._ReadFile(constants.SS_MASTER_NODE)
238
239   def GetNodeList(self):
240     """Return the list of cluster nodes.
241
242     """
243     data = self._ReadFile(constants.SS_NODE_LIST)
244     nl = data.splitlines(False)
245     return nl
246
247   def GetClusterTags(self):
248     """Return the cluster tags.
249
250     """
251     data = self._ReadFile(constants.SS_CLUSTER_TAGS)
252     nl = data.splitlines(False)
253     return nl
254
255
256 def GetMasterAndMyself(ss=None):
257   """Get the master node and my own hostname.
258
259   This can be either used for a 'soft' check (compared to CheckMaster,
260   which exits) or just for computing both at the same time.
261
262   The function does not handle any errors, these should be handled in
263   the caller (errors.ConfigurationError, errors.ResolverError).
264
265   @param ss: either a sstore.SimpleConfigReader or a
266       sstore.SimpleStore instance
267   @rtype: tuple
268   @return: a tuple (master node name, my own name)
269
270   """
271   if ss is None:
272     ss = SimpleStore()
273   return ss.GetMasterNode(), utils.HostInfo().name
274
275
276 def CheckMaster(debug, ss=None):
277   """Checks the node setup.
278
279   If this is the master, the function will return. Otherwise it will
280   exit with an exit code based on the node status.
281
282   """
283   try:
284     master_name, myself = GetMasterAndMyself(ss)
285   except errors.ConfigurationError, err:
286     print "Cluster configuration incomplete: '%s'" % str(err)
287     sys.exit(constants.EXIT_NODESETUP_ERROR)
288   except errors.ResolverError, err:
289     sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
290     sys.exit(constants.EXIT_NODESETUP_ERROR)
291
292   if myself != master_name:
293     if debug:
294       sys.stderr.write("Not master, exiting.\n")
295     sys.exit(constants.EXIT_NOTMASTER)