First write operation (add tag) for Ganeti RAPI
[ganeti-local] / lib / ssconf.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 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
36
37 class SimpleStore:
38   """Interface to static cluster data.
39
40   This is different that the config.ConfigWriter class in that it
41   holds data that is (mostly) constant after the cluster
42   initialization. Its purpose is to allow limited customization of
43   things which would otherwise normally live in constants.py. Note
44   that this data cannot live in ConfigWriter as that is available only
45   on the master node, and our data must be readable by both the master
46   and the nodes.
47
48   Other particularities of the datastore:
49     - keys are restricted to predefined values
50     - values are small (<4k)
51     - some keys are handled specially (read from the system)
52
53   """
54   _SS_FILEPREFIX = "ssconf_"
55   SS_HYPERVISOR = "hypervisor"
56   SS_NODED_PASS = "node_pass"
57   SS_MASTER_NODE = "master_node"
58   SS_MASTER_IP = "master_ip"
59   SS_MASTER_NETDEV = "master_netdev"
60   SS_CLUSTER_NAME = "cluster_name"
61   SS_FILE_STORAGE_DIR = "file_storage_dir"
62   SS_CONFIG_VERSION = "config_version"
63   _VALID_KEYS = (SS_HYPERVISOR, SS_NODED_PASS, SS_MASTER_NODE, SS_MASTER_IP,
64                  SS_MASTER_NETDEV, SS_CLUSTER_NAME, SS_FILE_STORAGE_DIR,
65                  SS_CONFIG_VERSION)
66   _MAX_SIZE = 4096
67
68   def __init__(self, cfg_location=None):
69     if cfg_location is None:
70       self._cfg_dir = constants.DATA_DIR
71     else:
72       self._cfg_dir = cfg_location
73
74   def KeyToFilename(self, key):
75     """Convert a given key into filename.
76
77     """
78     if key not in self._VALID_KEYS:
79       raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
80                                    % str(key))
81
82     filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
83     return filename
84
85   def _ReadFile(self, key):
86     """Generic routine to read keys.
87
88     This will read the file which holds the value requested. Errors
89     will be changed into ConfigurationErrors.
90
91     """
92     filename = self.KeyToFilename(key)
93     try:
94       fh = file(filename, 'r')
95       try:
96         data = fh.readline(self._MAX_SIZE)
97         data = data.rstrip('\n')
98       finally:
99         fh.close()
100     except EnvironmentError, err:
101       raise errors.ConfigurationError("Can't read from the ssconf file:"
102                                       " '%s'" % str(err))
103     return data
104
105   def GetNodeDaemonPort(self):
106     """Get the node daemon port for this cluster.
107
108     Note that this routine does not read a ganeti-specific file, but
109     instead uses socket.getservbyname to allow pre-customization of
110     this parameter outside of ganeti.
111
112     """
113     try:
114       port = socket.getservbyname("ganeti-noded", "tcp")
115     except socket.error:
116       port = constants.DEFAULT_NODED_PORT
117
118     return port
119
120   def GetHypervisorType(self):
121     """Get the hypervisor type for this cluster.
122
123     """
124     return self._ReadFile(self.SS_HYPERVISOR)
125
126   def GetNodeDaemonPassword(self):
127     """Get the node password for this cluster.
128
129     """
130     return self._ReadFile(self.SS_NODED_PASS)
131
132   def GetMasterNode(self):
133     """Get the hostname of the master node for this cluster.
134
135     """
136     return self._ReadFile(self.SS_MASTER_NODE)
137
138   def GetMasterIP(self):
139     """Get the IP of the master node for this cluster.
140
141     """
142     return self._ReadFile(self.SS_MASTER_IP)
143
144   def GetMasterNetdev(self):
145     """Get the netdev to which we'll add the master ip.
146
147     """
148     return self._ReadFile(self.SS_MASTER_NETDEV)
149
150   def GetClusterName(self):
151     """Get the cluster name.
152
153     """
154     return self._ReadFile(self.SS_CLUSTER_NAME)
155
156   def GetFileStorageDir(self):
157     """Get the file storage dir.
158
159     """
160     return self._ReadFile(self.SS_FILE_STORAGE_DIR)
161
162   def GetConfigVersion(self):
163     """Get the configuration version.
164
165     """
166     value = self._ReadFile(self.SS_CONFIG_VERSION)
167     try:
168       return int(value)
169     except (ValueError, TypeError), err:
170       raise errors.ConfigurationError("Failed to convert config version %s to"
171                                       " int: '%s'" % (value, str(err)))
172
173   def GetFileList(self):
174     """Return the list of all config files.
175
176     This is used for computing node replication data.
177
178     """
179     return [self.KeyToFilename(key) for key in self._VALID_KEYS]
180
181
182 class WritableSimpleStore(SimpleStore):
183   """This is a read/write interface to SimpleStore, which is used rarely, when
184   values need to be changed. Since WriteFile handles updates in an atomic way
185   it should be fine to use two WritableSimpleStore at the same time, but in
186   the future we might want to put additional protection for this class.
187
188   A WritableSimpleStore cannot be used to update system-dependent values.
189
190   """
191
192   def SetKey(self, key, value):
193     """Set the value of a key.
194
195     This should be used only when adding a node to a cluster, or in other
196     infrequent operations such as cluster-rename or master-failover.
197
198     """
199     file_name = self.KeyToFilename(key)
200     utils.WriteFile(file_name, data="%s\n" % str(value),
201                     uid=0, gid=0, mode=0400)
202
203
204 def GetMasterAndMyself(ss=None):
205   """Get the master node and my own hostname.
206
207   This can be either used for a 'soft' check (compared to CheckMaster,
208   which exits) or just for computing both at the same time.
209
210   The function does not handle any errors, these should be handled in
211   the caller (errors.ConfigurationError, errors.ResolverError).
212
213   """
214   if ss is None:
215     ss = SimpleStore()
216   return ss.GetMasterNode(), utils.HostInfo().name
217
218 def CheckMaster(debug, ss=None):
219   """Checks the node setup.
220
221   If this is the master, the function will return. Otherwise it will
222   exit with an exit code based on the node status.
223
224   """
225   try:
226     master_name, myself = GetMasterAndMyself(ss)
227   except errors.ConfigurationError, err:
228     print "Cluster configuration incomplete: '%s'" % str(err)
229     sys.exit(constants.EXIT_NODESETUP_ERROR)
230   except errors.ResolverError, err:
231     sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
232     sys.exit(constants.EXIT_NODESETUP_ERROR)
233
234   if myself != master_name:
235     if debug:
236       sys.stderr.write("Not master, exiting.\n")
237     sys.exit(constants.EXIT_NOTMASTER)