-#!/usr/bin/python
+#
#
# Copyright (C) 2006, 2007 Google Inc.
"""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
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
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
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.
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
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)
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:
"""
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)
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()
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
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()
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.
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()
"""
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()
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
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
return True
bad = False
nodelist = self.GetNodeList()
- myhostname = socket.gethostname()
+ myhostname = self._my_hostname
tgt_list = []
for node in nodelist:
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,
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()