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