X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/2d4011cd2b16ce77c395793bb50a6ccbbc040988..8c229cc7e24f1c7075b38ff0e21aab7c75299c4d:/lib/config.py diff --git a/lib/config.py b/lib/config.py index 3b9c3d4..f823a00 100644 --- a/lib/config.py +++ b/lib/config.py @@ -34,13 +34,30 @@ much memory. import os import tempfile import random +import re from ganeti import errors +from ganeti import locking from ganeti import logger from ganeti import utils from ganeti import constants from ganeti import rpc from ganeti import objects +from ganeti import serializer +from ganeti import ssconf + + +_config_lock = locking.SharedLock() + + +def ValidateConfig(): + sstore = ssconf.SimpleStore() + + if sstore.GetConfigVersion() != constants.CONFIG_VERSION: + raise errors.ConfigurationError("Cluster configuration version" + " mismatch, got %s instead of %s" % + (sstore.GetConfigVersion(), + constants.CONFIG_VERSION)) class ConfigWriter: @@ -49,6 +66,7 @@ class ConfigWriter: """ def __init__(self, cfg_file=None, offline=False): self.write_count = 0 + self._lock = _config_lock self._config_data = None self._config_time = None self._config_size = None @@ -73,6 +91,7 @@ class ConfigWriter: """ return os.path.exists(constants.CLUSTER_CONF_FILE) + @locking.ssynchronized(_config_lock, shared=1) def GenerateMAC(self): """Generate a MAC for an instance. @@ -80,7 +99,6 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() prefix = self._config_data.cluster.mac_prefix all_macs = self._AllMACs() retries = 64 @@ -96,6 +114,7 @@ class ConfigWriter: raise errors.ConfigurationError("Can't generate unique MAC") return mac + @locking.ssynchronized(_config_lock, shared=1) def IsMacInUse(self, mac): """Predicate: check if the specified MAC is in use in the Ganeti cluster. @@ -104,7 +123,6 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() all_macs = self._AllMACs() return mac in all_macs @@ -113,7 +131,6 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() lvnames = set() for instance in self._config_data.instances.values(): node_data = instance.MapLVsByNode() @@ -121,6 +138,7 @@ class ConfigWriter: lvnames.update(lv_list) return lvnames + @locking.ssynchronized(_config_lock, shared=1) def GenerateUniqueID(self, exceptions=None): """Generate an unique disk name. @@ -159,7 +177,6 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() result = [] for instance in self._config_data.instances.values(): @@ -168,11 +185,11 @@ class ConfigWriter: return result + @locking.ssynchronized(_config_lock, shared=1) def VerifyConfig(self): """Stub verify function. """ self._OpenConfig() - self._ReleaseLock() result = [] seen_macs = [] @@ -194,7 +211,7 @@ class ConfigWriter: seen_macs.append(nic.mac) return result - def SetDiskID(self, disk, node_name): + def _UnlockedSetDiskID(self, disk, node_name): """Convert the unique ID to the ID needed on the target nodes. This is used only for drbd, which needs ip/port configuration. @@ -203,10 +220,12 @@ class ConfigWriter: this helps when the only the top device is passed to the remote node. + This function is for internal use, when the config lock is already held. + """ if disk.children: for child in disk.children: - self.SetDiskID(child, node_name) + self._UnlockedSetDiskID(child, node_name) if disk.logical_id is None and disk.physical_id is not None: return @@ -215,8 +234,8 @@ class ConfigWriter: if node_name not in (pnode, snode): raise errors.ConfigurationError("DRBD device not knowing node %s" % node_name) - pnode_info = self.GetNodeInfo(pnode) - snode_info = self.GetNodeInfo(snode) + pnode_info = self._UnlockedGetNodeInfo(pnode) + snode_info = self._UnlockedGetNodeInfo(snode) if pnode_info is None or snode_info is None: raise errors.ConfigurationError("Can't find primary or secondary node" " for %s" % str(disk)) @@ -230,6 +249,20 @@ class ConfigWriter: disk.physical_id = disk.logical_id return + @locking.ssynchronized(_config_lock) + def SetDiskID(self, disk, node_name): + """Convert the unique ID to the ID needed on the target nodes. + + This is used only for drbd, which needs ip/port configuration. + + The routine descends down and updates its children also, because + this helps when the only the top device is passed to the remote + node. + + """ + return self._UnlockedSetDiskID(disk, node_name) + + @locking.ssynchronized(_config_lock) def AddTcpUdpPort(self, port): """Adds a new port to the available port pool. @@ -241,14 +274,15 @@ class ConfigWriter: self._config_data.cluster.tcpudp_port_pool.add(port) self._WriteConfig() + @locking.ssynchronized(_config_lock, shared=1) def GetPortList(self): """Returns a copy of the current port list. """ self._OpenConfig() - self._ReleaseLock() return self._config_data.cluster.tcpudp_port_pool.copy() + @locking.ssynchronized(_config_lock) def AllocatePort(self): """Allocate a port. @@ -273,6 +307,7 @@ class ConfigWriter: self._WriteConfig() return port + @locking.ssynchronized(_config_lock, shared=1) def GetHostKey(self): """Return the rsa hostkey from the config. @@ -281,9 +316,9 @@ class ConfigWriter: Returns: rsa hostkey """ self._OpenConfig() - self._ReleaseLock() return self._config_data.cluster.rsahostkeypub + @locking.ssynchronized(_config_lock) def AddInstance(self, instance): """Add an instance to the config. @@ -303,19 +338,32 @@ class ConfigWriter: self._config_data.instances[instance.name] = instance self._WriteConfig() - def MarkInstanceUp(self, instance_name): - """Mark the instance status to up in the config. + def _SetInstanceStatus(self, instance_name, status): + """Set the instance's status to a given value. """ + if status not in ("up", "down"): + raise errors.ProgrammerError("Invalid status '%s' passed to" + " ConfigWriter._SetInstanceStatus()" % + status) self._OpenConfig() if instance_name not in self._config_data.instances: raise errors.ConfigurationError("Unknown instance '%s'" % instance_name) instance = self._config_data.instances[instance_name] - instance.status = "up" - self._WriteConfig() + if instance.status != status: + instance.status = status + self._WriteConfig() + + @locking.ssynchronized(_config_lock) + def MarkInstanceUp(self, instance_name): + """Mark the instance status to up in the config. + """ + self._SetInstanceStatus(instance_name, "up") + + @locking.ssynchronized(_config_lock) def RemoveInstance(self, instance_name): """Remove the instance from the configuration. @@ -327,6 +375,7 @@ class ConfigWriter: del self._config_data.instances[instance_name] self._WriteConfig() + @locking.ssynchronized(_config_lock) def RenameInstance(self, old_name, new_name): """Rename an instance. @@ -341,21 +390,27 @@ class ConfigWriter: inst = self._config_data.instances[old_name] del self._config_data.instances[old_name] inst.name = new_name + + for disk in inst.disks: + if disk.dev_type == constants.LD_FILE: + # rename the file paths in logical and physical id + file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1])) + disk.physical_id = disk.logical_id = (disk.logical_id[0], + os.path.join(file_storage_dir, + inst.name, + disk.iv_name)) + self._config_data.instances[inst.name] = inst self._WriteConfig() + @locking.ssynchronized(_config_lock) def MarkInstanceDown(self, instance_name): """Mark the status of an instance to down in the configuration. """ - self._OpenConfig() - - if instance_name not in self._config_data.instances: - raise errors.ConfigurationError("Unknown instance '%s'" % instance_name) - instance = self._config_data.instances[instance_name] - instance.status = "down" - self._WriteConfig() + self._SetInstanceStatus(instance_name, "down") + @locking.ssynchronized(_config_lock, shared=1) def GetInstanceList(self): """Get the list of instances. @@ -365,20 +420,20 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() return self._config_data.instances.keys() + @locking.ssynchronized(_config_lock, shared=1) def ExpandInstanceName(self, short_name): """Attempt to expand an incomplete instance name. """ self._OpenConfig() - self._ReleaseLock() return utils.MatchNameComponent(short_name, self._config_data.instances.keys()) + @locking.ssynchronized(_config_lock, shared=1) def GetInstanceInfo(self, instance_name): """Returns informations about an instance. @@ -393,13 +448,13 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() if instance_name not in self._config_data.instances: return None return self._config_data.instances[instance_name] + @locking.ssynchronized(_config_lock) def AddNode(self, node): """Add a node to the configuration. @@ -411,6 +466,7 @@ class ConfigWriter: self._config_data.nodes[node.name] = node self._WriteConfig() + @locking.ssynchronized(_config_lock) def RemoveNode(self, node_name): """Remove a node from the configuration. @@ -422,45 +478,67 @@ class ConfigWriter: del self._config_data.nodes[node_name] self._WriteConfig() + @locking.ssynchronized(_config_lock, shared=1) def ExpandNodeName(self, short_name): """Attempt to expand an incomplete instance name. """ self._OpenConfig() - self._ReleaseLock() return utils.MatchNameComponent(short_name, self._config_data.nodes.keys()) - def GetNodeInfo(self, node_name): + def _UnlockedGetNodeInfo(self, node_name): """Get the configuration of a node, as stored in the config. + This function is for internal use, when the config lock is already held. + Args: node: nodename (tuple) of the node Returns: the node object """ self._OpenConfig() - self._ReleaseLock() if node_name not in self._config_data.nodes: return None return self._config_data.nodes[node_name] - def GetNodeList(self): + + @locking.ssynchronized(_config_lock, shared=1) + def GetNodeInfo(self, node_name): + """Get the configuration of a node, as stored in the config. + + Args: node: nodename (tuple) of the node + + Returns: the node object + + """ + return self._UnlockedGetNodeInfo(node_name) + + def _UnlockedGetNodeList(self): """Return the list of nodes which are in the configuration. + This function is for internal use, when the config lock is already held. + """ self._OpenConfig() - self._ReleaseLock() return self._config_data.nodes.keys() + + @locking.ssynchronized(_config_lock, shared=1) + def GetNodeList(self): + """Return the list of nodes which are in the configuration. + + """ + return self._UnlockedGetNodeList() + + @locking.ssynchronized(_config_lock, shared=1) def DumpConfig(self): """Return the entire configuration of the cluster. """ self._OpenConfig() - self._ReleaseLock() return self._config_data def _BumpSerialNo(self): @@ -488,32 +566,27 @@ class ConfigWriter: self._config_inode == st.st_ino): # data is current, so skip loading of config file return + + # Make sure the configuration has the right version + ValidateConfig() + f = open(self._cfg_file, 'r') try: try: - data = objects.ConfigData.Load(f) + data = objects.ConfigData.FromDict(serializer.Load(f.read())) except Exception, err: raise errors.ConfigurationError(err) finally: f.close() if (not hasattr(data, 'cluster') or - not hasattr(data.cluster, 'config_version')): + not hasattr(data.cluster, 'rsahostkeypub')): 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)) + " (missing cluster.rsahostkeypub)") self._config_data = data self._config_time = st.st_mtime self._config_size = st.st_size self._config_inode = st.st_ino - def _ReleaseLock(self): - """xxxx - """ - def _DistributeConfig(self): """Distribute the configuration to the other nodes. @@ -524,18 +597,16 @@ class ConfigWriter: if self._offline: return True bad = False - nodelist = self.GetNodeList() + nodelist = self._UnlockedGetNodeList() myhostname = self._my_hostname - tgt_list = [] - for node in nodelist: - nodeinfo = self.GetNodeInfo(node) - if nodeinfo.name == myhostname: - continue - tgt_list.append(node) + try: + nodelist.remove(myhostname) + except ValueError: + pass - result = rpc.call_upload_file(tgt_list, self._cfg_file) - for node in tgt_list: + result = rpc.call_upload_file(nodelist, self._cfg_file) + for node in nodelist: if not result[node]: logger.Error("copy of file %s to node %s failed" % (self._cfg_file, node)) @@ -549,11 +620,12 @@ class ConfigWriter: if destination is None: destination = self._cfg_file self._BumpSerialNo() + txt = serializer.Dump(self._config_data.ToDict()) dir_name, file_name = os.path.split(destination) fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name) f = os.fdopen(fd, 'w') try: - self._config_data.Dump(f) + f.write(txt) os.fsync(f.fileno()) finally: f.close() @@ -571,6 +643,7 @@ class ConfigWriter: # and redistribute the config file self._DistributeConfig() + @locking.ssynchronized(_config_lock) def InitConfig(self, node, primary_ip, secondary_ip, hostkeypub, mac_prefix, vg_name, def_bridge): """Create the initial cluster configuration. @@ -586,8 +659,7 @@ class ConfigWriter: """ hu_port = constants.FIRST_DRBD_PORT - 1 - globalconfig = objects.Cluster(config_version=constants.CONFIG_VERSION, - serial_no=1, + globalconfig = objects.Cluster(serial_no=1, rsahostkeypub=hostkeypub, highest_used_port=hu_port, mac_prefix=mac_prefix, @@ -604,14 +676,15 @@ class ConfigWriter: cluster=globalconfig) self._WriteConfig() + @locking.ssynchronized(_config_lock, shared=1) def GetVGName(self): """Return the volume group name. """ self._OpenConfig() - self._ReleaseLock() return self._config_data.cluster.volume_group_name + @locking.ssynchronized(_config_lock) def SetVGName(self, vg_name): """Set the volume group name. @@ -620,22 +693,23 @@ class ConfigWriter: self._config_data.cluster.volume_group_name = vg_name self._WriteConfig() + @locking.ssynchronized(_config_lock, shared=1) def GetDefBridge(self): """Return the default bridge. """ self._OpenConfig() - self._ReleaseLock() return self._config_data.cluster.default_bridge + @locking.ssynchronized(_config_lock, shared=1) def GetMACPrefix(self): """Return the mac prefix. """ self._OpenConfig() - self._ReleaseLock() return self._config_data.cluster.mac_prefix + @locking.ssynchronized(_config_lock, shared=1) def GetClusterInfo(self): """Returns informations about the cluster @@ -644,10 +718,10 @@ class ConfigWriter: """ self._OpenConfig() - self._ReleaseLock() return self._config_data.cluster + @locking.ssynchronized(_config_lock) def Update(self, target): """Notify function to be called after updates.