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