4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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=R0904
35 # R0904: Too many public methods
44 from ganeti import errors
45 from ganeti import locking
46 from ganeti import utils
47 from ganeti import constants
48 from ganeti import rpc
49 from ganeti import objects
50 from ganeti import serializer
51 from ganeti import uidpool
52 from ganeti import netutils
53 from ganeti import runtime
54 from ganeti import pathutils
55 from ganeti import network
58 _config_lock = locking.SharedLock("ConfigWriter")
60 # job id used for resource management at config upgrade time
61 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
64 def _ValidateConfig(data):
65 """Verifies that a configuration objects looks valid.
67 This only verifies the version of the configuration.
69 @raise errors.ConfigurationError: if the version differs from what
73 if data.version != constants.CONFIG_VERSION:
74 raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
77 class TemporaryReservationManager:
78 """A temporary resource reservation manager.
80 This is used to reserve resources in a job, before using them, making sure
81 other jobs cannot get them in the meantime.
85 self._ec_reserved = {}
87 def Reserved(self, resource):
88 for holder_reserved in self._ec_reserved.values():
89 if resource in holder_reserved:
93 def Reserve(self, ec_id, resource):
94 if self.Reserved(resource):
95 raise errors.ReservationError("Duplicate reservation for resource '%s'"
97 if ec_id not in self._ec_reserved:
98 self._ec_reserved[ec_id] = set([resource])
100 self._ec_reserved[ec_id].add(resource)
102 def DropECReservations(self, ec_id):
103 if ec_id in self._ec_reserved:
104 del self._ec_reserved[ec_id]
106 def GetReserved(self):
108 for holder_reserved in self._ec_reserved.values():
109 all_reserved.update(holder_reserved)
112 def GetECReserved(self, ec_id):
113 """ Used when you want to retrieve all reservations for a specific
114 execution context. E.g when commiting reserved IPs for a specific
119 if ec_id in self._ec_reserved:
120 ec_reserved.update(self._ec_reserved[ec_id])
123 def Generate(self, existing, generate_one_fn, ec_id):
124 """Generate a new resource of this type
127 assert callable(generate_one_fn)
129 all_elems = self.GetReserved()
130 all_elems.update(existing)
133 new_resource = generate_one_fn()
134 if new_resource is not None and new_resource not in all_elems:
137 raise errors.ConfigurationError("Not able generate new resource"
138 " (last tried: %s)" % new_resource)
139 self.Reserve(ec_id, new_resource)
143 def _MatchNameComponentIgnoreCase(short_name, names):
144 """Wrapper around L{utils.text.MatchNameComponent}.
147 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
150 def _CheckInstanceDiskIvNames(disks):
151 """Checks if instance's disks' C{iv_name} attributes are in order.
153 @type disks: list of L{objects.Disk}
154 @param disks: List of disks
155 @rtype: list of tuples; (int, string, string)
156 @return: List of wrongly named disks, each tuple contains disk index,
157 expected and actual name
162 for (idx, disk) in enumerate(disks):
163 exp_iv_name = "disk/%s" % idx
164 if disk.iv_name != exp_iv_name:
165 result.append((idx, exp_iv_name, disk.iv_name))
171 """The interface to the cluster configuration.
173 @ivar _temporary_lvs: reservation manager for temporary LVs
174 @ivar _all_rms: a list of all temporary reservation managers
177 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
178 accept_foreign=False):
180 self._lock = _config_lock
181 self._config_data = None
182 self._offline = offline
184 self._cfg_file = pathutils.CLUSTER_CONF_FILE
186 self._cfg_file = cfg_file
187 self._getents = _getents
188 self._temporary_ids = TemporaryReservationManager()
189 self._temporary_drbds = {}
190 self._temporary_macs = TemporaryReservationManager()
191 self._temporary_secrets = TemporaryReservationManager()
192 self._temporary_lvs = TemporaryReservationManager()
193 self._temporary_ips = TemporaryReservationManager()
194 self._all_rms = [self._temporary_ids, self._temporary_macs,
195 self._temporary_secrets, self._temporary_lvs,
197 # Note: in order to prevent errors when resolving our name in
198 # _DistributeConfig, we compute it here once and reuse it; it's
199 # better to raise an error before starting to modify the config
200 # file than after it was modified
201 self._my_hostname = netutils.Hostname.GetSysName()
202 self._last_cluster_serial = -1
205 self._OpenConfig(accept_foreign)
207 def _GetRpc(self, address_list):
208 """Returns RPC runner for configuration.
211 return rpc.ConfigRunner(self._context, address_list)
213 def SetContext(self, context):
214 """Sets Ganeti context.
217 self._context = context
219 # this method needs to be static, so that we can call it on the class
222 """Check if the cluster is configured.
225 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
227 @locking.ssynchronized(_config_lock, shared=1)
228 def GetNdParams(self, node):
229 """Get the node params populated with cluster defaults.
231 @type node: L{objects.Node}
232 @param node: The node we want to know the params for
233 @return: A dict with the filled in node params
236 nodegroup = self._UnlockedGetNodeGroup(node.group)
237 return self._config_data.cluster.FillND(node, nodegroup)
239 @locking.ssynchronized(_config_lock, shared=1)
240 def GetInstanceDiskParams(self, instance):
241 """Get the disk params populated with inherit chain.
243 @type instance: L{objects.Instance}
244 @param instance: The instance we want to know the params for
245 @return: A dict with the filled in disk params
248 node = self._UnlockedGetNodeInfo(instance.primary_node)
249 nodegroup = self._UnlockedGetNodeGroup(node.group)
250 return self._UnlockedGetGroupDiskParams(nodegroup)
252 @locking.ssynchronized(_config_lock, shared=1)
253 def GetGroupDiskParams(self, group):
254 """Get the disk params populated with inherit chain.
256 @type group: L{objects.NodeGroup}
257 @param group: The group we want to know the params for
258 @return: A dict with the filled in disk params
261 return self._UnlockedGetGroupDiskParams(group)
263 def _UnlockedGetGroupDiskParams(self, group):
264 """Get the disk params populated with inherit chain down to node-group.
266 @type group: L{objects.NodeGroup}
267 @param group: The group we want to know the params for
268 @return: A dict with the filled in disk params
271 return self._config_data.cluster.SimpleFillDP(group.diskparams)
273 def _UnlockedGetNetworkMACPrefix(self, net_uuid):
274 """Return the network mac prefix if it exists or the cluster level default.
279 nobj = self._UnlockedGetNetwork(net_uuid)
281 prefix = nobj.mac_prefix
285 def _GenerateOneMAC(self, prefix=None):
286 """Return a function that randomly generates a MAC suffic
287 and appends it to the given prefix. If prefix is not given get
288 the cluster level default.
292 prefix = self._config_data.cluster.mac_prefix
295 byte1 = random.randrange(0, 256)
296 byte2 = random.randrange(0, 256)
297 byte3 = random.randrange(0, 256)
298 mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
303 @locking.ssynchronized(_config_lock, shared=1)
304 def GenerateMAC(self, net_uuid, ec_id):
305 """Generate a MAC for an instance.
307 This should check the current instances for duplicates.
310 existing = self._AllMACs()
311 prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
312 gen_mac = self._GenerateOneMAC(prefix)
313 return self._temporary_ids.Generate(existing, gen_mac, ec_id)
315 @locking.ssynchronized(_config_lock, shared=1)
316 def ReserveMAC(self, mac, ec_id):
317 """Reserve a MAC for an instance.
319 This only checks instances managed by this cluster, it does not
320 check for potential collisions elsewhere.
323 all_macs = self._AllMACs()
325 raise errors.ReservationError("mac already in use")
327 self._temporary_macs.Reserve(ec_id, mac)
329 def _UnlockedCommitTemporaryIps(self, ec_id):
330 """Commit all reserved IP address to their respective pools
333 for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
334 self._UnlockedCommitIp(action, net_uuid, address)
336 def _UnlockedCommitIp(self, action, net_uuid, address):
337 """Commit a reserved IP address to an IP pool.
339 The IP address is taken from the network's IP pool and marked as reserved.
342 nobj = self._UnlockedGetNetwork(net_uuid)
343 pool = network.AddressPool(nobj)
344 if action == constants.RESERVE_ACTION:
345 pool.Reserve(address)
346 elif action == constants.RELEASE_ACTION:
347 pool.Release(address)
349 def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
350 """Give a specific IP address back to an IP pool.
352 The IP address is returned to the IP pool designated by pool_id and marked
356 self._temporary_ips.Reserve(ec_id,
357 (constants.RELEASE_ACTION, address, net_uuid))
359 @locking.ssynchronized(_config_lock, shared=1)
360 def ReleaseIp(self, net_uuid, address, ec_id):
361 """Give a specified IP address back to an IP pool.
363 This is just a wrapper around _UnlockedReleaseIp.
367 self._UnlockedReleaseIp(net_uuid, address, ec_id)
369 @locking.ssynchronized(_config_lock, shared=1)
370 def GenerateIp(self, net_uuid, ec_id):
371 """Find a free IPv4 address for an instance.
374 nobj = self._UnlockedGetNetwork(net_uuid)
375 pool = network.AddressPool(nobj)
379 ip = pool.GenerateFree()
380 except errors.AddressPoolError:
381 raise errors.ReservationError("Cannot generate IP. Network is full")
382 return (constants.RESERVE_ACTION, ip, net_uuid)
384 _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
387 def _UnlockedReserveIp(self, net_uuid, address, ec_id):
388 """Reserve a given IPv4 address for use by an instance.
391 nobj = self._UnlockedGetNetwork(net_uuid)
392 pool = network.AddressPool(nobj)
394 isreserved = pool.IsReserved(address)
395 except errors.AddressPoolError:
396 raise errors.ReservationError("IP address not in network")
398 raise errors.ReservationError("IP address already in use")
400 return self._temporary_ips.Reserve(ec_id,
401 (constants.RESERVE_ACTION,
404 @locking.ssynchronized(_config_lock, shared=1)
405 def ReserveIp(self, net_uuid, address, ec_id):
406 """Reserve a given IPv4 address for use by an instance.
410 return self._UnlockedReserveIp(net_uuid, address, ec_id)
412 @locking.ssynchronized(_config_lock, shared=1)
413 def ReserveLV(self, lv_name, ec_id):
414 """Reserve an VG/LV pair for an instance.
416 @type lv_name: string
417 @param lv_name: the logical volume name to reserve
420 all_lvs = self._AllLVs()
421 if lv_name in all_lvs:
422 raise errors.ReservationError("LV already in use")
424 self._temporary_lvs.Reserve(ec_id, lv_name)
426 @locking.ssynchronized(_config_lock, shared=1)
427 def GenerateDRBDSecret(self, ec_id):
428 """Generate a DRBD secret.
430 This checks the current disks for duplicates.
433 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
434 utils.GenerateSecret,
438 """Compute the list of all LVs.
442 for instance in self._config_data.instances.values():
443 node_data = instance.MapLVsByNode()
444 for lv_list in node_data.values():
445 lvnames.update(lv_list)
449 """Compute the list of all Disks.
453 for instance in self._config_data.instances.values():
454 disks.extend(instance.disks)
458 """Compute the list of all NICs.
462 for instance in self._config_data.instances.values():
463 nics.extend(instance.nics)
466 def _AllIDs(self, include_temporary):
467 """Compute the list of all UUIDs and names we have.
469 @type include_temporary: boolean
470 @param include_temporary: whether to include the _temporary_ids set
472 @return: a set of IDs
476 if include_temporary:
477 existing.update(self._temporary_ids.GetReserved())
478 existing.update(self._AllLVs())
479 existing.update(self._config_data.instances.keys())
480 existing.update(self._config_data.nodes.keys())
481 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
484 def _GenerateUniqueID(self, ec_id):
485 """Generate an unique UUID.
487 This checks the current node, instances and disk names for
491 @return: the unique id
494 existing = self._AllIDs(include_temporary=False)
495 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
497 @locking.ssynchronized(_config_lock, shared=1)
498 def GenerateUniqueID(self, ec_id):
499 """Generate an unique ID.
501 This is just a wrapper over the unlocked version.
504 @param ec_id: unique id for the job to reserve the id to
507 return self._GenerateUniqueID(ec_id)
510 """Return all MACs present in the config.
513 @return: the list of all MACs
517 for instance in self._config_data.instances.values():
518 for nic in instance.nics:
519 result.append(nic.mac)
523 def _AllDRBDSecrets(self):
524 """Return all DRBD secrets present in the config.
527 @return: the list of all DRBD secrets
530 def helper(disk, result):
531 """Recursively gather secrets from this disk."""
532 if disk.dev_type == constants.DT_DRBD8:
533 result.append(disk.logical_id[5])
535 for child in disk.children:
536 helper(child, result)
539 for instance in self._config_data.instances.values():
540 for disk in instance.disks:
545 def _CheckDiskIDs(self, disk, l_ids, p_ids):
546 """Compute duplicate disk IDs
548 @type disk: L{objects.Disk}
549 @param disk: the disk at which to start searching
551 @param l_ids: list of current logical ids
553 @param p_ids: list of current physical ids
555 @return: a list of error messages
559 if disk.logical_id is not None:
560 if disk.logical_id in l_ids:
561 result.append("duplicate logical id %s" % str(disk.logical_id))
563 l_ids.append(disk.logical_id)
564 if disk.physical_id is not None:
565 if disk.physical_id in p_ids:
566 result.append("duplicate physical id %s" % str(disk.physical_id))
568 p_ids.append(disk.physical_id)
571 for child in disk.children:
572 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
575 def _UnlockedVerifyConfig(self):
579 @return: a list of error messages; a non-empty list signifies
583 # pylint: disable=R0914
587 data = self._config_data
588 cluster = data.cluster
592 # global cluster checks
593 if not cluster.enabled_hypervisors:
594 result.append("enabled hypervisors list doesn't have any entries")
595 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
597 result.append("enabled hypervisors contains invalid entries: %s" %
598 utils.CommaJoin(invalid_hvs))
599 missing_hvp = (set(cluster.enabled_hypervisors) -
600 set(cluster.hvparams.keys()))
602 result.append("hypervisor parameters missing for the enabled"
603 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
605 if not cluster.enabled_disk_templates:
606 result.append("enabled disk templates list doesn't have any entries")
607 invalid_disk_templates = set(cluster.enabled_disk_templates) \
608 - constants.DISK_TEMPLATES
609 if invalid_disk_templates:
610 result.append("enabled disk templates list contains invalid entries:"
611 " %s" % utils.CommaJoin(invalid_disk_templates))
613 if cluster.master_node not in data.nodes:
614 result.append("cluster has invalid primary node '%s'" %
617 def _helper(owner, attr, value, template):
619 utils.ForceDictType(value, template)
620 except errors.GenericError, err:
621 result.append("%s has invalid %s: %s" % (owner, attr, err))
623 def _helper_nic(owner, params):
625 objects.NIC.CheckParameterSyntax(params)
626 except errors.ConfigurationError, err:
627 result.append("%s has invalid nicparams: %s" % (owner, err))
629 def _helper_ipolicy(owner, ipolicy, iscluster):
631 objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
632 except errors.ConfigurationError, err:
633 result.append("%s has invalid instance policy: %s" % (owner, err))
634 for key, value in ipolicy.items():
635 if key == constants.ISPECS_MINMAX:
636 for k in range(len(value)):
637 _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
638 elif key == constants.ISPECS_STD:
639 _helper(owner, "ipolicy/" + key, value,
640 constants.ISPECS_PARAMETER_TYPES)
642 # FIXME: assuming list type
643 if key in constants.IPOLICY_PARAMETERS:
647 if not isinstance(value, exp_type):
648 result.append("%s has invalid instance policy: for %s,"
649 " expecting %s, got %s" %
650 (owner, key, exp_type.__name__, type(value)))
652 def _helper_ispecs(owner, parentkey, params):
653 for (key, value) in params.items():
654 fullkey = "/".join([parentkey, key])
655 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
657 # check cluster parameters
658 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
659 constants.BES_PARAMETER_TYPES)
660 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
661 constants.NICS_PARAMETER_TYPES)
662 _helper_nic("cluster", cluster.SimpleFillNIC({}))
663 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
664 constants.NDS_PARAMETER_TYPES)
665 _helper_ipolicy("cluster", cluster.ipolicy, True)
667 # per-instance checks
668 for instance_name in data.instances:
669 instance = data.instances[instance_name]
670 if instance.name != instance_name:
671 result.append("instance '%s' is indexed by wrong name '%s'" %
672 (instance.name, instance_name))
673 if instance.primary_node not in data.nodes:
674 result.append("instance '%s' has invalid primary node '%s'" %
675 (instance_name, instance.primary_node))
676 for snode in instance.secondary_nodes:
677 if snode not in data.nodes:
678 result.append("instance '%s' has invalid secondary node '%s'" %
679 (instance_name, snode))
680 for idx, nic in enumerate(instance.nics):
681 if nic.mac in seen_macs:
682 result.append("instance '%s' has NIC %d mac %s duplicate" %
683 (instance_name, idx, nic.mac))
685 seen_macs.append(nic.mac)
687 filled = cluster.SimpleFillNIC(nic.nicparams)
688 owner = "instance %s nic %d" % (instance.name, idx)
689 _helper(owner, "nicparams",
690 filled, constants.NICS_PARAMETER_TYPES)
691 _helper_nic(owner, filled)
693 # disk template checks
694 if not instance.disk_template in data.cluster.enabled_disk_templates:
695 result.append("instance '%s' uses the disabled disk template '%s'." %
696 (instance_name, instance.disk_template))
699 if instance.beparams:
700 _helper("instance %s" % instance.name, "beparams",
701 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
703 # gather the drbd ports for duplicate checks
704 for (idx, dsk) in enumerate(instance.disks):
705 if dsk.dev_type in constants.LDS_DRBD:
706 tcp_port = dsk.logical_id[2]
707 if tcp_port not in ports:
709 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
710 # gather network port reservation
711 net_port = getattr(instance, "network_port", None)
712 if net_port is not None:
713 if net_port not in ports:
715 ports[net_port].append((instance.name, "network port"))
717 # instance disk verify
718 for idx, disk in enumerate(instance.disks):
719 result.extend(["instance '%s' disk %d error: %s" %
720 (instance.name, idx, msg) for msg in disk.Verify()])
721 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
723 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
725 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
726 (idx, exp_name, actual_name))
727 for (idx, exp_name, actual_name) in wrong_names)
729 result.append("Instance '%s' has wrongly named disks: %s" %
730 (instance.name, tmp))
732 # cluster-wide pool of free ports
733 for free_port in cluster.tcpudp_port_pool:
734 if free_port not in ports:
735 ports[free_port] = []
736 ports[free_port].append(("cluster", "port marked as free"))
738 # compute tcp/udp duplicate ports
744 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
745 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
747 # highest used tcp port check
749 if keys[-1] > cluster.highest_used_port:
750 result.append("Highest used port mismatch, saved %s, computed %s" %
751 (cluster.highest_used_port, keys[-1]))
753 if not data.nodes[cluster.master_node].master_candidate:
754 result.append("Master node is not a master candidate")
756 # master candidate checks
757 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
759 result.append("Not enough master candidates: actual %d, target %d" %
763 for node_uuid, node in data.nodes.items():
764 if node.uuid != node_uuid:
765 result.append("Node '%s' is indexed by wrong UUID '%s'" %
766 (node.name, node_uuid))
767 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
768 result.append("Node %s state is invalid: master_candidate=%s,"
769 " drain=%s, offline=%s" %
770 (node.name, node.master_candidate, node.drained,
772 if node.group not in data.nodegroups:
773 result.append("Node '%s' has invalid group '%s'" %
774 (node.name, node.group))
776 _helper("node %s" % node.name, "ndparams",
777 cluster.FillND(node, data.nodegroups[node.group]),
778 constants.NDS_PARAMETER_TYPES)
779 used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
781 result.append("Node '%s' has some global parameters set: %s" %
782 (node.name, utils.CommaJoin(used_globals)))
785 nodegroups_names = set()
786 for nodegroup_uuid in data.nodegroups:
787 nodegroup = data.nodegroups[nodegroup_uuid]
788 if nodegroup.uuid != nodegroup_uuid:
789 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
790 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
791 if utils.UUID_RE.match(nodegroup.name.lower()):
792 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
793 (nodegroup.name, nodegroup.uuid))
794 if nodegroup.name in nodegroups_names:
795 result.append("duplicate node group name '%s'" % nodegroup.name)
797 nodegroups_names.add(nodegroup.name)
798 group_name = "group %s" % nodegroup.name
799 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
801 if nodegroup.ndparams:
802 _helper(group_name, "ndparams",
803 cluster.SimpleFillND(nodegroup.ndparams),
804 constants.NDS_PARAMETER_TYPES)
807 _, duplicates = self._UnlockedComputeDRBDMap()
808 for node, minor, instance_a, instance_b in duplicates:
809 result.append("DRBD minor %d on node %s is assigned twice to instances"
810 " %s and %s" % (minor, node, instance_a, instance_b))
813 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
816 def _AddIpAddress(ip, name):
817 ips.setdefault(ip, []).append(name)
819 _AddIpAddress(cluster.master_ip, "cluster_ip")
821 for node in data.nodes.values():
822 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
823 if node.secondary_ip != node.primary_ip:
824 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
826 for instance in data.instances.values():
827 for idx, nic in enumerate(instance.nics):
831 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
832 nic_mode = nicparams[constants.NIC_MODE]
833 nic_link = nicparams[constants.NIC_LINK]
835 if nic_mode == constants.NIC_MODE_BRIDGED:
836 link = "bridge:%s" % nic_link
837 elif nic_mode == constants.NIC_MODE_ROUTED:
838 link = "route:%s" % nic_link
840 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
842 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
843 "instance:%s/nic:%d" % (instance.name, idx))
845 for ip, owners in ips.items():
847 result.append("IP address %s is used by multiple owners: %s" %
848 (ip, utils.CommaJoin(owners)))
852 @locking.ssynchronized(_config_lock, shared=1)
853 def VerifyConfig(self):
856 This is just a wrapper over L{_UnlockedVerifyConfig}.
859 @return: a list of error messages; a non-empty list signifies
863 return self._UnlockedVerifyConfig()
865 def _UnlockedSetDiskID(self, disk, node_uuid):
866 """Convert the unique ID to the ID needed on the target nodes.
868 This is used only for drbd, which needs ip/port configuration.
870 The routine descends down and updates its children also, because
871 this helps when the only the top device is passed to the remote
874 This function is for internal use, when the config lock is already held.
878 for child in disk.children:
879 self._UnlockedSetDiskID(child, node_uuid)
881 if disk.logical_id is None and disk.physical_id is not None:
883 if disk.dev_type == constants.LD_DRBD8:
884 pnode, snode, port, pminor, sminor, secret = disk.logical_id
885 if node_uuid not in (pnode, snode):
886 raise errors.ConfigurationError("DRBD device not knowing node %s" %
888 pnode_info = self._UnlockedGetNodeInfo(pnode)
889 snode_info = self._UnlockedGetNodeInfo(snode)
890 if pnode_info is None or snode_info is None:
891 raise errors.ConfigurationError("Can't find primary or secondary node"
892 " for %s" % str(disk))
893 p_data = (pnode_info.secondary_ip, port)
894 s_data = (snode_info.secondary_ip, port)
895 if pnode == node_uuid:
896 disk.physical_id = p_data + s_data + (pminor, secret)
897 else: # it must be secondary, we tested above
898 disk.physical_id = s_data + p_data + (sminor, secret)
900 disk.physical_id = disk.logical_id
903 @locking.ssynchronized(_config_lock)
904 def SetDiskID(self, disk, node_uuid):
905 """Convert the unique ID to the ID needed on the target nodes.
907 This is used only for drbd, which needs ip/port configuration.
909 The routine descends down and updates its children also, because
910 this helps when the only the top device is passed to the remote
914 return self._UnlockedSetDiskID(disk, node_uuid)
916 @locking.ssynchronized(_config_lock)
917 def AddTcpUdpPort(self, port):
918 """Adds a new port to the available port pool.
920 @warning: this method does not "flush" the configuration (via
921 L{_WriteConfig}); callers should do that themselves once the
922 configuration is stable
925 if not isinstance(port, int):
926 raise errors.ProgrammerError("Invalid type passed for port")
928 self._config_data.cluster.tcpudp_port_pool.add(port)
930 @locking.ssynchronized(_config_lock, shared=1)
931 def GetPortList(self):
932 """Returns a copy of the current port list.
935 return self._config_data.cluster.tcpudp_port_pool.copy()
937 @locking.ssynchronized(_config_lock)
938 def AllocatePort(self):
941 The port will be taken from the available port pool or from the
942 default port range (and in this case we increase
946 # If there are TCP/IP ports configured, we use them first.
947 if self._config_data.cluster.tcpudp_port_pool:
948 port = self._config_data.cluster.tcpudp_port_pool.pop()
950 port = self._config_data.cluster.highest_used_port + 1
951 if port >= constants.LAST_DRBD_PORT:
952 raise errors.ConfigurationError("The highest used port is greater"
953 " than %s. Aborting." %
954 constants.LAST_DRBD_PORT)
955 self._config_data.cluster.highest_used_port = port
960 def _UnlockedComputeDRBDMap(self):
961 """Compute the used DRBD minor/nodes.
964 @return: dictionary of node_uuid: dict of minor: instance_name;
965 the returned dict will have all the nodes in it (even if with
966 an empty list), and a list of duplicates; if the duplicates
967 list is not empty, the configuration is corrupted and its caller
968 should raise an exception
971 def _AppendUsedPorts(get_node_name_fn, instance_name, disk, used):
973 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
974 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
975 for node_uuid, port in ((node_a, minor_a), (node_b, minor_b)):
976 assert node_uuid in used, \
977 ("Node '%s' of instance '%s' not found in node list" %
978 (get_node_name_fn(node_uuid), instance_name))
979 if port in used[node_uuid]:
980 duplicates.append((node_uuid, port, instance_name,
981 used[node_uuid][port]))
983 used[node_uuid][port] = instance_name
985 for child in disk.children:
986 duplicates.extend(_AppendUsedPorts(get_node_name_fn, instance_name,
991 my_dict = dict((node, {}) for node in self._config_data.nodes)
992 for instance in self._config_data.instances.itervalues():
993 for disk in instance.disks:
994 duplicates.extend(_AppendUsedPorts(self._UnlockedGetNodeName,
995 instance.name, disk, my_dict))
996 for (node, minor), instance in self._temporary_drbds.iteritems():
997 if minor in my_dict[node] and my_dict[node][minor] != instance:
998 duplicates.append((node, minor, instance, my_dict[node][minor]))
1000 my_dict[node][minor] = instance
1001 return my_dict, duplicates
1003 @locking.ssynchronized(_config_lock)
1004 def ComputeDRBDMap(self):
1005 """Compute the used DRBD minor/nodes.
1007 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
1009 @return: dictionary of node_uuid: dict of minor: instance_name;
1010 the returned dict will have all the nodes in it (even if with
1014 d_map, duplicates = self._UnlockedComputeDRBDMap()
1016 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1020 @locking.ssynchronized(_config_lock)
1021 def AllocateDRBDMinor(self, node_uuids, instance):
1022 """Allocate a drbd minor.
1024 The free minor will be automatically computed from the existing
1025 devices. A node can be given multiple times in order to allocate
1026 multiple minors. The result is the list of minors, in the same
1027 order as the passed nodes.
1029 @type instance: string
1030 @param instance: the instance for which we allocate minors
1033 assert isinstance(instance, basestring), \
1034 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
1036 d_map, duplicates = self._UnlockedComputeDRBDMap()
1038 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1041 for nuuid in node_uuids:
1042 ndata = d_map[nuuid]
1044 # no minors used, we can start at 0
1047 self._temporary_drbds[(nuuid, 0)] = instance
1051 ffree = utils.FirstFree(keys)
1053 # return the next minor
1054 # TODO: implement high-limit check
1055 minor = keys[-1] + 1
1058 # double-check minor against current instances
1059 assert minor not in d_map[nuuid], \
1060 ("Attempt to reuse allocated DRBD minor %d on node %s,"
1061 " already allocated to instance %s" %
1062 (minor, nuuid, d_map[nuuid][minor]))
1063 ndata[minor] = instance
1064 # double-check minor against reservation
1065 r_key = (nuuid, minor)
1066 assert r_key not in self._temporary_drbds, \
1067 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1068 " reserved for instance %s" %
1069 (minor, nuuid, self._temporary_drbds[r_key]))
1070 self._temporary_drbds[r_key] = instance
1071 result.append(minor)
1072 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1076 def _UnlockedReleaseDRBDMinors(self, instance):
1077 """Release temporary drbd minors allocated for a given instance.
1079 @type instance: string
1080 @param instance: the instance for which temporary minors should be
1084 assert isinstance(instance, basestring), \
1085 "Invalid argument passed to ReleaseDRBDMinors"
1086 for key, name in self._temporary_drbds.items():
1087 if name == instance:
1088 del self._temporary_drbds[key]
1090 @locking.ssynchronized(_config_lock)
1091 def ReleaseDRBDMinors(self, instance):
1092 """Release temporary drbd minors allocated for a given instance.
1094 This should be called on the error paths, on the success paths
1095 it's automatically called by the ConfigWriter add and update
1098 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1100 @type instance: string
1101 @param instance: the instance for which temporary minors should be
1105 self._UnlockedReleaseDRBDMinors(instance)
1107 @locking.ssynchronized(_config_lock, shared=1)
1108 def GetConfigVersion(self):
1109 """Get the configuration version.
1111 @return: Config version
1114 return self._config_data.version
1116 @locking.ssynchronized(_config_lock, shared=1)
1117 def GetClusterName(self):
1118 """Get cluster name.
1120 @return: Cluster name
1123 return self._config_data.cluster.cluster_name
1125 @locking.ssynchronized(_config_lock, shared=1)
1126 def GetMasterNode(self):
1127 """Get the UUID of the master node for this cluster.
1129 @return: Master node UUID
1132 return self._config_data.cluster.master_node
1134 @locking.ssynchronized(_config_lock, shared=1)
1135 def GetMasterNodeName(self):
1136 """Get the hostname of the master node for this cluster.
1138 @return: Master node hostname
1141 return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1143 @locking.ssynchronized(_config_lock, shared=1)
1144 def GetMasterIP(self):
1145 """Get the IP of the master node for this cluster.
1150 return self._config_data.cluster.master_ip
1152 @locking.ssynchronized(_config_lock, shared=1)
1153 def GetMasterNetdev(self):
1154 """Get the master network device for this cluster.
1157 return self._config_data.cluster.master_netdev
1159 @locking.ssynchronized(_config_lock, shared=1)
1160 def GetMasterNetmask(self):
1161 """Get the netmask of the master node for this cluster.
1164 return self._config_data.cluster.master_netmask
1166 @locking.ssynchronized(_config_lock, shared=1)
1167 def GetUseExternalMipScript(self):
1168 """Get flag representing whether to use the external master IP setup script.
1171 return self._config_data.cluster.use_external_mip_script
1173 @locking.ssynchronized(_config_lock, shared=1)
1174 def GetFileStorageDir(self):
1175 """Get the file storage dir for this cluster.
1178 return self._config_data.cluster.file_storage_dir
1180 @locking.ssynchronized(_config_lock, shared=1)
1181 def GetSharedFileStorageDir(self):
1182 """Get the shared file storage dir for this cluster.
1185 return self._config_data.cluster.shared_file_storage_dir
1187 @locking.ssynchronized(_config_lock, shared=1)
1188 def GetHypervisorType(self):
1189 """Get the hypervisor type for this cluster.
1192 return self._config_data.cluster.enabled_hypervisors[0]
1194 @locking.ssynchronized(_config_lock, shared=1)
1195 def GetHostKey(self):
1196 """Return the rsa hostkey from the config.
1199 @return: the rsa hostkey
1202 return self._config_data.cluster.rsahostkeypub
1204 @locking.ssynchronized(_config_lock, shared=1)
1205 def GetDefaultIAllocator(self):
1206 """Get the default instance allocator for this cluster.
1209 return self._config_data.cluster.default_iallocator
1211 @locking.ssynchronized(_config_lock, shared=1)
1212 def GetPrimaryIPFamily(self):
1213 """Get cluster primary ip family.
1215 @return: primary ip family
1218 return self._config_data.cluster.primary_ip_family
1220 @locking.ssynchronized(_config_lock, shared=1)
1221 def GetMasterNetworkParameters(self):
1222 """Get network parameters of the master node.
1224 @rtype: L{object.MasterNetworkParameters}
1225 @return: network parameters of the master node
1228 cluster = self._config_data.cluster
1229 result = objects.MasterNetworkParameters(
1230 uuid=cluster.master_node, ip=cluster.master_ip,
1231 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1232 ip_family=cluster.primary_ip_family)
1236 @locking.ssynchronized(_config_lock)
1237 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1238 """Add a node group to the configuration.
1240 This method calls group.UpgradeConfig() to fill any missing attributes
1241 according to their default values.
1243 @type group: L{objects.NodeGroup}
1244 @param group: the NodeGroup object to add
1246 @param ec_id: unique id for the job to use when creating a missing UUID
1247 @type check_uuid: bool
1248 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1249 it does, ensure that it does not exist in the
1250 configuration already
1253 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1256 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1257 """Add a node group to the configuration.
1260 logging.info("Adding node group %s to configuration", group.name)
1262 # Some code might need to add a node group with a pre-populated UUID
1263 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1264 # the "does this UUID" exist already check.
1266 self._EnsureUUID(group, ec_id)
1269 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1270 except errors.OpPrereqError:
1273 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1274 " node group (UUID: %s)" %
1275 (group.name, existing_uuid),
1276 errors.ECODE_EXISTS)
1279 group.ctime = group.mtime = time.time()
1280 group.UpgradeConfig()
1282 self._config_data.nodegroups[group.uuid] = group
1283 self._config_data.cluster.serial_no += 1
1285 @locking.ssynchronized(_config_lock)
1286 def RemoveNodeGroup(self, group_uuid):
1287 """Remove a node group from the configuration.
1289 @type group_uuid: string
1290 @param group_uuid: the UUID of the node group to remove
1293 logging.info("Removing node group %s from configuration", group_uuid)
1295 if group_uuid not in self._config_data.nodegroups:
1296 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1298 assert len(self._config_data.nodegroups) != 1, \
1299 "Group '%s' is the only group, cannot be removed" % group_uuid
1301 del self._config_data.nodegroups[group_uuid]
1302 self._config_data.cluster.serial_no += 1
1305 def _UnlockedLookupNodeGroup(self, target):
1306 """Lookup a node group's UUID.
1308 @type target: string or None
1309 @param target: group name or UUID or None to look for the default
1311 @return: nodegroup UUID
1312 @raises errors.OpPrereqError: when the target group cannot be found
1316 if len(self._config_data.nodegroups) != 1:
1317 raise errors.OpPrereqError("More than one node group exists. Target"
1318 " group must be specified explicitly.")
1320 return self._config_data.nodegroups.keys()[0]
1321 if target in self._config_data.nodegroups:
1323 for nodegroup in self._config_data.nodegroups.values():
1324 if nodegroup.name == target:
1325 return nodegroup.uuid
1326 raise errors.OpPrereqError("Node group '%s' not found" % target,
1329 @locking.ssynchronized(_config_lock, shared=1)
1330 def LookupNodeGroup(self, target):
1331 """Lookup a node group's UUID.
1333 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1335 @type target: string or None
1336 @param target: group name or UUID or None to look for the default
1338 @return: nodegroup UUID
1341 return self._UnlockedLookupNodeGroup(target)
1343 def _UnlockedGetNodeGroup(self, uuid):
1344 """Lookup a node group.
1347 @param uuid: group UUID
1348 @rtype: L{objects.NodeGroup} or None
1349 @return: nodegroup object, or None if not found
1352 if uuid not in self._config_data.nodegroups:
1355 return self._config_data.nodegroups[uuid]
1357 @locking.ssynchronized(_config_lock, shared=1)
1358 def GetNodeGroup(self, uuid):
1359 """Lookup a node group.
1362 @param uuid: group UUID
1363 @rtype: L{objects.NodeGroup} or None
1364 @return: nodegroup object, or None if not found
1367 return self._UnlockedGetNodeGroup(uuid)
1369 @locking.ssynchronized(_config_lock, shared=1)
1370 def GetAllNodeGroupsInfo(self):
1371 """Get the configuration of all node groups.
1374 return dict(self._config_data.nodegroups)
1376 @locking.ssynchronized(_config_lock, shared=1)
1377 def GetNodeGroupList(self):
1378 """Get a list of node groups.
1381 return self._config_data.nodegroups.keys()
1383 @locking.ssynchronized(_config_lock, shared=1)
1384 def GetNodeGroupMembersByNodes(self, nodes):
1385 """Get nodes which are member in the same nodegroups as the given nodes.
1388 ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1389 return frozenset(member_uuid
1390 for node_uuid in nodes
1392 self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1394 @locking.ssynchronized(_config_lock, shared=1)
1395 def GetMultiNodeGroupInfo(self, group_uuids):
1396 """Get the configuration of multiple node groups.
1398 @param group_uuids: List of node group UUIDs
1400 @return: List of tuples of (group_uuid, group_info)
1403 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1405 @locking.ssynchronized(_config_lock)
1406 def AddInstance(self, instance, ec_id):
1407 """Add an instance to the config.
1409 This should be used after creating a new instance.
1411 @type instance: L{objects.Instance}
1412 @param instance: the instance object
1415 if not isinstance(instance, objects.Instance):
1416 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1418 if instance.disk_template != constants.DT_DISKLESS:
1419 all_lvs = instance.MapLVsByNode()
1420 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1422 all_macs = self._AllMACs()
1423 for nic in instance.nics:
1424 if nic.mac in all_macs:
1425 raise errors.ConfigurationError("Cannot add instance %s:"
1426 " MAC address '%s' already in use." %
1427 (instance.name, nic.mac))
1429 self._EnsureUUID(instance, ec_id)
1431 instance.serial_no = 1
1432 instance.ctime = instance.mtime = time.time()
1433 self._config_data.instances[instance.name] = instance
1434 self._config_data.cluster.serial_no += 1
1435 self._UnlockedReleaseDRBDMinors(instance.name)
1436 self._UnlockedCommitTemporaryIps(ec_id)
1439 def _EnsureUUID(self, item, ec_id):
1440 """Ensures a given object has a valid UUID.
1442 @param item: the instance or node to be checked
1443 @param ec_id: the execution context id for the uuid reservation
1447 item.uuid = self._GenerateUniqueID(ec_id)
1448 elif item.uuid in self._AllIDs(include_temporary=True):
1449 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1450 " in use" % (item.name, item.uuid))
1452 def _SetInstanceStatus(self, instance_name, status, disks_active):
1453 """Set the instance's status to a given value.
1456 if instance_name not in self._config_data.instances:
1457 raise errors.ConfigurationError("Unknown instance '%s'" %
1459 instance = self._config_data.instances[instance_name]
1462 status = instance.admin_state
1463 if disks_active is None:
1464 disks_active = instance.disks_active
1466 assert status in constants.ADMINST_ALL, \
1467 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1469 if instance.admin_state != status or \
1470 instance.disks_active != disks_active:
1471 instance.admin_state = status
1472 instance.disks_active = disks_active
1473 instance.serial_no += 1
1474 instance.mtime = time.time()
1477 @locking.ssynchronized(_config_lock)
1478 def MarkInstanceUp(self, instance_name):
1479 """Mark the instance status to up in the config.
1481 This also sets the instance disks active flag.
1484 self._SetInstanceStatus(instance_name, constants.ADMINST_UP, True)
1486 @locking.ssynchronized(_config_lock)
1487 def MarkInstanceOffline(self, instance_name):
1488 """Mark the instance status to down in the config.
1490 This also clears the instance disks active flag.
1493 self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE, False)
1495 @locking.ssynchronized(_config_lock)
1496 def RemoveInstance(self, instance_name):
1497 """Remove the instance from the configuration.
1500 if instance_name not in self._config_data.instances:
1501 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1503 # If a network port has been allocated to the instance,
1504 # return it to the pool of free ports.
1505 inst = self._config_data.instances[instance_name]
1506 network_port = getattr(inst, "network_port", None)
1507 if network_port is not None:
1508 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1510 instance = self._UnlockedGetInstanceInfo(instance_name)
1512 for nic in instance.nics:
1513 if nic.network and nic.ip:
1514 # Return all IP addresses to the respective address pools
1515 self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1517 del self._config_data.instances[instance_name]
1518 self._config_data.cluster.serial_no += 1
1521 @locking.ssynchronized(_config_lock)
1522 def RenameInstance(self, old_name, new_name):
1523 """Rename an instance.
1525 This needs to be done in ConfigWriter and not by RemoveInstance
1526 combined with AddInstance as only we can guarantee an atomic
1530 if old_name not in self._config_data.instances:
1531 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1533 # Operate on a copy to not loose instance object in case of a failure
1534 inst = self._config_data.instances[old_name].Copy()
1535 inst.name = new_name
1537 for (idx, disk) in enumerate(inst.disks):
1538 if disk.dev_type == constants.LD_FILE:
1539 # rename the file paths in logical and physical id
1540 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1541 disk.logical_id = (disk.logical_id[0],
1542 utils.PathJoin(file_storage_dir, inst.name,
1544 disk.physical_id = disk.logical_id
1546 # Actually replace instance object
1547 del self._config_data.instances[old_name]
1548 self._config_data.instances[inst.name] = inst
1550 # Force update of ssconf files
1551 self._config_data.cluster.serial_no += 1
1555 @locking.ssynchronized(_config_lock)
1556 def MarkInstanceDown(self, instance_name):
1557 """Mark the status of an instance to down in the configuration.
1559 This does not touch the instance disks active flag, as shut down instances
1560 can still have active disks.
1563 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN, None)
1565 @locking.ssynchronized(_config_lock)
1566 def MarkInstanceDisksActive(self, instance_name):
1567 """Mark the status of instance disks active.
1570 self._SetInstanceStatus(instance_name, None, True)
1572 @locking.ssynchronized(_config_lock)
1573 def MarkInstanceDisksInactive(self, instance_name):
1574 """Mark the status of instance disks inactive.
1577 self._SetInstanceStatus(instance_name, None, False)
1579 def _UnlockedGetInstanceList(self):
1580 """Get the list of instances.
1582 This function is for internal use, when the config lock is already held.
1585 return self._config_data.instances.keys()
1587 @locking.ssynchronized(_config_lock, shared=1)
1588 def GetInstanceList(self):
1589 """Get the list of instances.
1591 @return: array of instances, ex. ['instance2.example.com',
1592 'instance1.example.com']
1595 return self._UnlockedGetInstanceList()
1597 def ExpandInstanceName(self, short_name):
1598 """Attempt to expand an incomplete instance name.
1601 # Locking is done in L{ConfigWriter.GetInstanceList}
1602 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1604 def _UnlockedGetInstanceInfo(self, instance_name):
1605 """Returns information about an instance.
1607 This function is for internal use, when the config lock is already held.
1610 if instance_name not in self._config_data.instances:
1613 return self._config_data.instances[instance_name]
1615 @locking.ssynchronized(_config_lock, shared=1)
1616 def GetInstanceInfo(self, instance_name):
1617 """Returns information about an instance.
1619 It takes the information from the configuration file. Other information of
1620 an instance are taken from the live systems.
1622 @param instance_name: name of the instance, e.g.
1623 I{instance1.example.com}
1625 @rtype: L{objects.Instance}
1626 @return: the instance object
1629 return self._UnlockedGetInstanceInfo(instance_name)
1631 @locking.ssynchronized(_config_lock, shared=1)
1632 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1633 """Returns set of node group UUIDs for instance's nodes.
1638 instance = self._UnlockedGetInstanceInfo(instance_name)
1640 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1643 nodes = [instance.primary_node]
1645 nodes = instance.all_nodes
1647 return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1648 for node_uuid in nodes)
1650 @locking.ssynchronized(_config_lock, shared=1)
1651 def GetInstanceNetworks(self, instance_name):
1652 """Returns set of network UUIDs for instance's nics.
1657 instance = self._UnlockedGetInstanceInfo(instance_name)
1659 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1662 for nic in instance.nics:
1664 networks.add(nic.network)
1666 return frozenset(networks)
1668 @locking.ssynchronized(_config_lock, shared=1)
1669 def GetMultiInstanceInfo(self, instances):
1670 """Get the configuration of multiple instances.
1672 @param instances: list of instance names
1674 @return: list of tuples (instance, instance_info), where
1675 instance_info is what would GetInstanceInfo return for the
1676 node, while keeping the original order
1679 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1681 @locking.ssynchronized(_config_lock, shared=1)
1682 def GetAllInstancesInfo(self):
1683 """Get the configuration of all instances.
1686 @return: dict of (instance, instance_info), where instance_info is what
1687 would GetInstanceInfo return for the node
1690 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1691 for instance in self._UnlockedGetInstanceList()])
1694 @locking.ssynchronized(_config_lock, shared=1)
1695 def GetInstancesInfoByFilter(self, filter_fn):
1696 """Get instance configuration with a filter.
1698 @type filter_fn: callable
1699 @param filter_fn: Filter function receiving instance object as parameter,
1700 returning boolean. Important: this function is called while the
1701 configuration locks is held. It must not do any complex work or call
1702 functions potentially leading to a deadlock. Ideally it doesn't call any
1703 other functions and just compares instance attributes.
1706 return dict((name, inst)
1707 for (name, inst) in self._config_data.instances.items()
1710 @locking.ssynchronized(_config_lock)
1711 def AddNode(self, node, ec_id):
1712 """Add a node to the configuration.
1714 @type node: L{objects.Node}
1715 @param node: a Node instance
1718 logging.info("Adding node %s to configuration", node.name)
1720 self._EnsureUUID(node, ec_id)
1723 node.ctime = node.mtime = time.time()
1724 self._UnlockedAddNodeToGroup(node.uuid, node.group)
1725 self._config_data.nodes[node.uuid] = node
1726 self._config_data.cluster.serial_no += 1
1729 @locking.ssynchronized(_config_lock)
1730 def RemoveNode(self, node_uuid):
1731 """Remove a node from the configuration.
1734 logging.info("Removing node %s from configuration", node_uuid)
1736 if node_uuid not in self._config_data.nodes:
1737 raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1739 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1740 del self._config_data.nodes[node_uuid]
1741 self._config_data.cluster.serial_no += 1
1744 def ExpandNodeName(self, short_name):
1745 """Attempt to expand an incomplete node name into a node UUID.
1748 # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1749 all_nodes = self.GetAllNodesInfo().values()
1750 expanded_name = _MatchNameComponentIgnoreCase(
1751 short_name, [node.name for node in all_nodes])
1753 if expanded_name is not None:
1754 # there has to be exactly one node whith that name
1755 node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1756 return (node.uuid, node.name)
1760 def _UnlockedGetNodeInfo(self, node_uuid):
1761 """Get the configuration of a node, as stored in the config.
1763 This function is for internal use, when the config lock is already
1766 @param node_uuid: the node UUID
1768 @rtype: L{objects.Node}
1769 @return: the node object
1772 if node_uuid not in self._config_data.nodes:
1775 return self._config_data.nodes[node_uuid]
1777 @locking.ssynchronized(_config_lock, shared=1)
1778 def GetNodeInfo(self, node_uuid):
1779 """Get the configuration of a node, as stored in the config.
1781 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1783 @param node_uuid: the node UUID
1785 @rtype: L{objects.Node}
1786 @return: the node object
1789 return self._UnlockedGetNodeInfo(node_uuid)
1791 @locking.ssynchronized(_config_lock, shared=1)
1792 def GetNodeInstances(self, node_uuid):
1793 """Get the instances of a node, as stored in the config.
1795 @param node_uuid: the node UUID
1797 @rtype: (list, list)
1798 @return: a tuple with two lists: the primary and the secondary instances
1803 for inst in self._config_data.instances.values():
1804 if inst.primary_node == node_uuid:
1805 pri.append(inst.name)
1806 if node_uuid in inst.secondary_nodes:
1807 sec.append(inst.name)
1810 @locking.ssynchronized(_config_lock, shared=1)
1811 def GetNodeGroupInstances(self, uuid, primary_only=False):
1812 """Get the instances of a node group.
1814 @param uuid: Node group UUID
1815 @param primary_only: Whether to only consider primary nodes
1817 @return: List of instance names in node group
1821 nodes_fn = lambda inst: [inst.primary_node]
1823 nodes_fn = lambda inst: inst.all_nodes
1825 return frozenset(inst.name
1826 for inst in self._config_data.instances.values()
1827 for node_uuid in nodes_fn(inst)
1828 if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1830 def _UnlockedGetHvparamsString(self, hvname):
1831 """Return the string representation of the list of hyervisor parameters of
1832 the given hypervisor.
1834 @see: C{GetHvparams}
1838 hvparams = self._config_data.cluster.hvparams[hvname]
1839 for key in hvparams:
1840 result += "%s=%s\n" % (key, hvparams[key])
1843 @locking.ssynchronized(_config_lock, shared=1)
1844 def GetHvparamsString(self, hvname):
1845 """Return the hypervisor parameters of the given hypervisor.
1847 @type hvname: string
1848 @param hvname: name of a hypervisor
1850 @return: string containing key-value-pairs, one pair on each line;
1854 return self._UnlockedGetHvparamsString(hvname)
1856 def _UnlockedGetNodeList(self):
1857 """Return the list of nodes which are in the configuration.
1859 This function is for internal use, when the config lock is already
1865 return self._config_data.nodes.keys()
1867 @locking.ssynchronized(_config_lock, shared=1)
1868 def GetNodeList(self):
1869 """Return the list of nodes which are in the configuration.
1872 return self._UnlockedGetNodeList()
1874 def _UnlockedGetOnlineNodeList(self):
1875 """Return the list of nodes which are online.
1878 all_nodes = [self._UnlockedGetNodeInfo(node)
1879 for node in self._UnlockedGetNodeList()]
1880 return [node.uuid for node in all_nodes if not node.offline]
1882 @locking.ssynchronized(_config_lock, shared=1)
1883 def GetOnlineNodeList(self):
1884 """Return the list of nodes which are online.
1887 return self._UnlockedGetOnlineNodeList()
1889 @locking.ssynchronized(_config_lock, shared=1)
1890 def GetVmCapableNodeList(self):
1891 """Return the list of nodes which are not vm capable.
1894 all_nodes = [self._UnlockedGetNodeInfo(node)
1895 for node in self._UnlockedGetNodeList()]
1896 return [node.uuid for node in all_nodes if node.vm_capable]
1898 @locking.ssynchronized(_config_lock, shared=1)
1899 def GetNonVmCapableNodeList(self):
1900 """Return the list of nodes which are not vm capable.
1903 all_nodes = [self._UnlockedGetNodeInfo(node)
1904 for node in self._UnlockedGetNodeList()]
1905 return [node.uuid for node in all_nodes if not node.vm_capable]
1907 @locking.ssynchronized(_config_lock, shared=1)
1908 def GetMultiNodeInfo(self, node_uuids):
1909 """Get the configuration of multiple nodes.
1911 @param node_uuids: list of node UUIDs
1913 @return: list of tuples of (node, node_info), where node_info is
1914 what would GetNodeInfo return for the node, in the original
1918 return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
1920 def _UnlockedGetAllNodesInfo(self):
1921 """Gets configuration of all nodes.
1923 @note: See L{GetAllNodesInfo}
1926 return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
1927 for node_uuid in self._UnlockedGetNodeList()])
1929 @locking.ssynchronized(_config_lock, shared=1)
1930 def GetAllNodesInfo(self):
1931 """Get the configuration of all nodes.
1934 @return: dict of (node, node_info), where node_info is what
1935 would GetNodeInfo return for the node
1938 return self._UnlockedGetAllNodesInfo()
1940 def _UnlockedGetNodeInfoByName(self, node_name):
1941 for node in self._UnlockedGetAllNodesInfo().values():
1942 if node.name == node_name:
1946 @locking.ssynchronized(_config_lock, shared=1)
1947 def GetNodeInfoByName(self, node_name):
1948 """Get the L{objects.Node} object for a named node.
1950 @param node_name: name of the node to get information for
1951 @type node_name: string
1952 @return: the corresponding L{objects.Node} instance or None if no
1953 information is available
1956 return self._UnlockedGetNodeInfoByName(node_name)
1958 def _UnlockedGetNodeName(self, node_spec):
1959 if isinstance(node_spec, objects.Node):
1960 return node_spec.name
1961 elif isinstance(node_spec, basestring):
1962 node_info = self._UnlockedGetNodeInfo(node_spec)
1963 if node_info is None:
1964 raise errors.OpExecError("Unknown node: %s" % node_spec)
1965 return node_info.name
1967 raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
1969 @locking.ssynchronized(_config_lock, shared=1)
1970 def GetNodeName(self, node_spec):
1971 """Gets the node name for the passed node.
1973 @param node_spec: node to get names for
1974 @type node_spec: either node UUID or a L{objects.Node} object
1979 return self._UnlockedGetNodeName(node_spec)
1981 def _UnlockedGetNodeNames(self, node_specs):
1982 return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
1984 @locking.ssynchronized(_config_lock, shared=1)
1985 def GetNodeNames(self, node_specs):
1986 """Gets the node names for the passed list of nodes.
1988 @param node_specs: list of nodes to get names for
1989 @type node_specs: list of either node UUIDs or L{objects.Node} objects
1990 @rtype: list of strings
1991 @return: list of node names
1994 return self._UnlockedGetNodeNames(node_specs)
1996 @locking.ssynchronized(_config_lock, shared=1)
1997 def GetNodeGroupsFromNodes(self, node_uuids):
1998 """Returns groups for a list of nodes.
2000 @type node_uuids: list of string
2001 @param node_uuids: List of node UUIDs
2005 return frozenset(self._UnlockedGetNodeInfo(uuid).group
2006 for uuid in node_uuids)
2008 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2009 """Get the number of current and maximum desired and possible candidates.
2011 @type exceptions: list
2012 @param exceptions: if passed, list of nodes that should be ignored
2014 @return: tuple of (current, desired and possible, possible)
2017 mc_now = mc_should = mc_max = 0
2018 for node in self._config_data.nodes.values():
2019 if exceptions and node.uuid in exceptions:
2021 if not (node.offline or node.drained) and node.master_capable:
2023 if node.master_candidate:
2025 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2026 return (mc_now, mc_should, mc_max)
2028 @locking.ssynchronized(_config_lock, shared=1)
2029 def GetMasterCandidateStats(self, exceptions=None):
2030 """Get the number of current and maximum possible candidates.
2032 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2034 @type exceptions: list
2035 @param exceptions: if passed, list of nodes that should be ignored
2037 @return: tuple of (current, max)
2040 return self._UnlockedGetMasterCandidateStats(exceptions)
2042 @locking.ssynchronized(_config_lock)
2043 def MaintainCandidatePool(self, exception_node_uuids):
2044 """Try to grow the candidate pool to the desired size.
2046 @type exception_node_uuids: list
2047 @param exception_node_uuids: if passed, list of nodes that should be ignored
2049 @return: list with the adjusted nodes (L{objects.Node} instances)
2052 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2053 exception_node_uuids)
2056 node_list = self._config_data.nodes.keys()
2057 random.shuffle(node_list)
2058 for uuid in node_list:
2059 if mc_now >= mc_max:
2061 node = self._config_data.nodes[uuid]
2062 if (node.master_candidate or node.offline or node.drained or
2063 node.uuid in exception_node_uuids or not node.master_capable):
2065 mod_list.append(node)
2066 node.master_candidate = True
2069 if mc_now != mc_max:
2070 # this should not happen
2071 logging.warning("Warning: MaintainCandidatePool didn't manage to"
2072 " fill the candidate pool (%d/%d)", mc_now, mc_max)
2074 self._config_data.cluster.serial_no += 1
2079 def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2080 """Add a given node to the specified group.
2083 if nodegroup_uuid not in self._config_data.nodegroups:
2084 # This can happen if a node group gets deleted between its lookup and
2085 # when we're adding the first node to it, since we don't keep a lock in
2086 # the meantime. It's ok though, as we'll fail cleanly if the node group
2087 # is not found anymore.
2088 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2089 if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2090 self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2092 def _UnlockedRemoveNodeFromGroup(self, node):
2093 """Remove a given node from its group.
2096 nodegroup = node.group
2097 if nodegroup not in self._config_data.nodegroups:
2098 logging.warning("Warning: node '%s' has unknown node group '%s'"
2099 " (while being removed from it)", node.uuid, nodegroup)
2100 nodegroup_obj = self._config_data.nodegroups[nodegroup]
2101 if node.uuid not in nodegroup_obj.members:
2102 logging.warning("Warning: node '%s' not a member of its node group '%s'"
2103 " (while being removed from it)", node.uuid, nodegroup)
2105 nodegroup_obj.members.remove(node.uuid)
2107 @locking.ssynchronized(_config_lock)
2108 def AssignGroupNodes(self, mods):
2109 """Changes the group of a number of nodes.
2111 @type mods: list of tuples; (node name, new group UUID)
2112 @param mods: Node membership modifications
2115 groups = self._config_data.nodegroups
2116 nodes = self._config_data.nodes
2120 # Try to resolve UUIDs first
2121 for (node_uuid, new_group_uuid) in mods:
2123 node = nodes[node_uuid]
2125 raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2127 if node.group == new_group_uuid:
2128 # Node is being assigned to its current group
2129 logging.debug("Node '%s' was assigned to its current group (%s)",
2130 node_uuid, node.group)
2133 # Try to find current group of node
2135 old_group = groups[node.group]
2137 raise errors.ConfigurationError("Unable to find old group '%s'" %
2140 # Try to find new group for node
2142 new_group = groups[new_group_uuid]
2144 raise errors.ConfigurationError("Unable to find new group '%s'" %
2147 assert node.uuid in old_group.members, \
2148 ("Inconsistent configuration: node '%s' not listed in members for its"
2149 " old group '%s'" % (node.uuid, old_group.uuid))
2150 assert node.uuid not in new_group.members, \
2151 ("Inconsistent configuration: node '%s' already listed in members for"
2152 " its new group '%s'" % (node.uuid, new_group.uuid))
2154 resmod.append((node, old_group, new_group))
2157 for (node, old_group, new_group) in resmod:
2158 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2159 "Assigning to current group is not possible"
2161 node.group = new_group.uuid
2163 # Update members of involved groups
2164 if node.uuid in old_group.members:
2165 old_group.members.remove(node.uuid)
2166 if node.uuid not in new_group.members:
2167 new_group.members.append(node.uuid)
2169 # Update timestamps and serials (only once per node/group object)
2171 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2175 # Force ssconf update
2176 self._config_data.cluster.serial_no += 1
2180 def _BumpSerialNo(self):
2181 """Bump up the serial number of the config.
2184 self._config_data.serial_no += 1
2185 self._config_data.mtime = time.time()
2187 def _AllUUIDObjects(self):
2188 """Returns all objects with uuid attributes.
2191 return (self._config_data.instances.values() +
2192 self._config_data.nodes.values() +
2193 self._config_data.nodegroups.values() +
2194 self._config_data.networks.values() +
2197 [self._config_data.cluster])
2199 def _OpenConfig(self, accept_foreign):
2200 """Read the config data from disk.
2203 raw_data = utils.ReadFile(self._cfg_file)
2206 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2207 except Exception, err:
2208 raise errors.ConfigurationError(err)
2210 # Make sure the configuration has the right version
2211 _ValidateConfig(data)
2213 if (not hasattr(data, "cluster") or
2214 not hasattr(data.cluster, "rsahostkeypub")):
2215 raise errors.ConfigurationError("Incomplete configuration"
2216 " (missing cluster.rsahostkeypub)")
2218 if not data.cluster.master_node in data.nodes:
2219 msg = ("The configuration denotes node %s as master, but does not"
2220 " contain information about this node" %
2221 data.cluster.master_node)
2222 raise errors.ConfigurationError(msg)
2224 master_info = data.nodes[data.cluster.master_node]
2225 if master_info.name != self._my_hostname and not accept_foreign:
2226 msg = ("The configuration denotes node %s as master, while my"
2227 " hostname is %s; opening a foreign configuration is only"
2228 " possible in accept_foreign mode" %
2229 (master_info.name, self._my_hostname))
2230 raise errors.ConfigurationError(msg)
2232 self._config_data = data
2233 # reset the last serial as -1 so that the next write will cause
2235 self._last_cluster_serial = -1
2237 # Upgrade configuration if needed
2238 self._UpgradeConfig()
2240 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2242 def _UpgradeConfig(self):
2243 """Run any upgrade steps.
2245 This method performs both in-object upgrades and also update some data
2246 elements that need uniqueness across the whole configuration or interact
2249 @warning: this function will call L{_WriteConfig()}, but also
2250 L{DropECReservations} so it needs to be called only from a
2251 "safe" place (the constructor). If one wanted to call it with
2252 the lock held, a DropECReservationUnlocked would need to be
2253 created first, to avoid causing deadlock.
2256 # Keep a copy of the persistent part of _config_data to check for changes
2257 # Serialization doesn't guarantee order in dictionaries
2258 oldconf = copy.deepcopy(self._config_data.ToDict())
2260 # In-object upgrades
2261 self._config_data.UpgradeConfig()
2263 for item in self._AllUUIDObjects():
2264 if item.uuid is None:
2265 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2266 if not self._config_data.nodegroups:
2267 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2268 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2270 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2271 for node in self._config_data.nodes.values():
2273 node.group = self.LookupNodeGroup(None)
2274 # This is technically *not* an upgrade, but needs to be done both when
2275 # nodegroups are being added, and upon normally loading the config,
2276 # because the members list of a node group is discarded upon
2277 # serializing/deserializing the object.
2278 self._UnlockedAddNodeToGroup(node.uuid, node.group)
2280 modified = (oldconf != self._config_data.ToDict())
2283 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2284 # only called at config init time, without the lock held
2285 self.DropECReservations(_UPGRADE_CONFIG_JID)
2287 config_errors = self._UnlockedVerifyConfig()
2289 errmsg = ("Loaded configuration data is not consistent: %s" %
2290 (utils.CommaJoin(config_errors)))
2291 logging.critical(errmsg)
2293 def _DistributeConfig(self, feedback_fn):
2294 """Distribute the configuration to the other nodes.
2296 Currently, this only copies the configuration file. In the future,
2297 it could be used to encapsulate the 2/3-phase update mechanism.
2307 myhostname = self._my_hostname
2308 # we can skip checking whether _UnlockedGetNodeInfo returns None
2309 # since the node list comes from _UnlocketGetNodeList, and we are
2310 # called with the lock held, so no modifications should take place
2312 for node_uuid in self._UnlockedGetNodeList():
2313 node_info = self._UnlockedGetNodeInfo(node_uuid)
2314 if node_info.name == myhostname or not node_info.master_candidate:
2316 node_list.append(node_info.name)
2317 addr_list.append(node_info.primary_ip)
2319 # TODO: Use dedicated resolver talking to config writer for name resolution
2321 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2322 for to_node, to_result in result.items():
2323 msg = to_result.fail_msg
2325 msg = ("Copy of file %s to node %s failed: %s" %
2326 (self._cfg_file, to_node, msg))
2336 def _WriteConfig(self, destination=None, feedback_fn=None):
2337 """Write the configuration data to persistent storage.
2340 assert feedback_fn is None or callable(feedback_fn)
2342 # Warn on config errors, but don't abort the save - the
2343 # configuration has already been modified, and we can't revert;
2344 # the best we can do is to warn the user and save as is, leaving
2345 # recovery to the user
2346 config_errors = self._UnlockedVerifyConfig()
2348 errmsg = ("Configuration data is not consistent: %s" %
2349 (utils.CommaJoin(config_errors)))
2350 logging.critical(errmsg)
2354 if destination is None:
2355 destination = self._cfg_file
2356 self._BumpSerialNo()
2357 txt = serializer.Dump(self._config_data.ToDict())
2359 getents = self._getents()
2361 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2362 close=False, gid=getents.confd_gid, mode=0640)
2363 except errors.LockError:
2364 raise errors.ConfigurationError("The configuration file has been"
2365 " modified since the last write, cannot"
2368 self._cfg_id = utils.GetFileID(fd=fd)
2372 self.write_count += 1
2374 # and redistribute the config file to master candidates
2375 self._DistributeConfig(feedback_fn)
2377 # Write ssconf files on all nodes (including locally)
2378 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2379 if not self._offline:
2380 result = self._GetRpc(None).call_write_ssconf_files(
2381 self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2382 self._UnlockedGetSsconfValues())
2384 for nname, nresu in result.items():
2385 msg = nresu.fail_msg
2387 errmsg = ("Error while uploading ssconf files to"
2388 " node %s: %s" % (nname, msg))
2389 logging.warning(errmsg)
2394 self._last_cluster_serial = self._config_data.cluster.serial_no
2396 def _GetAllHvparamsStrings(self, hypervisors):
2397 """Get the hvparams of all given hypervisors from the config.
2399 @type hypervisors: list of string
2400 @param hypervisors: list of hypervisor names
2401 @rtype: dict of strings
2402 @returns: dictionary mapping the hypervisor name to a string representation
2403 of the hypervisor's hvparams
2407 for hv in hypervisors:
2408 hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2412 def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2413 """Extends the ssconf_values dictionary by hvparams.
2415 @type ssconf_values: dict of strings
2416 @param ssconf_values: dictionary mapping ssconf_keys to strings
2417 representing the content of ssconf files
2418 @type all_hvparams: dict of strings
2419 @param all_hvparams: dictionary mapping hypervisor names to a string
2420 representation of their hvparams
2421 @rtype: same as ssconf_values
2422 @returns: the ssconf_values dictionary extended by hvparams
2425 for hv in all_hvparams:
2426 ssconf_key = constants.SS_HVPARAMS_PREF + hv
2427 ssconf_values[ssconf_key] = all_hvparams[hv]
2428 return ssconf_values
2430 def _UnlockedGetSsconfValues(self):
2431 """Return the values needed by ssconf.
2434 @return: a dictionary with keys the ssconf names and values their
2439 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2440 node_infos = self._UnlockedGetAllNodesInfo().values()
2441 node_names = [node.name for node in node_infos]
2442 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2443 for ninfo in node_infos]
2444 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2445 for ninfo in node_infos]
2447 instance_data = fn(instance_names)
2448 off_data = fn(node.name for node in node_infos if node.offline)
2449 on_data = fn(node.name for node in node_infos if not node.offline)
2450 mc_data = fn(node.name for node in node_infos if node.master_candidate)
2451 mc_ips_data = fn(node.primary_ip for node in node_infos
2452 if node.master_candidate)
2453 node_data = fn(node_names)
2454 node_pri_ips_data = fn(node_pri_ips)
2455 node_snd_ips_data = fn(node_snd_ips)
2457 cluster = self._config_data.cluster
2458 cluster_tags = fn(cluster.GetTags())
2460 hypervisor_list = fn(cluster.enabled_hypervisors)
2461 all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2463 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2465 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2466 self._config_data.nodegroups.values()]
2467 nodegroups_data = fn(utils.NiceSort(nodegroups))
2468 networks = ["%s %s" % (net.uuid, net.name) for net in
2469 self._config_data.networks.values()]
2470 networks_data = fn(utils.NiceSort(networks))
2473 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2474 constants.SS_CLUSTER_TAGS: cluster_tags,
2475 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2476 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2477 constants.SS_MASTER_CANDIDATES: mc_data,
2478 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2479 constants.SS_MASTER_IP: cluster.master_ip,
2480 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2481 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2482 constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2483 constants.SS_NODE_LIST: node_data,
2484 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2485 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2486 constants.SS_OFFLINE_NODES: off_data,
2487 constants.SS_ONLINE_NODES: on_data,
2488 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2489 constants.SS_INSTANCE_LIST: instance_data,
2490 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2491 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2492 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2493 constants.SS_UID_POOL: uid_pool,
2494 constants.SS_NODEGROUPS: nodegroups_data,
2495 constants.SS_NETWORKS: networks_data,
2497 ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2499 bad_values = [(k, v) for k, v in ssconf_values.items()
2500 if not isinstance(v, (str, basestring))]
2502 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2503 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2504 " values: %s" % err)
2505 return ssconf_values
2507 @locking.ssynchronized(_config_lock, shared=1)
2508 def GetSsconfValues(self):
2509 """Wrapper using lock around _UnlockedGetSsconf().
2512 return self._UnlockedGetSsconfValues()
2514 @locking.ssynchronized(_config_lock, shared=1)
2515 def GetVGName(self):
2516 """Return the volume group name.
2519 return self._config_data.cluster.volume_group_name
2521 @locking.ssynchronized(_config_lock)
2522 def SetVGName(self, vg_name):
2523 """Set the volume group name.
2526 self._config_data.cluster.volume_group_name = vg_name
2527 self._config_data.cluster.serial_no += 1
2530 @locking.ssynchronized(_config_lock, shared=1)
2531 def GetDRBDHelper(self):
2532 """Return DRBD usermode helper.
2535 return self._config_data.cluster.drbd_usermode_helper
2537 @locking.ssynchronized(_config_lock)
2538 def SetDRBDHelper(self, drbd_helper):
2539 """Set DRBD usermode helper.
2542 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2543 self._config_data.cluster.serial_no += 1
2546 @locking.ssynchronized(_config_lock, shared=1)
2547 def GetMACPrefix(self):
2548 """Return the mac prefix.
2551 return self._config_data.cluster.mac_prefix
2553 @locking.ssynchronized(_config_lock, shared=1)
2554 def GetClusterInfo(self):
2555 """Returns information about the cluster
2557 @rtype: L{objects.Cluster}
2558 @return: the cluster object
2561 return self._config_data.cluster
2563 @locking.ssynchronized(_config_lock, shared=1)
2564 def HasAnyDiskOfType(self, dev_type):
2565 """Check if in there is at disk of the given type in the configuration.
2568 return self._config_data.HasAnyDiskOfType(dev_type)
2570 @locking.ssynchronized(_config_lock)
2571 def Update(self, target, feedback_fn, ec_id=None):
2572 """Notify function to be called after updates.
2574 This function must be called when an object (as returned by
2575 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2576 caller wants the modifications saved to the backing store. Note
2577 that all modified objects will be saved, but the target argument
2578 is the one the caller wants to ensure that it's saved.
2580 @param target: an instance of either L{objects.Cluster},
2581 L{objects.Node} or L{objects.Instance} which is existing in
2583 @param feedback_fn: Callable feedback function
2586 if self._config_data is None:
2587 raise errors.ProgrammerError("Configuration file not read,"
2589 update_serial = False
2590 if isinstance(target, objects.Cluster):
2591 test = target == self._config_data.cluster
2592 elif isinstance(target, objects.Node):
2593 test = target in self._config_data.nodes.values()
2594 update_serial = True
2595 elif isinstance(target, objects.Instance):
2596 test = target in self._config_data.instances.values()
2597 elif isinstance(target, objects.NodeGroup):
2598 test = target in self._config_data.nodegroups.values()
2599 elif isinstance(target, objects.Network):
2600 test = target in self._config_data.networks.values()
2602 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2603 " ConfigWriter.Update" % type(target))
2605 raise errors.ConfigurationError("Configuration updated since object"
2606 " has been read or unknown object")
2607 target.serial_no += 1
2608 target.mtime = now = time.time()
2611 # for node updates, we need to increase the cluster serial too
2612 self._config_data.cluster.serial_no += 1
2613 self._config_data.cluster.mtime = now
2615 if isinstance(target, objects.Instance):
2616 self._UnlockedReleaseDRBDMinors(target.name)
2618 if ec_id is not None:
2619 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2620 self._UnlockedCommitTemporaryIps(ec_id)
2622 self._WriteConfig(feedback_fn=feedback_fn)
2624 @locking.ssynchronized(_config_lock)
2625 def DropECReservations(self, ec_id):
2626 """Drop per-execution-context reservations
2629 for rm in self._all_rms:
2630 rm.DropECReservations(ec_id)
2632 @locking.ssynchronized(_config_lock, shared=1)
2633 def GetAllNetworksInfo(self):
2634 """Get configuration info of all the networks.
2637 return dict(self._config_data.networks)
2639 def _UnlockedGetNetworkList(self):
2640 """Get the list of networks.
2642 This function is for internal use, when the config lock is already held.
2645 return self._config_data.networks.keys()
2647 @locking.ssynchronized(_config_lock, shared=1)
2648 def GetNetworkList(self):
2649 """Get the list of networks.
2651 @return: array of networks, ex. ["main", "vlan100", "200]
2654 return self._UnlockedGetNetworkList()
2656 @locking.ssynchronized(_config_lock, shared=1)
2657 def GetNetworkNames(self):
2658 """Get a list of network names
2662 for net in self._config_data.networks.values()]
2665 def _UnlockedGetNetwork(self, uuid):
2666 """Returns information about a network.
2668 This function is for internal use, when the config lock is already held.
2671 if uuid not in self._config_data.networks:
2674 return self._config_data.networks[uuid]
2676 @locking.ssynchronized(_config_lock, shared=1)
2677 def GetNetwork(self, uuid):
2678 """Returns information about a network.
2680 It takes the information from the configuration file.
2682 @param uuid: UUID of the network
2684 @rtype: L{objects.Network}
2685 @return: the network object
2688 return self._UnlockedGetNetwork(uuid)
2690 @locking.ssynchronized(_config_lock)
2691 def AddNetwork(self, net, ec_id, check_uuid=True):
2692 """Add a network to the configuration.
2694 @type net: L{objects.Network}
2695 @param net: the Network object to add
2697 @param ec_id: unique id for the job to use when creating a missing UUID
2700 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2703 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2704 """Add a network to the configuration.
2707 logging.info("Adding network %s to configuration", net.name)
2710 self._EnsureUUID(net, ec_id)
2713 self._config_data.networks[net.uuid] = net
2714 self._config_data.cluster.serial_no += 1
2716 def _UnlockedLookupNetwork(self, target):
2717 """Lookup a network's UUID.
2719 @type target: string
2720 @param target: network name or UUID
2722 @return: network UUID
2723 @raises errors.OpPrereqError: when the target network cannot be found
2728 if target in self._config_data.networks:
2730 for net in self._config_data.networks.values():
2731 if net.name == target:
2733 raise errors.OpPrereqError("Network '%s' not found" % target,
2736 @locking.ssynchronized(_config_lock, shared=1)
2737 def LookupNetwork(self, target):
2738 """Lookup a network's UUID.
2740 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2742 @type target: string
2743 @param target: network name or UUID
2745 @return: network UUID
2748 return self._UnlockedLookupNetwork(target)
2750 @locking.ssynchronized(_config_lock)
2751 def RemoveNetwork(self, network_uuid):
2752 """Remove a network from the configuration.
2754 @type network_uuid: string
2755 @param network_uuid: the UUID of the network to remove
2758 logging.info("Removing network %s from configuration", network_uuid)
2760 if network_uuid not in self._config_data.networks:
2761 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2763 del self._config_data.networks[network_uuid]
2764 self._config_data.cluster.serial_no += 1
2767 def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2768 """Get the netparams (mode, link) of a network.
2770 Get a network's netparams for a given node.
2772 @type net_uuid: string
2773 @param net_uuid: network uuid
2774 @type node_uuid: string
2775 @param node_uuid: node UUID
2776 @rtype: dict or None
2780 node_info = self._UnlockedGetNodeInfo(node_uuid)
2781 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2782 netparams = nodegroup_info.networks.get(net_uuid, None)
2786 @locking.ssynchronized(_config_lock, shared=1)
2787 def GetGroupNetParams(self, net_uuid, node_uuid):
2788 """Locking wrapper of _UnlockedGetGroupNetParams()
2791 return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2793 @locking.ssynchronized(_config_lock, shared=1)
2794 def CheckIPInNodeGroup(self, ip, node_uuid):
2795 """Check IP uniqueness in nodegroup.
2797 Check networks that are connected in the node's node group
2798 if ip is contained in any of them. Used when creating/adding
2799 a NIC to ensure uniqueness among nodegroups.
2802 @param ip: ip address
2803 @type node_uuid: string
2804 @param node_uuid: node UUID
2805 @rtype: (string, dict) or (None, None)
2806 @return: (network name, netparams)
2811 node_info = self._UnlockedGetNodeInfo(node_uuid)
2812 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2813 for net_uuid in nodegroup_info.networks.keys():
2814 net_info = self._UnlockedGetNetwork(net_uuid)
2815 pool = network.AddressPool(net_info)
2816 if pool.Contains(ip):
2817 return (net_info.name, nodegroup_info.networks[net_uuid])