4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
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.
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.
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
22 """Configuration management for Ganeti
24 This module provides the interface to the Ganeti cluster configuration.
26 The configuration data is stored on every node but is updated on the master
27 only. After each update, the master distributes the data to the other nodes.
29 Currently, the data storage format is JSON. YAML was slow and consuming too
34 # pylint: disable-msg=R0904
35 # R0904: Too many public methods
42 from ganeti import errors
43 from ganeti import locking
44 from ganeti import utils
45 from ganeti import constants
46 from ganeti import rpc
47 from ganeti import objects
48 from ganeti import serializer
49 from ganeti import uidpool
50 from ganeti import netutils
51 from ganeti import runtime
54 _config_lock = locking.SharedLock("ConfigWriter")
56 # job id used for resource management at config upgrade time
57 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
60 def _ValidateConfig(data):
61 """Verifies that a configuration objects looks valid.
63 This only verifies the version of the configuration.
65 @raise errors.ConfigurationError: if the version differs from what
69 if data.version != constants.CONFIG_VERSION:
70 raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
73 class TemporaryReservationManager:
74 """A temporary resource reservation manager.
76 This is used to reserve resources in a job, before using them, making sure
77 other jobs cannot get them in the meantime.
81 self._ec_reserved = {}
83 def Reserved(self, resource):
84 for holder_reserved in self._ec_reserved.values():
85 if resource in holder_reserved:
89 def Reserve(self, ec_id, resource):
90 if self.Reserved(resource):
91 raise errors.ReservationError("Duplicate reservation for resource '%s'"
93 if ec_id not in self._ec_reserved:
94 self._ec_reserved[ec_id] = set([resource])
96 self._ec_reserved[ec_id].add(resource)
98 def DropECReservations(self, ec_id):
99 if ec_id in self._ec_reserved:
100 del self._ec_reserved[ec_id]
102 def GetReserved(self):
104 for holder_reserved in self._ec_reserved.values():
105 all_reserved.update(holder_reserved)
108 def Generate(self, existing, generate_one_fn, ec_id):
109 """Generate a new resource of this type
112 assert callable(generate_one_fn)
114 all_elems = self.GetReserved()
115 all_elems.update(existing)
118 new_resource = generate_one_fn()
119 if new_resource is not None and new_resource not in all_elems:
122 raise errors.ConfigurationError("Not able generate new resource"
123 " (last tried: %s)" % new_resource)
124 self.Reserve(ec_id, new_resource)
129 """The interface to the cluster configuration.
131 @ivar _temporary_lvs: reservation manager for temporary LVs
132 @ivar _all_rms: a list of all temporary reservation managers
135 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
136 accept_foreign=False):
138 self._lock = _config_lock
139 self._config_data = None
140 self._offline = offline
142 self._cfg_file = constants.CLUSTER_CONF_FILE
144 self._cfg_file = cfg_file
145 self._getents = _getents
146 self._temporary_ids = TemporaryReservationManager()
147 self._temporary_drbds = {}
148 self._temporary_macs = TemporaryReservationManager()
149 self._temporary_secrets = TemporaryReservationManager()
150 self._temporary_lvs = TemporaryReservationManager()
151 self._all_rms = [self._temporary_ids, self._temporary_macs,
152 self._temporary_secrets, self._temporary_lvs]
153 # Note: in order to prevent errors when resolving our name in
154 # _DistributeConfig, we compute it here once and reuse it; it's
155 # better to raise an error before starting to modify the config
156 # file than after it was modified
157 self._my_hostname = netutils.Hostname.GetSysName()
158 self._last_cluster_serial = -1
160 self._OpenConfig(accept_foreign)
162 # this method needs to be static, so that we can call it on the class
165 """Check if the cluster is configured.
168 return os.path.exists(constants.CLUSTER_CONF_FILE)
170 def _GenerateOneMAC(self):
171 """Generate one mac address
174 prefix = self._config_data.cluster.mac_prefix
175 byte1 = random.randrange(0, 256)
176 byte2 = random.randrange(0, 256)
177 byte3 = random.randrange(0, 256)
178 mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
181 @locking.ssynchronized(_config_lock, shared=1)
182 def GetNdParams(self, node):
183 """Get the node params populated with cluster defaults.
185 @type node: L{object.Node}
186 @param node: The node we want to know the params for
187 @return: A dict with the filled in node params
190 nodegroup = self._UnlockedGetNodeGroup(node.group)
191 return self._config_data.cluster.FillND(node, nodegroup)
193 @locking.ssynchronized(_config_lock, shared=1)
194 def GenerateMAC(self, ec_id):
195 """Generate a MAC for an instance.
197 This should check the current instances for duplicates.
200 existing = self._AllMACs()
201 return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
203 @locking.ssynchronized(_config_lock, shared=1)
204 def ReserveMAC(self, mac, ec_id):
205 """Reserve a MAC for an instance.
207 This only checks instances managed by this cluster, it does not
208 check for potential collisions elsewhere.
211 all_macs = self._AllMACs()
213 raise errors.ReservationError("mac already in use")
215 self._temporary_macs.Reserve(mac, ec_id)
217 @locking.ssynchronized(_config_lock, shared=1)
218 def ReserveLV(self, lv_name, ec_id):
219 """Reserve an VG/LV pair for an instance.
221 @type lv_name: string
222 @param lv_name: the logical volume name to reserve
225 all_lvs = self._AllLVs()
226 if lv_name in all_lvs:
227 raise errors.ReservationError("LV already in use")
229 self._temporary_lvs.Reserve(lv_name, ec_id)
231 @locking.ssynchronized(_config_lock, shared=1)
232 def GenerateDRBDSecret(self, ec_id):
233 """Generate a DRBD secret.
235 This checks the current disks for duplicates.
238 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
239 utils.GenerateSecret,
243 """Compute the list of all LVs.
247 for instance in self._config_data.instances.values():
248 node_data = instance.MapLVsByNode()
249 for lv_list in node_data.values():
250 lvnames.update(lv_list)
253 def _AllIDs(self, include_temporary):
254 """Compute the list of all UUIDs and names we have.
256 @type include_temporary: boolean
257 @param include_temporary: whether to include the _temporary_ids set
259 @return: a set of IDs
263 if include_temporary:
264 existing.update(self._temporary_ids.GetReserved())
265 existing.update(self._AllLVs())
266 existing.update(self._config_data.instances.keys())
267 existing.update(self._config_data.nodes.keys())
268 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
271 def _GenerateUniqueID(self, ec_id):
272 """Generate an unique UUID.
274 This checks the current node, instances and disk names for
278 @return: the unique id
281 existing = self._AllIDs(include_temporary=False)
282 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
284 @locking.ssynchronized(_config_lock, shared=1)
285 def GenerateUniqueID(self, ec_id):
286 """Generate an unique ID.
288 This is just a wrapper over the unlocked version.
291 @param ec_id: unique id for the job to reserve the id to
294 return self._GenerateUniqueID(ec_id)
297 """Return all MACs present in the config.
300 @return: the list of all MACs
304 for instance in self._config_data.instances.values():
305 for nic in instance.nics:
306 result.append(nic.mac)
310 def _AllDRBDSecrets(self):
311 """Return all DRBD secrets present in the config.
314 @return: the list of all DRBD secrets
317 def helper(disk, result):
318 """Recursively gather secrets from this disk."""
319 if disk.dev_type == constants.DT_DRBD8:
320 result.append(disk.logical_id[5])
322 for child in disk.children:
323 helper(child, result)
326 for instance in self._config_data.instances.values():
327 for disk in instance.disks:
332 def _CheckDiskIDs(self, disk, l_ids, p_ids):
333 """Compute duplicate disk IDs
335 @type disk: L{objects.Disk}
336 @param disk: the disk at which to start searching
338 @param l_ids: list of current logical ids
340 @param p_ids: list of current physical ids
342 @return: a list of error messages
346 if disk.logical_id is not None:
347 if disk.logical_id in l_ids:
348 result.append("duplicate logical id %s" % str(disk.logical_id))
350 l_ids.append(disk.logical_id)
351 if disk.physical_id is not None:
352 if disk.physical_id in p_ids:
353 result.append("duplicate physical id %s" % str(disk.physical_id))
355 p_ids.append(disk.physical_id)
358 for child in disk.children:
359 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
362 def _UnlockedVerifyConfig(self):
366 @return: a list of error messages; a non-empty list signifies
373 data = self._config_data
377 # global cluster checks
378 if not data.cluster.enabled_hypervisors:
379 result.append("enabled hypervisors list doesn't have any entries")
380 invalid_hvs = set(data.cluster.enabled_hypervisors) - constants.HYPER_TYPES
382 result.append("enabled hypervisors contains invalid entries: %s" %
384 missing_hvp = (set(data.cluster.enabled_hypervisors) -
385 set(data.cluster.hvparams.keys()))
387 result.append("hypervisor parameters missing for the enabled"
388 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
390 if data.cluster.master_node not in data.nodes:
391 result.append("cluster has invalid primary node '%s'" %
392 data.cluster.master_node)
394 # per-instance checks
395 for instance_name in data.instances:
396 instance = data.instances[instance_name]
397 if instance.name != instance_name:
398 result.append("instance '%s' is indexed by wrong name '%s'" %
399 (instance.name, instance_name))
400 if instance.primary_node not in data.nodes:
401 result.append("instance '%s' has invalid primary node '%s'" %
402 (instance_name, instance.primary_node))
403 for snode in instance.secondary_nodes:
404 if snode not in data.nodes:
405 result.append("instance '%s' has invalid secondary node '%s'" %
406 (instance_name, snode))
407 for idx, nic in enumerate(instance.nics):
408 if nic.mac in seen_macs:
409 result.append("instance '%s' has NIC %d mac %s duplicate" %
410 (instance_name, idx, nic.mac))
412 seen_macs.append(nic.mac)
414 # gather the drbd ports for duplicate checks
415 for dsk in instance.disks:
416 if dsk.dev_type in constants.LDS_DRBD:
417 tcp_port = dsk.logical_id[2]
418 if tcp_port not in ports:
420 ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
421 # gather network port reservation
422 net_port = getattr(instance, "network_port", None)
423 if net_port is not None:
424 if net_port not in ports:
426 ports[net_port].append((instance.name, "network port"))
428 # instance disk verify
429 for idx, disk in enumerate(instance.disks):
430 result.extend(["instance '%s' disk %d error: %s" %
431 (instance.name, idx, msg) for msg in disk.Verify()])
432 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
434 # cluster-wide pool of free ports
435 for free_port in data.cluster.tcpudp_port_pool:
436 if free_port not in ports:
437 ports[free_port] = []
438 ports[free_port].append(("cluster", "port marked as free"))
440 # compute tcp/udp duplicate ports
446 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
447 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
449 # highest used tcp port check
451 if keys[-1] > data.cluster.highest_used_port:
452 result.append("Highest used port mismatch, saved %s, computed %s" %
453 (data.cluster.highest_used_port, keys[-1]))
455 if not data.nodes[data.cluster.master_node].master_candidate:
456 result.append("Master node is not a master candidate")
458 # master candidate checks
459 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
461 result.append("Not enough master candidates: actual %d, target %d" %
465 for node_name, node in data.nodes.items():
466 if node.name != node_name:
467 result.append("Node '%s' is indexed by wrong name '%s'" %
468 (node.name, node_name))
469 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
470 result.append("Node %s state is invalid: master_candidate=%s,"
471 " drain=%s, offline=%s" %
472 (node.name, node.master_candidate, node.drained,
476 nodegroups_names = set()
477 for nodegroup_uuid in data.nodegroups:
478 nodegroup = data.nodegroups[nodegroup_uuid]
479 if nodegroup.uuid != nodegroup_uuid:
480 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
481 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
482 if utils.UUID_RE.match(nodegroup.name.lower()):
483 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
484 (nodegroup.name, nodegroup.uuid))
485 if nodegroup.name in nodegroups_names:
486 result.append("duplicate node group name '%s'" % nodegroup.name)
488 nodegroups_names.add(nodegroup.name)
491 _, duplicates = self._UnlockedComputeDRBDMap()
492 for node, minor, instance_a, instance_b in duplicates:
493 result.append("DRBD minor %d on node %s is assigned twice to instances"
494 " %s and %s" % (minor, node, instance_a, instance_b))
497 default_nicparams = data.cluster.nicparams[constants.PP_DEFAULT]
500 def _AddIpAddress(ip, name):
501 ips.setdefault(ip, []).append(name)
503 _AddIpAddress(data.cluster.master_ip, "cluster_ip")
505 for node in data.nodes.values():
506 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
507 if node.secondary_ip != node.primary_ip:
508 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
510 for instance in data.instances.values():
511 for idx, nic in enumerate(instance.nics):
515 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
516 nic_mode = nicparams[constants.NIC_MODE]
517 nic_link = nicparams[constants.NIC_LINK]
519 if nic_mode == constants.NIC_MODE_BRIDGED:
520 link = "bridge:%s" % nic_link
521 elif nic_mode == constants.NIC_MODE_ROUTED:
522 link = "route:%s" % nic_link
524 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
526 _AddIpAddress("%s/%s" % (link, nic.ip),
527 "instance:%s/nic:%d" % (instance.name, idx))
529 for ip, owners in ips.items():
531 result.append("IP address %s is used by multiple owners: %s" %
532 (ip, utils.CommaJoin(owners)))
536 @locking.ssynchronized(_config_lock, shared=1)
537 def VerifyConfig(self):
540 This is just a wrapper over L{_UnlockedVerifyConfig}.
543 @return: a list of error messages; a non-empty list signifies
547 return self._UnlockedVerifyConfig()
549 def _UnlockedSetDiskID(self, disk, node_name):
550 """Convert the unique ID to the ID needed on the target nodes.
552 This is used only for drbd, which needs ip/port configuration.
554 The routine descends down and updates its children also, because
555 this helps when the only the top device is passed to the remote
558 This function is for internal use, when the config lock is already held.
562 for child in disk.children:
563 self._UnlockedSetDiskID(child, node_name)
565 if disk.logical_id is None and disk.physical_id is not None:
567 if disk.dev_type == constants.LD_DRBD8:
568 pnode, snode, port, pminor, sminor, secret = disk.logical_id
569 if node_name not in (pnode, snode):
570 raise errors.ConfigurationError("DRBD device not knowing node %s" %
572 pnode_info = self._UnlockedGetNodeInfo(pnode)
573 snode_info = self._UnlockedGetNodeInfo(snode)
574 if pnode_info is None or snode_info is None:
575 raise errors.ConfigurationError("Can't find primary or secondary node"
576 " for %s" % str(disk))
577 p_data = (pnode_info.secondary_ip, port)
578 s_data = (snode_info.secondary_ip, port)
579 if pnode == node_name:
580 disk.physical_id = p_data + s_data + (pminor, secret)
581 else: # it must be secondary, we tested above
582 disk.physical_id = s_data + p_data + (sminor, secret)
584 disk.physical_id = disk.logical_id
587 @locking.ssynchronized(_config_lock)
588 def SetDiskID(self, disk, node_name):
589 """Convert the unique ID to the ID needed on the target nodes.
591 This is used only for drbd, which needs ip/port configuration.
593 The routine descends down and updates its children also, because
594 this helps when the only the top device is passed to the remote
598 return self._UnlockedSetDiskID(disk, node_name)
600 @locking.ssynchronized(_config_lock)
601 def AddTcpUdpPort(self, port):
602 """Adds a new port to the available port pool.
605 if not isinstance(port, int):
606 raise errors.ProgrammerError("Invalid type passed for port")
608 self._config_data.cluster.tcpudp_port_pool.add(port)
611 @locking.ssynchronized(_config_lock, shared=1)
612 def GetPortList(self):
613 """Returns a copy of the current port list.
616 return self._config_data.cluster.tcpudp_port_pool.copy()
618 @locking.ssynchronized(_config_lock)
619 def AllocatePort(self):
622 The port will be taken from the available port pool or from the
623 default port range (and in this case we increase
627 # If there are TCP/IP ports configured, we use them first.
628 if self._config_data.cluster.tcpudp_port_pool:
629 port = self._config_data.cluster.tcpudp_port_pool.pop()
631 port = self._config_data.cluster.highest_used_port + 1
632 if port >= constants.LAST_DRBD_PORT:
633 raise errors.ConfigurationError("The highest used port is greater"
634 " than %s. Aborting." %
635 constants.LAST_DRBD_PORT)
636 self._config_data.cluster.highest_used_port = port
641 def _UnlockedComputeDRBDMap(self):
642 """Compute the used DRBD minor/nodes.
645 @return: dictionary of node_name: dict of minor: instance_name;
646 the returned dict will have all the nodes in it (even if with
647 an empty list), and a list of duplicates; if the duplicates
648 list is not empty, the configuration is corrupted and its caller
649 should raise an exception
652 def _AppendUsedPorts(instance_name, disk, used):
654 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
655 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
656 for node, port in ((node_a, minor_a), (node_b, minor_b)):
657 assert node in used, ("Node '%s' of instance '%s' not found"
658 " in node list" % (node, instance_name))
659 if port in used[node]:
660 duplicates.append((node, port, instance_name, used[node][port]))
662 used[node][port] = instance_name
664 for child in disk.children:
665 duplicates.extend(_AppendUsedPorts(instance_name, child, used))
669 my_dict = dict((node, {}) for node in self._config_data.nodes)
670 for instance in self._config_data.instances.itervalues():
671 for disk in instance.disks:
672 duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
673 for (node, minor), instance in self._temporary_drbds.iteritems():
674 if minor in my_dict[node] and my_dict[node][minor] != instance:
675 duplicates.append((node, minor, instance, my_dict[node][minor]))
677 my_dict[node][minor] = instance
678 return my_dict, duplicates
680 @locking.ssynchronized(_config_lock)
681 def ComputeDRBDMap(self):
682 """Compute the used DRBD minor/nodes.
684 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
686 @return: dictionary of node_name: dict of minor: instance_name;
687 the returned dict will have all the nodes in it (even if with
691 d_map, duplicates = self._UnlockedComputeDRBDMap()
693 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
697 @locking.ssynchronized(_config_lock)
698 def AllocateDRBDMinor(self, nodes, instance):
699 """Allocate a drbd minor.
701 The free minor will be automatically computed from the existing
702 devices. A node can be given multiple times in order to allocate
703 multiple minors. The result is the list of minors, in the same
704 order as the passed nodes.
706 @type instance: string
707 @param instance: the instance for which we allocate minors
710 assert isinstance(instance, basestring), \
711 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
713 d_map, duplicates = self._UnlockedComputeDRBDMap()
715 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
721 # no minors used, we can start at 0
724 self._temporary_drbds[(nname, 0)] = instance
728 ffree = utils.FirstFree(keys)
730 # return the next minor
731 # TODO: implement high-limit check
735 # double-check minor against current instances
736 assert minor not in d_map[nname], \
737 ("Attempt to reuse allocated DRBD minor %d on node %s,"
738 " already allocated to instance %s" %
739 (minor, nname, d_map[nname][minor]))
740 ndata[minor] = instance
741 # double-check minor against reservation
742 r_key = (nname, minor)
743 assert r_key not in self._temporary_drbds, \
744 ("Attempt to reuse reserved DRBD minor %d on node %s,"
745 " reserved for instance %s" %
746 (minor, nname, self._temporary_drbds[r_key]))
747 self._temporary_drbds[r_key] = instance
749 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
753 def _UnlockedReleaseDRBDMinors(self, instance):
754 """Release temporary drbd minors allocated for a given instance.
756 @type instance: string
757 @param instance: the instance for which temporary minors should be
761 assert isinstance(instance, basestring), \
762 "Invalid argument passed to ReleaseDRBDMinors"
763 for key, name in self._temporary_drbds.items():
765 del self._temporary_drbds[key]
767 @locking.ssynchronized(_config_lock)
768 def ReleaseDRBDMinors(self, instance):
769 """Release temporary drbd minors allocated for a given instance.
771 This should be called on the error paths, on the success paths
772 it's automatically called by the ConfigWriter add and update
775 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
777 @type instance: string
778 @param instance: the instance for which temporary minors should be
782 self._UnlockedReleaseDRBDMinors(instance)
784 @locking.ssynchronized(_config_lock, shared=1)
785 def GetConfigVersion(self):
786 """Get the configuration version.
788 @return: Config version
791 return self._config_data.version
793 @locking.ssynchronized(_config_lock, shared=1)
794 def GetClusterName(self):
797 @return: Cluster name
800 return self._config_data.cluster.cluster_name
802 @locking.ssynchronized(_config_lock, shared=1)
803 def GetMasterNode(self):
804 """Get the hostname of the master node for this cluster.
806 @return: Master hostname
809 return self._config_data.cluster.master_node
811 @locking.ssynchronized(_config_lock, shared=1)
812 def GetMasterIP(self):
813 """Get the IP of the master node for this cluster.
818 return self._config_data.cluster.master_ip
820 @locking.ssynchronized(_config_lock, shared=1)
821 def GetMasterNetdev(self):
822 """Get the master network device for this cluster.
825 return self._config_data.cluster.master_netdev
827 @locking.ssynchronized(_config_lock, shared=1)
828 def GetFileStorageDir(self):
829 """Get the file storage dir for this cluster.
832 return self._config_data.cluster.file_storage_dir
834 @locking.ssynchronized(_config_lock, shared=1)
835 def GetHypervisorType(self):
836 """Get the hypervisor type for this cluster.
839 return self._config_data.cluster.enabled_hypervisors[0]
841 @locking.ssynchronized(_config_lock, shared=1)
842 def GetHostKey(self):
843 """Return the rsa hostkey from the config.
846 @return: the rsa hostkey
849 return self._config_data.cluster.rsahostkeypub
851 @locking.ssynchronized(_config_lock, shared=1)
852 def GetDefaultIAllocator(self):
853 """Get the default instance allocator for this cluster.
856 return self._config_data.cluster.default_iallocator
858 @locking.ssynchronized(_config_lock, shared=1)
859 def GetPrimaryIPFamily(self):
860 """Get cluster primary ip family.
862 @return: primary ip family
865 return self._config_data.cluster.primary_ip_family
867 @locking.ssynchronized(_config_lock)
868 def AddNodeGroup(self, group, ec_id, check_uuid=True):
869 """Add a node group to the configuration.
871 This method calls group.UpgradeConfig() to fill any missing attributes
872 according to their default values.
874 @type group: L{objects.NodeGroup}
875 @param group: the NodeGroup object to add
877 @param ec_id: unique id for the job to use when creating a missing UUID
878 @type check_uuid: bool
879 @param check_uuid: add an UUID to the group if it doesn't have one or, if
880 it does, ensure that it does not exist in the
881 configuration already
884 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
887 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
888 """Add a node group to the configuration.
891 logging.info("Adding node group %s to configuration", group.name)
893 # Some code might need to add a node group with a pre-populated UUID
894 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
895 # the "does this UUID" exist already check.
897 self._EnsureUUID(group, ec_id)
900 group.ctime = group.mtime = time.time()
901 group.UpgradeConfig()
903 self._config_data.nodegroups[group.uuid] = group
904 self._config_data.cluster.serial_no += 1
906 @locking.ssynchronized(_config_lock)
907 def RemoveNodeGroup(self, group_uuid):
908 """Remove a node group from the configuration.
910 @type group_uuid: string
911 @param group_uuid: the UUID of the node group to remove
914 logging.info("Removing node group %s from configuration", group_uuid)
916 if group_uuid not in self._config_data.nodegroups:
917 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
919 del self._config_data.nodegroups[group_uuid]
920 self._config_data.cluster.serial_no += 1
923 @locking.ssynchronized(_config_lock, shared=1)
924 def LookupNodeGroup(self, target):
925 """Lookup a node group's UUID.
927 @type target: string or None
928 @param target: group name or UUID or None to look for the default
930 @return: nodegroup UUID
931 @raises errors.OpPrereqError: when the target group cannot be found
935 if len(self._config_data.nodegroups) != 1:
936 raise errors.OpPrereqError("More than one node group exists. Target"
937 " group must be specified explicitely.")
939 return self._config_data.nodegroups.keys()[0]
940 if target in self._config_data.nodegroups:
942 for nodegroup in self._config_data.nodegroups.values():
943 if nodegroup.name == target:
944 return nodegroup.uuid
945 raise errors.OpPrereqError("Node group '%s' not found" % target,
948 def _UnlockedGetNodeGroup(self, uuid):
949 """Lookup a node group.
952 @param uuid: group UUID
953 @rtype: L{objects.NodeGroup} or None
954 @return: nodegroup object, or None if not found
957 if uuid not in self._config_data.nodegroups:
960 return self._config_data.nodegroups[uuid]
962 @locking.ssynchronized(_config_lock, shared=1)
963 def GetNodeGroup(self, uuid):
964 """Lookup a node group.
967 @param uuid: group UUID
968 @rtype: L{objects.NodeGroup} or None
969 @return: nodegroup object, or None if not found
972 return self._UnlockedGetNodeGroup(uuid)
974 @locking.ssynchronized(_config_lock, shared=1)
975 def GetAllNodeGroupsInfo(self):
976 """Get the configuration of all node groups.
979 return dict(self._config_data.nodegroups)
981 @locking.ssynchronized(_config_lock, shared=1)
982 def GetNodeGroupList(self):
983 """Get a list of node groups.
986 return self._config_data.nodegroups.keys()
988 @locking.ssynchronized(_config_lock)
989 def AddInstance(self, instance, ec_id):
990 """Add an instance to the config.
992 This should be used after creating a new instance.
994 @type instance: L{objects.Instance}
995 @param instance: the instance object
998 if not isinstance(instance, objects.Instance):
999 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1001 if instance.disk_template != constants.DT_DISKLESS:
1002 all_lvs = instance.MapLVsByNode()
1003 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1005 all_macs = self._AllMACs()
1006 for nic in instance.nics:
1007 if nic.mac in all_macs:
1008 raise errors.ConfigurationError("Cannot add instance %s:"
1009 " MAC address '%s' already in use." %
1010 (instance.name, nic.mac))
1012 self._EnsureUUID(instance, ec_id)
1014 instance.serial_no = 1
1015 instance.ctime = instance.mtime = time.time()
1016 self._config_data.instances[instance.name] = instance
1017 self._config_data.cluster.serial_no += 1
1018 self._UnlockedReleaseDRBDMinors(instance.name)
1021 def _EnsureUUID(self, item, ec_id):
1022 """Ensures a given object has a valid UUID.
1024 @param item: the instance or node to be checked
1025 @param ec_id: the execution context id for the uuid reservation
1029 item.uuid = self._GenerateUniqueID(ec_id)
1030 elif item.uuid in self._AllIDs(include_temporary=True):
1031 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1032 " in use" % (item.name, item.uuid))
1034 def _SetInstanceStatus(self, instance_name, status):
1035 """Set the instance's status to a given value.
1038 assert isinstance(status, bool), \
1039 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1041 if instance_name not in self._config_data.instances:
1042 raise errors.ConfigurationError("Unknown instance '%s'" %
1044 instance = self._config_data.instances[instance_name]
1045 if instance.admin_up != status:
1046 instance.admin_up = status
1047 instance.serial_no += 1
1048 instance.mtime = time.time()
1051 @locking.ssynchronized(_config_lock)
1052 def MarkInstanceUp(self, instance_name):
1053 """Mark the instance status to up in the config.
1056 self._SetInstanceStatus(instance_name, True)
1058 @locking.ssynchronized(_config_lock)
1059 def RemoveInstance(self, instance_name):
1060 """Remove the instance from the configuration.
1063 if instance_name not in self._config_data.instances:
1064 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1065 del self._config_data.instances[instance_name]
1066 self._config_data.cluster.serial_no += 1
1069 @locking.ssynchronized(_config_lock)
1070 def RenameInstance(self, old_name, new_name):
1071 """Rename an instance.
1073 This needs to be done in ConfigWriter and not by RemoveInstance
1074 combined with AddInstance as only we can guarantee an atomic
1078 if old_name not in self._config_data.instances:
1079 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1080 inst = self._config_data.instances[old_name]
1081 del self._config_data.instances[old_name]
1082 inst.name = new_name
1084 for disk in inst.disks:
1085 if disk.dev_type == constants.LD_FILE:
1086 # rename the file paths in logical and physical id
1087 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1088 disk_fname = "disk%s" % disk.iv_name.split("/")[1]
1089 disk.physical_id = disk.logical_id = (disk.logical_id[0],
1090 utils.PathJoin(file_storage_dir,
1094 # Force update of ssconf files
1095 self._config_data.cluster.serial_no += 1
1097 self._config_data.instances[inst.name] = inst
1100 @locking.ssynchronized(_config_lock)
1101 def MarkInstanceDown(self, instance_name):
1102 """Mark the status of an instance to down in the configuration.
1105 self._SetInstanceStatus(instance_name, False)
1107 def _UnlockedGetInstanceList(self):
1108 """Get the list of instances.
1110 This function is for internal use, when the config lock is already held.
1113 return self._config_data.instances.keys()
1115 @locking.ssynchronized(_config_lock, shared=1)
1116 def GetInstanceList(self):
1117 """Get the list of instances.
1119 @return: array of instances, ex. ['instance2.example.com',
1120 'instance1.example.com']
1123 return self._UnlockedGetInstanceList()
1125 @locking.ssynchronized(_config_lock, shared=1)
1126 def ExpandInstanceName(self, short_name):
1127 """Attempt to expand an incomplete instance name.
1130 return utils.MatchNameComponent(short_name,
1131 self._config_data.instances.keys(),
1132 case_sensitive=False)
1134 def _UnlockedGetInstanceInfo(self, instance_name):
1135 """Returns information about an instance.
1137 This function is for internal use, when the config lock is already held.
1140 if instance_name not in self._config_data.instances:
1143 return self._config_data.instances[instance_name]
1145 @locking.ssynchronized(_config_lock, shared=1)
1146 def GetInstanceInfo(self, instance_name):
1147 """Returns information about an instance.
1149 It takes the information from the configuration file. Other information of
1150 an instance are taken from the live systems.
1152 @param instance_name: name of the instance, e.g.
1153 I{instance1.example.com}
1155 @rtype: L{objects.Instance}
1156 @return: the instance object
1159 return self._UnlockedGetInstanceInfo(instance_name)
1161 @locking.ssynchronized(_config_lock, shared=1)
1162 def GetAllInstancesInfo(self):
1163 """Get the configuration of all instances.
1166 @return: dict of (instance, instance_info), where instance_info is what
1167 would GetInstanceInfo return for the node
1170 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1171 for instance in self._UnlockedGetInstanceList()])
1174 @locking.ssynchronized(_config_lock)
1175 def AddNode(self, node, ec_id):
1176 """Add a node to the configuration.
1178 @type node: L{objects.Node}
1179 @param node: a Node instance
1182 logging.info("Adding node %s to configuration", node.name)
1184 self._EnsureUUID(node, ec_id)
1187 node.ctime = node.mtime = time.time()
1188 self._UnlockedAddNodeToGroup(node.name, node.group)
1189 self._config_data.nodes[node.name] = node
1190 self._config_data.cluster.serial_no += 1
1193 @locking.ssynchronized(_config_lock)
1194 def RemoveNode(self, node_name):
1195 """Remove a node from the configuration.
1198 logging.info("Removing node %s from configuration", node_name)
1200 if node_name not in self._config_data.nodes:
1201 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1203 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1204 del self._config_data.nodes[node_name]
1205 self._config_data.cluster.serial_no += 1
1208 @locking.ssynchronized(_config_lock, shared=1)
1209 def ExpandNodeName(self, short_name):
1210 """Attempt to expand an incomplete instance name.
1213 return utils.MatchNameComponent(short_name,
1214 self._config_data.nodes.keys(),
1215 case_sensitive=False)
1217 def _UnlockedGetNodeInfo(self, node_name):
1218 """Get the configuration of a node, as stored in the config.
1220 This function is for internal use, when the config lock is already
1223 @param node_name: the node name, e.g. I{node1.example.com}
1225 @rtype: L{objects.Node}
1226 @return: the node object
1229 if node_name not in self._config_data.nodes:
1232 return self._config_data.nodes[node_name]
1234 @locking.ssynchronized(_config_lock, shared=1)
1235 def GetNodeInfo(self, node_name):
1236 """Get the configuration of a node, as stored in the config.
1238 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1240 @param node_name: the node name, e.g. I{node1.example.com}
1242 @rtype: L{objects.Node}
1243 @return: the node object
1246 return self._UnlockedGetNodeInfo(node_name)
1248 @locking.ssynchronized(_config_lock, shared=1)
1249 def GetNodeInstances(self, node_name):
1250 """Get the instances of a node, as stored in the config.
1252 @param node_name: the node name, e.g. I{node1.example.com}
1254 @rtype: (list, list)
1255 @return: a tuple with two lists: the primary and the secondary instances
1260 for inst in self._config_data.instances.values():
1261 if inst.primary_node == node_name:
1262 pri.append(inst.name)
1263 if node_name in inst.secondary_nodes:
1264 sec.append(inst.name)
1267 def _UnlockedGetNodeList(self):
1268 """Return the list of nodes which are in the configuration.
1270 This function is for internal use, when the config lock is already
1276 return self._config_data.nodes.keys()
1278 @locking.ssynchronized(_config_lock, shared=1)
1279 def GetNodeList(self):
1280 """Return the list of nodes which are in the configuration.
1283 return self._UnlockedGetNodeList()
1285 def _UnlockedGetOnlineNodeList(self):
1286 """Return the list of nodes which are online.
1289 all_nodes = [self._UnlockedGetNodeInfo(node)
1290 for node in self._UnlockedGetNodeList()]
1291 return [node.name for node in all_nodes if not node.offline]
1293 @locking.ssynchronized(_config_lock, shared=1)
1294 def GetOnlineNodeList(self):
1295 """Return the list of nodes which are online.
1298 return self._UnlockedGetOnlineNodeList()
1300 @locking.ssynchronized(_config_lock, shared=1)
1301 def GetNonVmCapableNodeList(self):
1302 """Return the list of nodes which are not vm capable.
1305 all_nodes = [self._UnlockedGetNodeInfo(node)
1306 for node in self._UnlockedGetNodeList()]
1307 return [node.name for node in all_nodes if not node.vm_capable]
1309 @locking.ssynchronized(_config_lock, shared=1)
1310 def GetAllNodesInfo(self):
1311 """Get the configuration of all nodes.
1314 @return: dict of (node, node_info), where node_info is what
1315 would GetNodeInfo return for the node
1318 my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
1319 for node in self._UnlockedGetNodeList()])
1322 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1323 """Get the number of current and maximum desired and possible candidates.
1325 @type exceptions: list
1326 @param exceptions: if passed, list of nodes that should be ignored
1328 @return: tuple of (current, desired and possible, possible)
1331 mc_now = mc_should = mc_max = 0
1332 for node in self._config_data.nodes.values():
1333 if exceptions and node.name in exceptions:
1335 if not (node.offline or node.drained) and node.master_capable:
1337 if node.master_candidate:
1339 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1340 return (mc_now, mc_should, mc_max)
1342 @locking.ssynchronized(_config_lock, shared=1)
1343 def GetMasterCandidateStats(self, exceptions=None):
1344 """Get the number of current and maximum possible candidates.
1346 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1348 @type exceptions: list
1349 @param exceptions: if passed, list of nodes that should be ignored
1351 @return: tuple of (current, max)
1354 return self._UnlockedGetMasterCandidateStats(exceptions)
1356 @locking.ssynchronized(_config_lock)
1357 def MaintainCandidatePool(self, exceptions):
1358 """Try to grow the candidate pool to the desired size.
1360 @type exceptions: list
1361 @param exceptions: if passed, list of nodes that should be ignored
1363 @return: list with the adjusted nodes (L{objects.Node} instances)
1366 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1369 node_list = self._config_data.nodes.keys()
1370 random.shuffle(node_list)
1371 for name in node_list:
1372 if mc_now >= mc_max:
1374 node = self._config_data.nodes[name]
1375 if (node.master_candidate or node.offline or node.drained or
1376 node.name in exceptions or not node.master_capable):
1378 mod_list.append(node)
1379 node.master_candidate = True
1382 if mc_now != mc_max:
1383 # this should not happen
1384 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1385 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1387 self._config_data.cluster.serial_no += 1
1392 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1393 """Add a given node to the specified group.
1396 if nodegroup_uuid not in self._config_data.nodegroups:
1397 # This can happen if a node group gets deleted between its lookup and
1398 # when we're adding the first node to it, since we don't keep a lock in
1399 # the meantime. It's ok though, as we'll fail cleanly if the node group
1400 # is not found anymore.
1401 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1402 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1403 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1405 def _UnlockedRemoveNodeFromGroup(self, node):
1406 """Remove a given node from its group.
1409 nodegroup = node.group
1410 if nodegroup not in self._config_data.nodegroups:
1411 logging.warning("Warning: node '%s' has unknown node group '%s'"
1412 " (while being removed from it)", node.name, nodegroup)
1413 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1414 if node.name not in nodegroup_obj.members:
1415 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1416 " (while being removed from it)", node.name, nodegroup)
1418 nodegroup_obj.members.remove(node.name)
1420 def _BumpSerialNo(self):
1421 """Bump up the serial number of the config.
1424 self._config_data.serial_no += 1
1425 self._config_data.mtime = time.time()
1427 def _AllUUIDObjects(self):
1428 """Returns all objects with uuid attributes.
1431 return (self._config_data.instances.values() +
1432 self._config_data.nodes.values() +
1433 self._config_data.nodegroups.values() +
1434 [self._config_data.cluster])
1436 def _OpenConfig(self, accept_foreign):
1437 """Read the config data from disk.
1440 raw_data = utils.ReadFile(self._cfg_file)
1443 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1444 except Exception, err:
1445 raise errors.ConfigurationError(err)
1447 # Make sure the configuration has the right version
1448 _ValidateConfig(data)
1450 if (not hasattr(data, 'cluster') or
1451 not hasattr(data.cluster, 'rsahostkeypub')):
1452 raise errors.ConfigurationError("Incomplete configuration"
1453 " (missing cluster.rsahostkeypub)")
1455 if data.cluster.master_node != self._my_hostname and not accept_foreign:
1456 msg = ("The configuration denotes node %s as master, while my"
1457 " hostname is %s; opening a foreign configuration is only"
1458 " possible in accept_foreign mode" %
1459 (data.cluster.master_node, self._my_hostname))
1460 raise errors.ConfigurationError(msg)
1462 # Upgrade configuration if needed
1463 data.UpgradeConfig()
1465 self._config_data = data
1466 # reset the last serial as -1 so that the next write will cause
1468 self._last_cluster_serial = -1
1470 # And finally run our (custom) config upgrade sequence
1471 self._UpgradeConfig()
1473 self._cfg_id = utils.GetFileID(path=self._cfg_file)
1475 def _UpgradeConfig(self):
1476 """Run upgrade steps that cannot be done purely in the objects.
1478 This is because some data elements need uniqueness across the
1479 whole configuration, etc.
1481 @warning: this function will call L{_WriteConfig()}, but also
1482 L{DropECReservations} so it needs to be called only from a
1483 "safe" place (the constructor). If one wanted to call it with
1484 the lock held, a DropECReservationUnlocked would need to be
1485 created first, to avoid causing deadlock.
1489 for item in self._AllUUIDObjects():
1490 if item.uuid is None:
1491 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1493 if not self._config_data.nodegroups:
1494 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
1495 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
1497 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
1499 for node in self._config_data.nodes.values():
1501 node.group = self.LookupNodeGroup(None)
1503 # This is technically *not* an upgrade, but needs to be done both when
1504 # nodegroups are being added, and upon normally loading the config,
1505 # because the members list of a node group is discarded upon
1506 # serializing/deserializing the object.
1507 self._UnlockedAddNodeToGroup(node.name, node.group)
1510 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
1511 # only called at config init time, without the lock held
1512 self.DropECReservations(_UPGRADE_CONFIG_JID)
1514 def _DistributeConfig(self, feedback_fn):
1515 """Distribute the configuration to the other nodes.
1517 Currently, this only copies the configuration file. In the future,
1518 it could be used to encapsulate the 2/3-phase update mechanism.
1528 myhostname = self._my_hostname
1529 # we can skip checking whether _UnlockedGetNodeInfo returns None
1530 # since the node list comes from _UnlocketGetNodeList, and we are
1531 # called with the lock held, so no modifications should take place
1533 for node_name in self._UnlockedGetNodeList():
1534 if node_name == myhostname:
1536 node_info = self._UnlockedGetNodeInfo(node_name)
1537 if not node_info.master_candidate:
1539 node_list.append(node_info.name)
1540 addr_list.append(node_info.primary_ip)
1542 result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1543 address_list=addr_list)
1544 for to_node, to_result in result.items():
1545 msg = to_result.fail_msg
1547 msg = ("Copy of file %s to node %s failed: %s" %
1548 (self._cfg_file, to_node, msg))
1558 def _WriteConfig(self, destination=None, feedback_fn=None):
1559 """Write the configuration data to persistent storage.
1562 assert feedback_fn is None or callable(feedback_fn)
1564 # Warn on config errors, but don't abort the save - the
1565 # configuration has already been modified, and we can't revert;
1566 # the best we can do is to warn the user and save as is, leaving
1567 # recovery to the user
1568 config_errors = self._UnlockedVerifyConfig()
1570 errmsg = ("Configuration data is not consistent: %s" %
1571 (utils.CommaJoin(config_errors)))
1572 logging.critical(errmsg)
1576 if destination is None:
1577 destination = self._cfg_file
1578 self._BumpSerialNo()
1579 txt = serializer.Dump(self._config_data.ToDict())
1581 getents = self._getents()
1583 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
1584 close=False, gid=getents.confd_gid, mode=0640)
1585 except errors.LockError:
1586 raise errors.ConfigurationError("The configuration file has been"
1587 " modified since the last write, cannot"
1590 self._cfg_id = utils.GetFileID(fd=fd)
1594 self.write_count += 1
1596 # and redistribute the config file to master candidates
1597 self._DistributeConfig(feedback_fn)
1599 # Write ssconf files on all nodes (including locally)
1600 if self._last_cluster_serial < self._config_data.cluster.serial_no:
1601 if not self._offline:
1602 result = rpc.RpcRunner.call_write_ssconf_files(
1603 self._UnlockedGetOnlineNodeList(),
1604 self._UnlockedGetSsconfValues())
1606 for nname, nresu in result.items():
1607 msg = nresu.fail_msg
1609 errmsg = ("Error while uploading ssconf files to"
1610 " node %s: %s" % (nname, msg))
1611 logging.warning(errmsg)
1616 self._last_cluster_serial = self._config_data.cluster.serial_no
1618 def _UnlockedGetSsconfValues(self):
1619 """Return the values needed by ssconf.
1622 @return: a dictionary with keys the ssconf names and values their
1627 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1628 node_names = utils.NiceSort(self._UnlockedGetNodeList())
1629 node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1630 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
1631 for ninfo in node_info]
1632 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
1633 for ninfo in node_info]
1635 instance_data = fn(instance_names)
1636 off_data = fn(node.name for node in node_info if node.offline)
1637 on_data = fn(node.name for node in node_info if not node.offline)
1638 mc_data = fn(node.name for node in node_info if node.master_candidate)
1639 mc_ips_data = fn(node.primary_ip for node in node_info
1640 if node.master_candidate)
1641 node_data = fn(node_names)
1642 node_pri_ips_data = fn(node_pri_ips)
1643 node_snd_ips_data = fn(node_snd_ips)
1645 cluster = self._config_data.cluster
1646 cluster_tags = fn(cluster.GetTags())
1648 hypervisor_list = fn(cluster.enabled_hypervisors)
1650 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
1652 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
1653 self._config_data.nodegroups.values()]
1654 nodegroups_data = fn(utils.NiceSort(nodegroups))
1657 constants.SS_CLUSTER_NAME: cluster.cluster_name,
1658 constants.SS_CLUSTER_TAGS: cluster_tags,
1659 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1660 constants.SS_MASTER_CANDIDATES: mc_data,
1661 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
1662 constants.SS_MASTER_IP: cluster.master_ip,
1663 constants.SS_MASTER_NETDEV: cluster.master_netdev,
1664 constants.SS_MASTER_NODE: cluster.master_node,
1665 constants.SS_NODE_LIST: node_data,
1666 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
1667 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
1668 constants.SS_OFFLINE_NODES: off_data,
1669 constants.SS_ONLINE_NODES: on_data,
1670 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
1671 constants.SS_INSTANCE_LIST: instance_data,
1672 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1673 constants.SS_HYPERVISOR_LIST: hypervisor_list,
1674 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
1675 constants.SS_UID_POOL: uid_pool,
1676 constants.SS_NODEGROUPS: nodegroups_data,
1679 @locking.ssynchronized(_config_lock, shared=1)
1680 def GetSsconfValues(self):
1681 """Wrapper using lock around _UnlockedGetSsconf().
1684 return self._UnlockedGetSsconfValues()
1686 @locking.ssynchronized(_config_lock, shared=1)
1687 def GetVGName(self):
1688 """Return the volume group name.
1691 return self._config_data.cluster.volume_group_name
1693 @locking.ssynchronized(_config_lock)
1694 def SetVGName(self, vg_name):
1695 """Set the volume group name.
1698 self._config_data.cluster.volume_group_name = vg_name
1699 self._config_data.cluster.serial_no += 1
1702 @locking.ssynchronized(_config_lock, shared=1)
1703 def GetDRBDHelper(self):
1704 """Return DRBD usermode helper.
1707 return self._config_data.cluster.drbd_usermode_helper
1709 @locking.ssynchronized(_config_lock)
1710 def SetDRBDHelper(self, drbd_helper):
1711 """Set DRBD usermode helper.
1714 self._config_data.cluster.drbd_usermode_helper = drbd_helper
1715 self._config_data.cluster.serial_no += 1
1718 @locking.ssynchronized(_config_lock, shared=1)
1719 def GetMACPrefix(self):
1720 """Return the mac prefix.
1723 return self._config_data.cluster.mac_prefix
1725 @locking.ssynchronized(_config_lock, shared=1)
1726 def GetClusterInfo(self):
1727 """Returns information about the cluster
1729 @rtype: L{objects.Cluster}
1730 @return: the cluster object
1733 return self._config_data.cluster
1735 @locking.ssynchronized(_config_lock, shared=1)
1736 def HasAnyDiskOfType(self, dev_type):
1737 """Check if in there is at disk of the given type in the configuration.
1740 return self._config_data.HasAnyDiskOfType(dev_type)
1742 @locking.ssynchronized(_config_lock)
1743 def Update(self, target, feedback_fn):
1744 """Notify function to be called after updates.
1746 This function must be called when an object (as returned by
1747 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
1748 caller wants the modifications saved to the backing store. Note
1749 that all modified objects will be saved, but the target argument
1750 is the one the caller wants to ensure that it's saved.
1752 @param target: an instance of either L{objects.Cluster},
1753 L{objects.Node} or L{objects.Instance} which is existing in
1755 @param feedback_fn: Callable feedback function
1758 if self._config_data is None:
1759 raise errors.ProgrammerError("Configuration file not read,"
1761 update_serial = False
1762 if isinstance(target, objects.Cluster):
1763 test = target == self._config_data.cluster
1764 elif isinstance(target, objects.Node):
1765 test = target in self._config_data.nodes.values()
1766 update_serial = True
1767 elif isinstance(target, objects.Instance):
1768 test = target in self._config_data.instances.values()
1769 elif isinstance(target, objects.NodeGroup):
1770 test = target in self._config_data.nodegroups.values()
1772 raise errors.ProgrammerError("Invalid object type (%s) passed to"
1773 " ConfigWriter.Update" % type(target))
1775 raise errors.ConfigurationError("Configuration updated since object"
1776 " has been read or unknown object")
1777 target.serial_no += 1
1778 target.mtime = now = time.time()
1781 # for node updates, we need to increase the cluster serial too
1782 self._config_data.cluster.serial_no += 1
1783 self._config_data.cluster.mtime = now
1785 if isinstance(target, objects.Instance):
1786 self._UnlockedReleaseDRBDMinors(target.name)
1788 self._WriteConfig(feedback_fn=feedback_fn)
1790 @locking.ssynchronized(_config_lock)
1791 def DropECReservations(self, ec_id):
1792 """Drop per-execution-context reservations
1795 for rm in self._all_rms:
1796 rm.DropECReservations(ec_id)