Minor fix in query.py
[ganeti-local] / lib / ssconf.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012 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 import os
32 import errno
33
34 from ganeti import errors
35 from ganeti import constants
36 from ganeti import utils
37 from ganeti import serializer
38 from ganeti import objects
39 from ganeti import netutils
40 from ganeti import pathutils
41
42
43 SSCONF_LOCK_TIMEOUT = 10
44
45 RE_VALID_SSCONF_NAME = re.compile(r"^[-_a-z0-9]+$")
46
47
48 class SimpleConfigReader(object):
49   """Simple class to read configuration file.
50
51   """
52   def __init__(self, file_name=pathutils.CLUSTER_CONF_FILE):
53     """Initializes this class.
54
55     @type file_name: string
56     @param file_name: Configuration file path
57
58     """
59     self._file_name = file_name
60     self._last_inode = None
61     self._last_mtime = None
62     self._last_size = None
63
64     self._config_data = None
65     self._inst_ips_by_link = None
66     self._ip_to_inst_by_link = None
67     self._instances_ips = None
68     self._mc_primary_ips = None
69     self._nodes_primary_ips = None
70
71     # we need a forced reload at class init time, to initialize _last_*
72     self._Load(force=True)
73
74   def _Load(self, force=False):
75     """Loads (or reloads) the config file.
76
77     @type force: boolean
78     @param force: whether to force the reload without checking the mtime
79     @rtype: boolean
80     @return: boolean value that says whether we reloaded the configuration or
81              not (because we decided it was already up-to-date)
82
83     """
84     try:
85       cfg_stat = os.stat(self._file_name)
86     except EnvironmentError, err:
87       raise errors.ConfigurationError("Cannot stat config file %s: %s" %
88                                       (self._file_name, err))
89     inode = cfg_stat.st_ino
90     mtime = cfg_stat.st_mtime
91     size = cfg_stat.st_size
92
93     if (force or inode != self._last_inode or
94         mtime > self._last_mtime or
95         size != self._last_size):
96       self._last_inode = inode
97       self._last_mtime = mtime
98       self._last_size = size
99     else:
100       # Don't reload
101       return False
102
103     try:
104       self._config_data = serializer.Load(utils.ReadFile(self._file_name))
105     except EnvironmentError, err:
106       raise errors.ConfigurationError("Cannot read config file %s: %s" %
107                                       (self._file_name, err))
108     except ValueError, err:
109       raise errors.ConfigurationError("Cannot load config file %s: %s" %
110                                       (self._file_name, err))
111
112     self._ip_to_inst_by_link = {}
113     self._instances_ips = []
114     self._inst_ips_by_link = {}
115     c_nparams = self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
116     for iname in self._config_data["instances"]:
117       instance = self._config_data["instances"][iname]
118       for nic in instance["nics"]:
119         if "ip" in nic and nic["ip"]:
120           params = objects.FillDict(c_nparams, nic["nicparams"])
121           if not params["link"] in self._inst_ips_by_link:
122             self._inst_ips_by_link[params["link"]] = []
123             self._ip_to_inst_by_link[params["link"]] = {}
124           self._ip_to_inst_by_link[params["link"]][nic["ip"]] = iname
125           self._inst_ips_by_link[params["link"]].append(nic["ip"])
126
127     self._nodes_primary_ips = []
128     self._mc_primary_ips = []
129     for node_name in self._config_data["nodes"]:
130       node = self._config_data["nodes"][node_name]
131       self._nodes_primary_ips.append(node["primary_ip"])
132       if node["master_candidate"]:
133         self._mc_primary_ips.append(node["primary_ip"])
134
135     return True
136
137   # Clients can request a reload of the config file, so we export our internal
138   # _Load function as Reload.
139   Reload = _Load
140
141   def GetClusterName(self):
142     return self._config_data["cluster"]["cluster_name"]
143
144   def GetHostKey(self):
145     return self._config_data["cluster"]["rsahostkeypub"]
146
147   def GetMasterNode(self):
148     return self._config_data["cluster"]["master_node"]
149
150   def GetMasterIP(self):
151     return self._config_data["cluster"]["master_ip"]
152
153   def GetMasterNetdev(self):
154     return self._config_data["cluster"]["master_netdev"]
155
156   def GetMasterNetmask(self):
157     return self._config_data["cluster"]["master_netmask"]
158
159   def GetFileStorageDir(self):
160     return self._config_data["cluster"]["file_storage_dir"]
161
162   def GetSharedFileStorageDir(self):
163     return self._config_data["cluster"]["shared_file_storage_dir"]
164
165   def GetNodeList(self):
166     return self._config_data["nodes"].keys()
167
168   def GetConfigSerialNo(self):
169     return self._config_data["serial_no"]
170
171   def GetClusterSerialNo(self):
172     return self._config_data["cluster"]["serial_no"]
173
174   def GetDefaultNicParams(self):
175     return self._config_data["cluster"]["nicparams"][constants.PP_DEFAULT]
176
177   def GetDefaultNicLink(self):
178     return self.GetDefaultNicParams()[constants.NIC_LINK]
179
180   def GetNodeStatusFlags(self, node):
181     """Get a node's status flags
182
183     @type node: string
184     @param node: node name
185     @rtype: (bool, bool, bool)
186     @return: (master_candidate, drained, offline) (or None if no such node)
187
188     """
189     if node not in self._config_data["nodes"]:
190       return None
191
192     master_candidate = self._config_data["nodes"][node]["master_candidate"]
193     drained = self._config_data["nodes"][node]["drained"]
194     offline = self._config_data["nodes"][node]["offline"]
195     return master_candidate, drained, offline
196
197   def GetInstanceByLinkIp(self, ip, link):
198     """Get instance name from its link and ip address.
199
200     @type ip: string
201     @param ip: ip address
202     @type link: string
203     @param link: nic link
204     @rtype: string
205     @return: instance name
206
207     """
208     if not link:
209       link = self.GetDefaultNicLink()
210     if not link in self._ip_to_inst_by_link:
211       return None
212     if not ip in self._ip_to_inst_by_link[link]:
213       return None
214     return self._ip_to_inst_by_link[link][ip]
215
216   def GetNodePrimaryIp(self, node):
217     """Get a node's primary ip
218
219     @type node: string
220     @param node: node name
221     @rtype: string, or None
222     @return: node's primary ip, or None if no such node
223
224     """
225     if node not in self._config_data["nodes"]:
226       return None
227     return self._config_data["nodes"][node]["primary_ip"]
228
229   def GetInstancePrimaryNode(self, instance):
230     """Get an instance's primary node
231
232     @type instance: string
233     @param instance: instance name
234     @rtype: string, or None
235     @return: primary node, or None if no such instance
236
237     """
238     if instance not in self._config_data["instances"]:
239       return None
240     return self._config_data["instances"][instance]["primary_node"]
241
242   def GetNodesPrimaryIps(self):
243     return self._nodes_primary_ips
244
245   def GetMasterCandidatesPrimaryIps(self):
246     return self._mc_primary_ips
247
248   def GetInstancesIps(self, link):
249     """Get list of nic ips connected to a certain link.
250
251     @type link: string
252     @param link: nic link
253     @rtype: list
254     @return: list of ips connected to that link
255
256     """
257     if not link:
258       link = self.GetDefaultNicLink()
259
260     if link in self._inst_ips_by_link:
261       return self._inst_ips_by_link[link]
262     else:
263       return []
264
265
266 class SimpleStore(object):
267   """Interface to static cluster data.
268
269   This is different that the config.ConfigWriter and
270   SimpleConfigReader classes in that it holds data that will always be
271   present, even on nodes which don't have all the cluster data.
272
273   Other particularities of the datastore:
274     - keys are restricted to predefined values
275
276   """
277   _VALID_KEYS = (
278     constants.SS_CLUSTER_NAME,
279     constants.SS_CLUSTER_TAGS,
280     constants.SS_FILE_STORAGE_DIR,
281     constants.SS_SHARED_FILE_STORAGE_DIR,
282     constants.SS_MASTER_CANDIDATES,
283     constants.SS_MASTER_CANDIDATES_IPS,
284     constants.SS_MASTER_IP,
285     constants.SS_MASTER_NETDEV,
286     constants.SS_MASTER_NETMASK,
287     constants.SS_MASTER_NODE,
288     constants.SS_NODE_LIST,
289     constants.SS_NODE_PRIMARY_IPS,
290     constants.SS_NODE_SECONDARY_IPS,
291     constants.SS_OFFLINE_NODES,
292     constants.SS_ONLINE_NODES,
293     constants.SS_PRIMARY_IP_FAMILY,
294     constants.SS_INSTANCE_LIST,
295     constants.SS_RELEASE_VERSION,
296     constants.SS_HYPERVISOR_LIST,
297     constants.SS_MAINTAIN_NODE_HEALTH,
298     constants.SS_UID_POOL,
299     constants.SS_NODEGROUPS,
300     constants.SS_NETWORKS,
301     )
302   _MAX_SIZE = 131072
303
304   def __init__(self, cfg_location=None):
305     if cfg_location is None:
306       self._cfg_dir = pathutils.DATA_DIR
307     else:
308       self._cfg_dir = cfg_location
309
310   def KeyToFilename(self, key):
311     """Convert a given key into filename.
312
313     """
314     if key not in self._VALID_KEYS:
315       raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
316                                    % str(key))
317
318     filename = self._cfg_dir + "/" + constants.SSCONF_FILEPREFIX + key
319     return filename
320
321   def _ReadFile(self, key, default=None):
322     """Generic routine to read keys.
323
324     This will read the file which holds the value requested. Errors
325     will be changed into ConfigurationErrors.
326
327     """
328     filename = self.KeyToFilename(key)
329     try:
330       data = utils.ReadFile(filename, size=self._MAX_SIZE)
331     except EnvironmentError, err:
332       if err.errno == errno.ENOENT and default is not None:
333         return default
334       raise errors.ConfigurationError("Can't read from the ssconf file:"
335                                       " '%s'" % str(err))
336     data = data.rstrip("\n")
337     return data
338
339   def WriteFiles(self, values):
340     """Writes ssconf files used by external scripts.
341
342     @type values: dict
343     @param values: Dictionary of (name, value)
344
345     """
346     ssconf_lock = utils.FileLock.Open(pathutils.SSCONF_LOCK_FILE)
347
348     # Get lock while writing files
349     ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
350     try:
351       for name, value in values.iteritems():
352         if value and not value.endswith("\n"):
353           value += "\n"
354         if len(value) > self._MAX_SIZE:
355           raise errors.ConfigurationError("ssconf file %s above maximum size" %
356                                           name)
357         utils.WriteFile(self.KeyToFilename(name), data=value,
358                         mode=constants.SS_FILE_PERMS)
359     finally:
360       ssconf_lock.Unlock()
361
362   def GetFileList(self):
363     """Return the list of all config files.
364
365     This is used for computing node replication data.
366
367     """
368     return [self.KeyToFilename(key) for key in self._VALID_KEYS]
369
370   def GetClusterName(self):
371     """Get the cluster name.
372
373     """
374     return self._ReadFile(constants.SS_CLUSTER_NAME)
375
376   def GetFileStorageDir(self):
377     """Get the file storage dir.
378
379     """
380     return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
381
382   def GetSharedFileStorageDir(self):
383     """Get the shared file storage dir.
384
385     """
386     return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
387
388   def GetMasterCandidates(self):
389     """Return the list of master candidates.
390
391     """
392     data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
393     nl = data.splitlines(False)
394     return nl
395
396   def GetMasterCandidatesIPList(self):
397     """Return the list of master candidates' primary IP.
398
399     """
400     data = self._ReadFile(constants.SS_MASTER_CANDIDATES_IPS)
401     nl = data.splitlines(False)
402     return nl
403
404   def GetMasterIP(self):
405     """Get the IP of the master node for this cluster.
406
407     """
408     return self._ReadFile(constants.SS_MASTER_IP)
409
410   def GetMasterNetdev(self):
411     """Get the netdev to which we'll add the master ip.
412
413     """
414     return self._ReadFile(constants.SS_MASTER_NETDEV)
415
416   def GetMasterNetmask(self):
417     """Get the master netmask.
418
419     """
420     try:
421       return self._ReadFile(constants.SS_MASTER_NETMASK)
422     except errors.ConfigurationError:
423       family = self.GetPrimaryIPFamily()
424       ipcls = netutils.IPAddress.GetClassFromIpFamily(family)
425       return ipcls.iplen
426
427   def GetMasterNode(self):
428     """Get the hostname of the master node for this cluster.
429
430     """
431     return self._ReadFile(constants.SS_MASTER_NODE)
432
433   def GetNodeList(self):
434     """Return the list of cluster nodes.
435
436     """
437     data = self._ReadFile(constants.SS_NODE_LIST)
438     nl = data.splitlines(False)
439     return nl
440
441   def GetNodePrimaryIPList(self):
442     """Return the list of cluster nodes' primary IP.
443
444     """
445     data = self._ReadFile(constants.SS_NODE_PRIMARY_IPS)
446     nl = data.splitlines(False)
447     return nl
448
449   def GetNodeSecondaryIPList(self):
450     """Return the list of cluster nodes' secondary IP.
451
452     """
453     data = self._ReadFile(constants.SS_NODE_SECONDARY_IPS)
454     nl = data.splitlines(False)
455     return nl
456
457   def GetNodegroupList(self):
458     """Return the list of nodegroups.
459
460     """
461     data = self._ReadFile(constants.SS_NODEGROUPS)
462     nl = data.splitlines(False)
463     return nl
464
465   def GetNetworkList(self):
466     """Return the list of networks.
467
468     """
469     data = self._ReadFile(constants.SS_NETWORKS)
470     nl = data.splitlines(False)
471     return nl
472
473   def GetClusterTags(self):
474     """Return the cluster tags.
475
476     """
477     data = self._ReadFile(constants.SS_CLUSTER_TAGS)
478     nl = data.splitlines(False)
479     return nl
480
481   def GetHypervisorList(self):
482     """Return the list of enabled hypervisors.
483
484     """
485     data = self._ReadFile(constants.SS_HYPERVISOR_LIST)
486     nl = data.splitlines(False)
487     return nl
488
489   def GetMaintainNodeHealth(self):
490     """Return the value of the maintain_node_health option.
491
492     """
493     data = self._ReadFile(constants.SS_MAINTAIN_NODE_HEALTH)
494     # we rely on the bool serialization here
495     return data == "True"
496
497   def GetUidPool(self):
498     """Return the user-id pool definition string.
499
500     The separator character is a newline.
501
502     The return value can be parsed using uidpool.ParseUidPool()::
503
504       ss = ssconf.SimpleStore()
505       uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n")
506
507     """
508     data = self._ReadFile(constants.SS_UID_POOL)
509     return data
510
511   def GetPrimaryIPFamily(self):
512     """Return the cluster-wide primary address family.
513
514     """
515     try:
516       return int(self._ReadFile(constants.SS_PRIMARY_IP_FAMILY,
517                                 default=netutils.IP4Address.family))
518     except (ValueError, TypeError), err:
519       raise errors.ConfigurationError("Error while trying to parse primary ip"
520                                       " family: %s" % err)
521
522
523 def WriteSsconfFiles(values):
524   """Update all ssconf files.
525
526   Wrapper around L{SimpleStore.WriteFiles}.
527
528   """
529   SimpleStore().WriteFiles(values)
530
531
532 def GetMasterAndMyself(ss=None):
533   """Get the master node and my own hostname.
534
535   This can be either used for a 'soft' check (compared to CheckMaster,
536   which exits) or just for computing both at the same time.
537
538   The function does not handle any errors, these should be handled in
539   the caller (errors.ConfigurationError, errors.ResolverError).
540
541   @param ss: either a sstore.SimpleConfigReader or a
542       sstore.SimpleStore instance
543   @rtype: tuple
544   @return: a tuple (master node name, my own name)
545
546   """
547   if ss is None:
548     ss = SimpleStore()
549   return ss.GetMasterNode(), netutils.Hostname.GetSysName()
550
551
552 def CheckMaster(debug, ss=None):
553   """Checks the node setup.
554
555   If this is the master, the function will return. Otherwise it will
556   exit with an exit code based on the node status.
557
558   """
559   try:
560     master_name, myself = GetMasterAndMyself(ss)
561   except errors.ConfigurationError, err:
562     print "Cluster configuration incomplete: '%s'" % str(err)
563     sys.exit(constants.EXIT_NODESETUP_ERROR)
564   except errors.ResolverError, err:
565     sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
566     sys.exit(constants.EXIT_NODESETUP_ERROR)
567
568   if myself != master_name:
569     if debug:
570       sys.stderr.write("Not master, exiting.\n")
571     sys.exit(constants.EXIT_NOTMASTER)