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