X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/62779dd082bc92cb84376d67ab9cab183b710195..aff173bb7a40a59c820f53dac6e64f450a9e4dc5:/lib/config.py diff --git a/lib/config.py b/lib/config.py index e4d948f..d8c2605 100644 --- a/lib/config.py +++ b/lib/config.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +# # # Copyright (C) 2006, 2007 Google Inc. @@ -21,21 +21,17 @@ """Configuration management for Ganeti -This module provides the interface to the ganeti cluster configuration. - +This module provides the interface to the Ganeti cluster configuration. -The configuration data is stored on every node but is updated on the -master only. After each update, the master distributes the data to the -other nodes. +The configuration data is stored on every node but is updated on the master +only. After each update, the master distributes the data to the other nodes. -Currently the data storage format is pickle as yaml was initially not -available, then we used it but it was a memory-eating slow beast, so -we reverted to pickle using custom Unpicklers. +Currently, the data storage format is JSON. YAML was slow and consuming too +much memory. """ import os -import socket import tempfile import random @@ -46,28 +42,13 @@ from ganeti import constants from ganeti import rpc from ganeti import objects -def _my_uuidgen(): - """Poor-man's uuidgen using the uuidgen binary. - - """ - result = utils.RunCmd(["uuidgen", "-r"]) - if result.failed: - return None - return result.stdout.rstrip('\n') - - -try: - import uuid - _uuidgen = uuid.uuid4 -except ImportError: - _uuidgen = _my_uuidgen - class ConfigWriter: """The interface to the cluster configuration. """ def __init__(self, cfg_file=None, offline=False): + self.write_count = 0 self._config_data = None self._config_time = None self._config_size = None @@ -78,6 +59,11 @@ class ConfigWriter: else: self._cfg_file = cfg_file self._temporary_ids = set() + # Note: in order to prevent errors when resolving our name in + # _DistributeConfig, we compute it here once and reuse it; it's + # better to raise an error before starting to modify the config + # file than after it was modified + self._my_hostname = utils.HostInfo().name # this method needs to be static, so that we can call it on the class @staticmethod @@ -107,9 +93,21 @@ class ConfigWriter: break retries -= 1 else: - raise errors.ConfigurationError, ("Can't generate unique MAC") + raise errors.ConfigurationError("Can't generate unique MAC") return mac + def IsMacInUse(self, mac): + """Predicate: check if the specified MAC is in use in the Ganeti cluster. + + This only checks instances managed by this cluster, it does not + check for potential collisions elsewhere. + + """ + self._OpenConfig() + self._ReleaseLock() + all_macs = self._AllMACs() + return mac in all_macs + def _ComputeAllLVs(self): """Compute the list of all LVs. @@ -147,12 +145,12 @@ class ConfigWriter: existing.update(exceptions) retries = 64 while retries > 0: - unique_id = _uuidgen() + unique_id = utils.NewUUID() if unique_id not in existing and unique_id is not None: break else: - raise errors.ConfigurationError, ("Not able generate an unique ID" - " (last tried ID: %s" % unique_id) + raise errors.ConfigurationError("Not able generate an unique ID" + " (last tried ID: %s" % unique_id) self._temporary_ids.add(unique_id) return unique_id @@ -182,15 +180,15 @@ class ConfigWriter: for instance_name in data.instances: instance = data.instances[instance_name] if instance.primary_node not in data.nodes: - result.append("Instance '%s' has invalid primary node '%s'" % + result.append("instance '%s' has invalid primary node '%s'" % (instance_name, instance.primary_node)) for snode in instance.secondary_nodes: if snode not in data.nodes: - result.append("Instance '%s' has invalid secondary node '%s'" % + result.append("instance '%s' has invalid secondary node '%s'" % (instance_name, snode)) for idx, nic in enumerate(instance.nics): if nic.mac in seen_macs: - result.append("Instance '%s' has NIC %d mac %s duplicate" % + result.append("instance '%s' has NIC %d mac %s duplicate" % (instance_name, idx, nic.mac)) else: seen_macs.append(nic.mac) @@ -212,11 +210,11 @@ class ConfigWriter: if disk.logical_id is None and disk.physical_id is not None: return - if disk.dev_type == "drbd": + if disk.dev_type in constants.LDS_DRBD: pnode, snode, port = disk.logical_id if node_name not in (pnode, snode): - raise errors.ConfigurationError, ("DRBD device not knowing node %s" % - node_name) + raise errors.ConfigurationError("DRBD device not knowing node %s" % + node_name) pnode_info = self.GetNodeInfo(pnode) snode_info = self.GetNodeInfo(snode) if pnode_info is None or snode_info is None: @@ -237,7 +235,7 @@ class ConfigWriter: """ if not isinstance(port, int): - raise errors.ProgrammerError, ("Invalid type passed for port") + raise errors.ProgrammerError("Invalid type passed for port") self._OpenConfig() self._config_data.cluster.tcpudp_port_pool.add(port) @@ -267,9 +265,9 @@ class ConfigWriter: else: port = self._config_data.cluster.highest_used_port + 1 if port >= constants.LAST_DRBD_PORT: - raise errors.ConfigurationError, ("The highest used port is greater" - " than %s. Aborting." % - constants.LAST_DRBD_PORT) + raise errors.ConfigurationError("The highest used port is greater" + " than %s. Aborting." % + constants.LAST_DRBD_PORT) self._config_data.cluster.highest_used_port = port self._WriteConfig() @@ -297,8 +295,9 @@ class ConfigWriter: if not isinstance(instance, objects.Instance): raise errors.ProgrammerError("Invalid type passed to AddInstance") - all_lvs = instance.MapLVsByNode() - logger.Info("Instance '%s' DISK_LAYOUT: %s" % (instance.name, all_lvs)) + if instance.disk_template != constants.DT_DISKLESS: + all_lvs = instance.MapLVsByNode() + logger.Info("Instance '%s' DISK_LAYOUT: %s" % (instance.name, all_lvs)) self._OpenConfig() self._config_data.instances[instance.name] = instance @@ -311,8 +310,8 @@ class ConfigWriter: self._OpenConfig() if instance_name not in self._config_data.instances: - raise errors.ConfigurationError, ("Unknown instance '%s'" % - instance_name) + raise errors.ConfigurationError("Unknown instance '%s'" % + instance_name) instance = self._config_data.instances[instance_name] instance.status = "up" self._WriteConfig() @@ -324,11 +323,27 @@ class ConfigWriter: self._OpenConfig() if instance_name not in self._config_data.instances: - raise errors.ConfigurationError, ("Unknown instance '%s'" % - instance_name) + raise errors.ConfigurationError("Unknown instance '%s'" % instance_name) del self._config_data.instances[instance_name] self._WriteConfig() + def RenameInstance(self, old_name, new_name): + """Rename an instance. + + This needs to be done in ConfigWriter and not by RemoveInstance + combined with AddInstance as only we can guarantee an atomic + rename. + + """ + self._OpenConfig() + if old_name not in self._config_data.instances: + raise errors.ConfigurationError("Unknown instance '%s'" % old_name) + inst = self._config_data.instances[old_name] + del self._config_data.instances[old_name] + inst.name = new_name + self._config_data.instances[inst.name] = inst + self._WriteConfig() + def MarkInstanceDown(self, instance_name): """Mark the status of an instance to down in the configuration. @@ -336,8 +351,7 @@ class ConfigWriter: self._OpenConfig() if instance_name not in self._config_data.instances: - raise errors.ConfigurationError, ("Unknown instance '%s'" % - instance_name) + raise errors.ConfigurationError("Unknown instance '%s'" % instance_name) instance = self._config_data.instances[instance_name] instance.status = "down" self._WriteConfig() @@ -403,7 +417,7 @@ class ConfigWriter: """ self._OpenConfig() if node_name not in self._config_data.nodes: - raise errors.ConfigurationError, ("Unknown node '%s'" % node_name) + raise errors.ConfigurationError("Unknown node '%s'" % node_name) del self._config_data.nodes[node_name] self._WriteConfig() @@ -466,7 +480,7 @@ class ConfigWriter: try: st = os.stat(self._cfg_file) except OSError, err: - raise errors.ConfigurationError, "Can't stat config file: %s" % err + raise errors.ConfigurationError("Can't stat config file: %s" % err) if (self._config_data is not None and self._config_time is not None and self._config_time == st.st_mtime and @@ -477,20 +491,20 @@ class ConfigWriter: f = open(self._cfg_file, 'r') try: try: - data = objects.ConfigObject.Load(f) + data = objects.ConfigData.Load(f) except Exception, err: - raise errors.ConfigurationError, err + raise errors.ConfigurationError(err) finally: f.close() if (not hasattr(data, 'cluster') or not hasattr(data.cluster, 'config_version')): - raise errors.ConfigurationError, ("Incomplete configuration" - " (missing cluster.config_version)") + raise errors.ConfigurationError("Incomplete configuration" + " (missing cluster.config_version)") if data.cluster.config_version != constants.CONFIG_VERSION: - raise errors.ConfigurationError, ("Cluster configuration version" - " mismatch, got %s instead of %s" % - (data.cluster.config_version, - constants.CONFIG_VERSION)) + raise errors.ConfigurationError("Cluster configuration version" + " mismatch, got %s instead of %s" % + (data.cluster.config_version, + constants.CONFIG_VERSION)) self._config_data = data self._config_time = st.st_mtime self._config_size = st.st_size @@ -511,7 +525,7 @@ class ConfigWriter: return True bad = False nodelist = self.GetNodeList() - myhostname = socket.gethostname() + myhostname = self._my_hostname tgt_list = [] for node in nodelist: @@ -545,6 +559,16 @@ class ConfigWriter: f.close() # we don't need to do os.close(fd) as f.close() did it os.rename(name, destination) + self.write_count += 1 + # re-set our cache as not to re-read the config file + try: + st = os.stat(destination) + except OSError, err: + raise errors.ConfigurationError("Can't stat config file: %s" % err) + self._config_time = st.st_mtime + self._config_size = st.st_size + self._config_inode = st.st_ino + # and redistribute the config file self._DistributeConfig() def InitConfig(self, node, primary_ip, secondary_ip, @@ -615,3 +639,30 @@ class ConfigWriter: self._ReleaseLock() return self._config_data.cluster + + def Update(self, target): + """Notify function to be called after updates. + + This function must be called when an object (as returned by + GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the + caller wants the modifications saved to the backing store. Note + that all modified objects will be saved, but the target argument + is the one the caller wants to ensure that it's saved. + + """ + if self._config_data is None: + raise errors.ProgrammerError("Configuration file not read," + " cannot save.") + if isinstance(target, objects.Cluster): + test = target == self._config_data.cluster + elif isinstance(target, objects.Node): + test = target in self._config_data.nodes.values() + elif isinstance(target, objects.Instance): + test = target in self._config_data.instances.values() + else: + raise errors.ProgrammerError("Invalid object type (%s) passed to" + " ConfigWriter.Update" % type(target)) + if not test: + raise errors.ConfigurationError("Configuration updated since object" + " has been read or unknown object") + self._WriteConfig()